Skip to main content
API Reference

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.

server_url vs webhook_url

server_url: Real-time call control. Called at call start (call.started) and for each tool the AI invokes. Must be public HTTPS. Respond within 5 seconds for call.started, 2 seconds for tool calls.
webhook_url: Async event notifications. Staffify POSTs here after events happen (call ended, recording ready, transfer completed). Use for logging and post-call automation.
Full server_url integration guide →
GET
/v1/agents

List all agents with cursor pagination

POST
/v1/agents

Create a new agent

GET
/v1/agents/:agentId

Get a single agent by public_id or numeric id

PATCH
/v1/agents/:agentId

Update agent fields (partial update)

DELETE
/v1/agents/:agentId

Delete an agent (fails if active calls)

POST
/v1/agents/:agentId/clone

Clone an agent with all settings and knowledge bases

POST
/v1/agents/:agentId/rotate-webhook-secret

Issue a new webhook secret (old one immediately invalidated)

GET
/v1/agents/:agentId/audit-log

View 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:

HTTPcodeWhen
401MISSING_API_KEYNo Authorization header or X-Api-Key header present
401INVALID_API_KEYKey does not exist, has been revoked, or is malformed
402INSUFFICIENT_CREDITSAccount is paused due to insufficient credits (write operations only)
403READ_ONLY_KEYAPI key has read-only scope and the request is a write (POST/PATCH/DELETE)
429RATE_LIMIT_EXCEEDEDPer-org per-minute rate limit exceeded. Check X-RateLimit-Reset header.
429AUTH_RATE_LIMITEDMore 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 lifecycle
call.ringingcall.startedcall.endedcall.failedcall.recording_readycall.dtmf.receivedcall.gather.completed
Speech & transcription
transcript.updatedspeech.startedspeech.endedai.speech.startedai.speech.endedbarge_in
Tools
tool.invokedtool.completedtool.timeout
Transfers
transfer.initiatedtransfer.completedtransfer.failed
SMS & Knowledge base
sms.sentknowledge_base.refreshedknowledge_base.refresh_failedknowledge_base.auto_refresh_disabled
Address verification
address.verification.pendingaddress.verification.verifiedaddress.verification.partially_verifiedaddress.verification.rejectedaddress.verification.expired

When webhook_events is omitted but webhook_url is set, all events are subscribed by default.

GET/v1/agents

Returns a paginated list of agents. Uses cursor-based pagination.

Query paramDefaultDescription
limit20Results per page (max 100)
afterCursor from previous response's next_cursor field. Accepts a public_id (agent_*) string or a positive integer agent id.
namePartial name filter (case-insensitive LIKE match)
Response shape: { "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"
POST/v1/agents
FieldTypeRequiredDescription
namestringrequiredDisplay name for the agent. Whitespace is trimmed.
server_urlstringrequiredPublic 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.
languagestringoptionalBCP-47 language code (default: en-US)
voicestringoptionalTTS voice name (default: Sarah). Must be valid for the selected language. See /v1/voices for options.
max_durationintegeroptionalMax call seconds (default 1800). Values are clamped to the range 60–7200; out-of-range values are silently adjusted.
recordingbooleanoptionalRecord calls (default false)
end_call_enabledbooleanoptionalLet agent end the call (default false)
webhook_urlstringoptionalHTTPS 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_eventsstring[]optionalEvents to subscribe to. Must be a non-empty array of valid event names (see Webhooks reference). Sending an empty array returns 400.
knowledge_base_idsstring[]optionalKB 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.
If webhook_url is set on create, the response includes a webhook_secret and webhook_secret_note. This is shown once only -- save it immediately.

Create response shape

Returns HTTP 201. 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"]
  }'
PATCH/v1/agents/:agentId

Partial update. Send only the fields you want to change. All create fields can be updated with the following rules:

  • server_url cannot be set to null or an empty string once set — returns 400.
  • voice is cross-validated against the current (or newly set) language. If the voice is not available for that language, returns 400.
  • Setting webhook_url to null or empty string clears the webhook secret and unregisters all webhook event subscriptions for that agent.
  • Setting knowledge_base_ids to [] (empty array) removes all attached knowledge bases.
  • webhook_events must 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 HTTP 200. 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.
DELETE/v1/agents/:agentId

Soft-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" }
POST/v1/agents/:agentId/clone

Clone 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" }'
POST/v1/agents/:agentId/rotate-webhook-secret
The old secret is immediately invalidated. Update your server before rotating. Returns 400 if the agent has no webhook_url configured.
curl -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."
}
GET/v1/agents/:agentId/audit-log

Returns a history of changes made to this agent. Actions: created | updated | deleted | cloned | webhook_secret_rotated

Query paramDefaultDescription
limit50Results per page (max 100)
offset0Pagination offset (max 1,000,000)
actionFilter by action: created | updated | deleted | cloned | webhook_secret_rotated
start_dateISO date string (inclusive)
end_dateISO 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

en-USen-GBes-ESnl-NLde-DEfr-FRit-ITpt-PTpl-PLsv-SEda-DKnb-NOfi-FI

Use /v1/voices to list available voice names for each language.

Agents - Staffify API