Agents
Agents are the AI voices that answer your calls. Each agent has a language, voice, server_url for real-time call control, and an optional knowledge base for Q&A retrieval.
/v1/agentsList all agents with cursor pagination
/v1/agentsCreate a new agent
/v1/agents/:agentIdGet a single agent by public_id or numeric id
/v1/agents/:agentIdUpdate agent fields (partial update)
/v1/agents/:agentIdDelete an agent (fails if active calls)
/v1/agents/:agentId/cloneClone an agent with all settings and knowledge bases
/v1/agents/:agentId/rotate-webhook-secretIssue a new webhook secret (old one immediately invalidated)
/v1/agents/:agentId/audit-logView change history for this agent
Authentication & rate limits
All agent endpoints require a valid API key passed as Authorization: Bearer sfy_live_YOUR_KEY or via the X-Api-Key header. Common auth-layer responses:
| HTTP | code | When |
|---|---|---|
| 401 | MISSING_API_KEY | No Authorization header or X-Api-Key header present |
| 401 | INVALID_API_KEY | Key does not exist, has been revoked, or is malformed |
| 402 | INSUFFICIENT_CREDITS | Account is paused due to insufficient credits (write operations only) |
| 403 | READ_ONLY_KEY | API key has read-only scope and the request is a write (POST/PATCH/DELETE) |
| 429 | RATE_LIMIT_EXCEEDED | Per-org per-minute rate limit exceeded. Check X-RateLimit-Reset header. |
| 429 | AUTH_RATE_LIMITED | More than 100 failed auth attempts from the same IP in 15 minutes |
Every response includes rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix epoch), and X-Concurrent-Call-Limit. Enterprise accounts are uncapped and do not receive rate limit headers.
The agent object
{
"public_id": "agent_28c51f81",
"name": "Support Agent",
"language": "en-US",
"voice": "Sarah",
"max_duration": 1800,
"recording": true,
"end_call_enabled": false,
"server_url": "https://your-server.com/call-handler",
"webhook_url": "https://your-server.com/webhooks",
"webhook_secret_hint": "...a1b2c3d4",
"webhook_events": ["call.started", "call.ended"],
"knowledge_base_ids": [{ "id": 42, "public_id": "kb_00000001" }],
"created_at": "2026-01-15T10:00:00Z",
"updated_at": "2026-01-15T12:00:00Z"
}webhook_events is null when no webhook subscription is active. knowledge_base_ids is always an array (empty if none attached). webhook_secret_hint is the last 8 characters of the webhook signing secret.
Valid webhook_events values
Use these exact strings in the webhook_events array. Any unrecognized value returns 400 with the full valid list in the error message.
call.ringingcall.startedcall.endedcall.failedcall.recording_readycall.dtmf.receivedcall.gather.completedtranscript.updatedspeech.startedspeech.endedai.speech.startedai.speech.endedbarge_intool.invokedtool.completedtool.timeouttransfer.initiatedtransfer.completedtransfer.failedsms.sentknowledge_base.refreshedknowledge_base.refresh_failedknowledge_base.auto_refresh_disabledaddress.verification.pendingaddress.verification.verifiedaddress.verification.partially_verifiedaddress.verification.rejectedaddress.verification.expiredWhen webhook_events is omitted but webhook_url is set, all events are subscribed by default.
/v1/agentsReturns a paginated list of agents. Uses cursor-based pagination.
| Query param | Default | Description |
|---|---|---|
| limit | 20 | Results per page (max 100) |
| after | — | Cursor from previous response's next_cursor field. Accepts a public_id (agent_*) string or a positive integer agent id. |
| name | — | Partial name filter (case-insensitive LIKE match) |
{ "agents": [...], "next_cursor": "agent_..." | null }. When next_cursor is null there are no more pages. Results are ordered newest-first by internal id.curl "https://api.staffifyai.com/v1/agents?limit=10" \ -H "Authorization: Bearer sfy_live_YOUR_KEY"
/v1/agents| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Display name for the agent. Whitespace is trimmed. | |
| server_url | string | Public HTTPS URL for real-time call control. Must be HTTPS. Private/internal IPs (RFC1918, loopback, link-local, AWS metadata) are rejected — the hostname is DNS-resolved and checked. Cannot be removed once set. | |
| language | string | optional | BCP-47 language code (default: en-US) |
| voice | string | optional | TTS voice name (default: Sarah). Must be valid for the selected language. See /v1/voices for options. |
| max_duration | integer | optional | Max call seconds (default 1800). Values are clamped to the range 60–7200; out-of-range values are silently adjusted. |
| recording | boolean | optional | Record calls (default false) |
| end_call_enabled | boolean | optional | Let agent end the call (default false) |
| webhook_url | string | optional | HTTPS URL for async event webhooks. Same HTTPS and IP restrictions as server_url. Can be set to null in a PATCH to remove it. |
| webhook_events | string[] | optional | Events to subscribe to. Must be a non-empty array of valid event names (see Webhooks reference). Sending an empty array returns 400. |
| knowledge_base_ids | string[] | optional | KB public_ids (kb_*) or numeric ids to attach (max 20). Returns 400 if any id is not found. On PATCH, replaces the full list — send [] to detach all. |
Create response shape
Returns HTTP201. The agent object is wrapped under an agent key. If webhook_url was provided, two additional top-level fields are included:{
"agent": { ...agent object... },
"webhook_secret": "64-char hex string",
"webhook_secret_note": "Save this secret — it will not be shown again. ..."
}curl -X POST https://api.staffifyai.com/v1/agents \
-H "Authorization: Bearer sfy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Sales Agent",
"server_url": "https://your-server.com/call",
"language": "en-US",
"voice": "Sarah",
"recording": true,
"end_call_enabled": false,
"webhook_url": "https://your-server.com/webhooks",
"webhook_events": ["call.started", "call.ended", "transcript.updated"],
"knowledge_base_ids": ["kb_00000001"]
}'/v1/agents/:agentIdPartial update. Send only the fields you want to change. All create fields can be updated with the following rules:
server_urlcannot be set to null or an empty string once set — returns 400.voiceis cross-validated against the current (or newly set)language. If the voice is not available for that language, returns 400.- Setting
webhook_urlto null or empty string clears the webhook secret and unregisters all webhook event subscriptions for that agent. - Setting
knowledge_base_idsto[](empty array) removes all attached knowledge bases. webhook_eventsmust be a non-empty array. Sending an empty array returns 400.- If no valid fields are present in the request body, returns 400 "No valid fields to update".
# Enable recording
curl -X PATCH https://api.staffifyai.com/v1/agents/agent_28c51f81 \
-H "Authorization: Bearer sfy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "recording": true }'
# Switch language and voice
curl -X PATCH https://api.staffifyai.com/v1/agents/agent_28c51f81 \
-H "Authorization: Bearer sfy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "language": "nl-NL", "voice": "Nienke" }'
# Replace knowledge bases
curl -X PATCH https://api.staffifyai.com/v1/agents/agent_28c51f81 \
-H "Authorization: Bearer sfy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "knowledge_base_ids": ["kb_00000001", "kb_00000002"] }'
# Clear all knowledge bases
curl -X PATCH https://api.staffifyai.com/v1/agents/agent_28c51f81 \
-H "Authorization: Bearer sfy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "knowledge_base_ids": [] }'Update response shape
Returns HTTP200. The updated agent object is wrapped under an agent key: { "agent": { ...agent object... } }. If webhook_url is newly added to an agent that had none before, a webhook secret is auto-generated internally but is not returned in the PATCH response. Use the rotate-webhook-secret endpoint immediately after to retrieve it./v1/agents/:agentIdSoft-deletes an agent. Returns 409 if the agent has active calls in progress. On success, any phone numbers assigned to this agent are automatically unassigned (their agent_id is set to null). Attached knowledge bases are also detached and webhook subscriptions are removed.
curl -X DELETE https://api.staffifyai.com/v1/agents/agent_28c51f81 \
-H "Authorization: Bearer sfy_live_YOUR_KEY"
# Response
{ "deleted": true, "agent_id": "agent_28c51f81" }/v1/agents/:agentId/cloneClone an agent with all settings and attached knowledge bases. Generates a fresh webhook secret if the source agent has a webhook_url. Returns 201. Returns 409 with "Cannot clone agent with active calls in progress" if the source agent is in use. The cloned agent's name defaults to "Copy of {original name}" if name is not provided. The clone inherits all settings from the source agent.
curl -X POST https://api.staffifyai.com/v1/agents/agent_28c51f81/clone \
-H "Authorization: Bearer sfy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Sales Agent EU" }'/v1/agents/:agentId/rotate-webhook-secretcurl -X POST https://api.staffifyai.com/v1/agents/agent_28c51f81/rotate-webhook-secret \
-H "Authorization: Bearer sfy_live_YOUR_KEY"
# Response
{
"id": 12345,
"webhook_secret": "new_secret_value_64_hex_chars...",
"webhook_secret_note": "Save this secret — it will not be shown again."
}/v1/agents/:agentId/audit-logReturns a history of changes made to this agent. Actions: created | updated | deleted | cloned | webhook_secret_rotated
| Query param | Default | Description |
|---|---|---|
| limit | 50 | Results per page (max 100) |
| offset | 0 | Pagination offset (max 1,000,000) |
| action | — | Filter by action: created | updated | deleted | cloned | webhook_secret_rotated |
| start_date | — | ISO date string (inclusive) |
| end_date | — | ISO date string (inclusive) |
curl "https://api.staffifyai.com/v1/agents/agent_28c51f81/audit-log?limit=20&action=updated" \
-H "Authorization: Bearer sfy_live_YOUR_KEY"
# Response
{
"audit_logs": [
{
"id": 99,
"agent_id": 12345,
"api_key_id": 42,
"action": "updated",
"changed_fields": ["recording", "voice"],
"old_values": { "recording": false, "voice": "Michael" },
"new_values": { "recording": true, "voice": "Sarah" },
"created_at": "2026-01-15T12:00:00Z"
}
],
"total": 5, "limit": 20, "offset": 0, "has_more": false
}Supported languages
Use /v1/voices to list available voice names for each language.