Hire an AI Employee
Hire a published AI Employee for a brand. The hire is created immediately and the agent is provisioned in the background — there is no payment step in this flow.
POST /api/v1/employees/{profile_id}/hire
profile_id is the UUID of a published catalog profile (an "AI Employee").
Who can call it¶
The same endpoint serves two callers. The endpoint detects which one you are from your credentials.
Active-brand self-serve. A signed-in brand user hires for their own brand. The brand must have an active subscription. The brand is always taken from your session — you cannot hire on another brand's behalf.
Admin grant. A platform super-admin grants a hire for any brand, optionally
recording a reason (for a comped / free hire). Authenticate with an admin API key
(X-Admin-Key) or an admin dashboard session, and name the target brand in the
body.
Request¶
Self-serve¶
The body is empty — the brand comes from your session.
curl -X POST https://api.opvs.ai/api/v1/employees/$PROFILE_ID/hire \
-H "Cookie: $SESSION_COOKIE" \
-H "Content-Type: application/json" \
-d '{}'
Admin grant¶
curl -X POST https://api.opvs.ai/api/v1/employees/$PROFILE_ID/hire \
-H "X-Admin-Key: $OPVS_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{"brand_id": 11, "comp_reason": "Beta partner — comped"}'
Body fields¶
| Field | Type | Notes |
|---|---|---|
brand_id |
integer | Admin only, required. The brand to hire for. Ignored on the self-serve path. |
comp_reason |
string | Admin only, optional. Rationale for a comped hire. Ignored on the self-serve path. |
Response¶
{
"hire_id": "f1e2d3c4-0000-4444-8888-aaaabbbbcccc",
"status": "provisioning",
"onboarding_url": "/dashboard/agents/onboarding?hire=f1e2d3c4-0000-4444-8888-aaaabbbbcccc"
}
The hire starts in provisioning. The agent (marketplace skills installed, persona
deployed) is built out-of-band; poll the hire or open onboarding_url to watch it
become active.
Status codes¶
| Code | Meaning |
|---|---|
200 |
Hire created; provisioning started. |
401 |
Not authenticated. |
402 |
Self-serve path: the brand's subscription is not active. Ask an admin to grant the hire, or activate a subscription. |
404 |
No published employee with that profile_id. |
409 |
This brand already holds an active hire of this employee. The body's detail.existing_hire_id points at it. |
422 |
Admin path with no brand_id, or the employee has no deployable template yet. |
429 |
Rate limit exceeded (5 hires/brand/hour self-serve, 50 grants/admin/hour). |
Cancel a hire¶
Two endpoints end a hire. They differ in when it ends, and in how much of the agent actually goes away.
POST /api/v1/employees/hires/{hire_id}/cancel
POST /api/v1/employees/hires/{hire_id}/cancel-immediate
An admin may cancel any hire; a brand user may cancel only a hire their own brand holds.
Cancel at period end¶
The agent keeps running until current_period_end. Nothing is disabled at call
time — the reconcile loop performs the marketplace disable once the date passes.
{
"hire_id": "f1e2d3c4-0000-4444-8888-aaaabbbbcccc",
"status": "canceled",
"cancel_at_period_end": true,
"effective_at": "2026-10-07T00:00:00Z",
"marketplace_disable_at": "2026-10-07T00:01:00Z"
}
Cancel immediately¶
The hire flips to canceled now and its marketplace installs are ref-counted
disabled: an install turns off only if no other active hire of the same brand
still references it. Tools disappear within the gateway's 60-second grant-cache
TTL, which is why marketplace_disable_at is a minute after effective_at.
{
"hire_id": "f1e2d3c4-0000-4444-8888-aaaabbbbcccc",
"status": "canceled",
"effective_at": "2026-09-07T12:00:00Z",
"marketplace_disable_at": "2026-09-07T12:01:00Z",
"installs_disabled": 2,
"installs_retained": 1,
"management_record_deleted": true,
"gateway_entry_removed": false,
"agent_torn_down": false
}
Read the teardown fields separately¶
Cancelling revokes an employee's tools. It does not unregister the agent.
| Field | Meaning |
|---|---|
management_record_deleted |
The agent's config.yaml entry and workspace directory were removed. Best-effort. |
gateway_entry_removed |
Whether the gateway's own registration was removed. Always false from a cancel — removing it needs a separate call plus a gateway restart, which a cancel deliberately does not perform. |
agent_torn_down |
Deprecated. Always false. Read the two fields above instead. |
While gateway_entry_removed is false, the persona stays reachable and can
still be messaged, with its marketplace tools disabled. If you are cancelling in
order to stop an agent answering, cancelling is not sufficient on its own.
agent_torn_down previously returned true on the Management API delete alone,
reporting a teardown that had not happened. It now means what it says: true
only when the agent is fully gone, which a cancel cannot achieve.
Status codes¶
| Code | Meaning |
|---|---|
200 |
Cancelled. |
401 |
Not authenticated. |
403 |
The hire belongs to another brand. |
404 |
No such hire. |
409 |
The hire is not in a state that can be cancelled. |