Calls
Have an agent call a phone number, track the call by its ID, and read why it failed if it did.
Use this resource to place one outbound call from one of your agents. A successful request returns a callId straight away, and you read the call's progress with that ID. A 201 means the call was accepted, not that the phone rang. To place calls in bulk, use campaigns.
| Endpoint | Request |
|---|---|
| Place a call | POST/v1/calls |
| Get a call | GET/v1/calls/{id} |
The call object
GET /v1/calls/{id} returns this object. Fields are in snake_case.
Attributes
idstringrequiredcallId from POST /v1/calls.statusstringrequiredfailure_codestring | nullrequiredfailure_messagestring | nullrequiredfailure_code.agent_idstring | nullrequiredphone_number_idstring | nullrequiredprovider_call_idstring | nullrequirednull until the call is sent out. Quote it when you ask about a call.contact_phonestringrequiredcontact_dataobject | nullrequiredlead_notesstring | nullrequiredleadNotes value sent with the call.duration_secondsinteger | nullrequiredcost_usdnumber | nullrequiredcreated_atstringrequiredcompleted_atstring | nullrequiredStatuses
| Status | Meaning |
|---|---|
queued | Accepted and waiting. It may be waiting for calling hours or a free line. No funds are held yet. |
dialing | Funds are held, and the call is about to go out. |
in_progress | The call has been sent to the voice provider. This doesn't mean anyone has answered. |
completed | The call ended normally. |
failed | The call won't be retried. failure_code says why. |
completed and failed are final.
Failure codes
A failed call has one of these values in failure_code.
| Code | Meaning | What to do |
|---|---|---|
account_locked | The account is locked. | Contact support. |
agent_inactive | The agent became inactive while the call waited. | Activate the agent, then place a new call. |
agent_not_found | The agent was deleted while the call waited. | Use another agent. |
phone_number_not_found | The phone number was removed while the call waited. | Use another number. |
insufficient_balance | The wallet couldn't cover the hold for the call's maximum duration. | Add funds, then place a new call. |
unresolved_placeholders | The first message has placeholders with no value in this request. | Send values for them, or change the message. |
line_capacity_reached | All your lines stayed busy for the 5 minutes this call could wait. | Place a new call when lines are free. |
system_busy | The platform stayed at capacity for those 5 minutes. | Try again shortly. |
outside_calling_hours | The recipient's calling hours didn't open within 35 hours. | Check the number and any timezone you sent. |
call_not_ready | The agent or phone number stopped being ready while the call waited. | Check them, then place a new call. |
too_many_queued | Too many calls were already queued or in progress. | Wait for some to finish. |
queue_unavailable | The call couldn't be queued. | Place a new call with a new key. |
call_not_started | The call couldn't be started. | Place a new call. If this continues, contact support. |
call_failed | The call went out, then ended with no answer, a busy line, a rejection or an error. | Decide whether to call again. |
To try a failed call again, send a new Idempotency-Key.
Place a call
/v1/callswrite keyAccepts an outbound call and returns 201 with a callId. This is a paid operation. Charges come from your wallet.
Headers
Idempotency-KeystringrequiredBody parameters
agentIdstringrequiredphoneNumberIdstringrequiredagentId.phoneNumberstringrequiredfirstNamestringcustomFields.lastNamestringcustomFields.emailstringcustomFields.companystringcustomFields.customFieldsobjectfirstMessagestringcallContextstringdefault outboundoutbound or callback. Selects which of the agent's configured intros to use. It has no effect when you send firstMessage.voiceCloneIdstringleadNotesstringFields that aren't listed here are ignored.
Response data
callIdstringrequiredGET /v1/calls/{id}.statusstringrequiredqueued on a new call.jobIdstringfailure_codestringfailure_messagestringfailure_code.Idempotency
Every request needs an Idempotency-Key header, so a retry after a timeout can't place a second call. Use one key per call you intend to make. A UUID or your own ID for the lead works well.
| You send | Result |
|---|---|
| A new key | The call is checked and, if it passes, accepted with 201. |
| The same key and same body, within 24 hours | The original response, with its original status and an Idempotency-Replay: true header. No second call. |
| The same key and same body, after 24 hours | 200 with the call as it stands, and an Idempotency-Replay: true header. It includes failure_code and failure_message if the call failed. No second call. |
| The same key with a different body | 409 IDEMPOTENCY_KEY_REUSED. Nothing is placed. |
| The same key while the first request is still running | 409 REQUEST_IN_PROGRESS. Wait, then retry with the same key. |
| No key | 400 MISSING_IDEMPOTENCY_KEY. |
| A key in the wrong format | 400 INVALID_IDEMPOTENCY_KEY. |
Bodies are compared by their content, so the order of JSON keys doesn't matter.
If the call was lost before it started, a repeat with the same key and body queues it again and returns 201. It still can't be dialed twice.
A replay returns what happened at the time, so status can be out of date. Read GET /v1/calls/{id} for the current state.
The body is validated before the key is looked at, so fixing an invalid body doesn't need a new key. When the request is refused with a 403, 404, 422 or 429, the key is released. Retry with the same key once you've cleared the cause.
A 503 IDEMPOTENCY_UNAVAILABLE means nothing was placed. Retry with the same key shortly.
A 503 SERVICE_UNAVAILABLE with Idempotency-Replay: true means the call couldn't be queued on the first attempt. That same key will keep returning this answer for 24 hours, so send a new key.
Phone number formats
phoneNumber is converted to E.164 before the call is queued. A number is accepted in these forms:
| Input | Treated as |
|---|---|
+12025550123 or +1 (202) 555-0123 | International number. Spaces and punctuation are removed. |
(202) 555-0123 or 2025550123 | 10 digits, so a US or Canadian number with +1 added. |
12025550123 | 11 digits starting with 1, so + is added. |
442079460123 | More than 10 digits not starting with 1, so + is added. |
The result must be a valid number for its country. Anything else returns 400. Send the full international form for numbers outside the US and Canada.
Custom fields
customFields can have up to 50 keys, each up to 100 characters. It can be up to 16 KB in total, nested up to 32 levels deep.
Keys are converted to snake_case, so orderId becomes order_id. Common variants become one standard key, for example firstname and first both become first_name. Keys with empty or null values are dropped. A full_name value is built from the first and last name when you don't send one.
Keys that look like internal identifiers, such as user_id and file_id, are left out when contact_data is returned.
To narrow the recipient's calling hours, send an IANA time zone name such as America/Chicago. Use any of these keys: timezone, time_zone, timeZone, tz, customer_timezone or customerTimezone. See Calling hours.
First message
firstMessage can use placeholders in the form {{first_name}} or [first_name]. They are filled from the lead details in the request.
Calling hours
Calls only go out from 09:00 to 20:00 at the destination.
- A call requested outside those hours still returns
201. Its status staysqueueduntil the hours open. - A waiting call holds no funds and no line.
- A call that hasn't started 35 hours after you requested it fails with
outside_calling_hours. It never goes out late.
The destination's hours come from the number being called. For US and Canadian numbers, that is the area code. Otherwise it is the country. When a number covers several time zones, the call waits until all of them are open. If those zones share less than 4 hours of the day, the country's main zone applies. If no time zone can be worked out, the call goes out without this check.
A time zone you send in customFields can only narrow these hours. It never widens them.
- A zone in the destination's country must be open as well as the number's own zones.
- A zone in another country is ignored, and so are
UTCandEtc/UTC. - A zone that would leave less than 4 shared hours is ignored.
Capacity
Two limits apply, and they work differently.
Queued and in-progress calls. Each account can have up to 100 calls queued or in progress through this route. Over that, the request returns 429 EXECUTION_CAPACITY_EXCEEDED with Retry-After: 60, and nothing is queued.
Concurrent lines. Each account has a limit on how many calls can be live at once. An accepted call that can't get a line stays queued and tries again every 30 seconds for up to 5 minutes. After that, it fails with line_capacity_reached, or with system_busy if the platform itself is at capacity. For a call that waited overnight, those 5 minutes start when the calling hours open.
Errors returned by this request
| Status | Code | When |
|---|---|---|
400 | VALIDATION_ERROR | The body failed validation. details.issues names each field. |
400 | MISSING_IDEMPOTENCY_KEY, INVALID_IDEMPOTENCY_KEY | The key is missing or in the wrong format. |
401 | UNAUTHORIZED | The API key is missing or invalid. |
403 | FORBIDDEN | The API key doesn't have write permission, or its owner's role can't make changes. |
403 | ACCOUNT_LOCKED | The account is locked. |
404 | NOT_FOUND | agentId or phoneNumberId doesn't exist on your account. |
409 | IDEMPOTENCY_KEY_REUSED, REQUEST_IN_PROGRESS, DUPLICATE_REQUEST | See Idempotency. |
413 | PAYLOAD_TOO_LARGE | The body is over 256 KB. |
422 | AGENT_INACTIVE | The agent isn't active. |
422 | See below | The phone number, agent or destination can't be used for this call. |
429 | RATE_LIMIT_EXCEEDED | The API key is over its rate limit. |
429 | EXECUTION_CAPACITY_EXCEEDED | You have 100 calls queued or in progress. Retry after 60 seconds. |
503 | IDEMPOTENCY_UNAVAILABLE, SERVICE_UNAVAILABLE | See Idempotency. |
These 422 codes all include details.action, which says how to fix the problem.
| Code | Meaning |
|---|---|
AGENT_NOT_READY | The agent isn't set up to place calls. |
PHONE_NUMBER_AGENT_MISMATCH | The phone number is assigned to a different agent. |
PHONE_NUMBER_UNASSIGNED | The phone number isn't assigned to an agent. |
PHONE_NUMBER_INACTIVE | The phone number isn't active. |
PHONE_NUMBER_OUTBOUND_UNSUPPORTED | The phone number can't make outbound voice calls. For example, Australian 1800 numbers are receive-only. |
PHONE_NUMBER_COMPLIANCE_PENDING | The phone number's regulatoryStatus is pending. |
PHONE_NUMBER_COMPLIANCE_BLOCKED | The phone number's regulatoryStatus is blocked. |
PHONE_NUMBER_NOT_SYNCED | The phone number is still being set up, or needs setting up again. |
PHONE_NUMBER_NOT_READY | The phone number's setup isn't finished. |
DESTINATION_NOT_ALLOWED | This phone number isn't allowed to call that destination. |
DESTINATION_NOT_READY | You are calling one of your own numbers, and it isn't ready to receive a call. |
CALL_NOT_READY | The call can't be placed right now, for another reason. |
Check isCallableForCampaigns on the phone number to catch most of these before you send a request.
Checks after the request succeeds
The checks above run again while the call waits, because things can change. Funds are checked only at this stage. A hold is placed on your wallet before dialing, covering the agent's maximum call duration, rounded up to whole minutes, at the per-minute price.
If one of these checks fails, the call's status becomes failed with a failure code. Read it with GET /v1/calls/{id}.
curl -X POST https://backend.synthesys.app/api/v1/calls \
-H "SYNTH_API_KEY: YOUR_API_KEY" \
-H "Idempotency-Key: lead-1042-renewal-call" \
-H "Content-Type: application/json" \
-d '{
"agentId": "6f1d2a34-8b7c-4e21-9a10-3c5d7e9f1b20",
"phoneNumberId": "b2c4d6e8-1a3f-4c5d-8e7f-90a1b2c3d4e5",
"phoneNumber": "+12025550123",
"firstName": "Jane",
"email": "jane@example.com",
"customFields": { "timezone": "America/Chicago", "order_id": "A-1042" },
"firstMessage": "Hi {{first_name}}, I am calling about order {{order_id}}.",
"callContext": "outbound",
"leadNotes": "Asked for a call back after 3pm."
}'{
"success": true,
"data": {
"callId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"jobId": "0c9e7a52-4d1b-4f36-a8e2-7b6c5d4e3f21",
"status": "queued"
}
}{
"success": false,
"error": "This phone number is assigned to a different agent.",
"code": "PHONE_NUMBER_AGENT_MISMATCH",
"requestId": "e3a1c5d7-2b4f-4a6c-9d8e-1f2a3b4c5d6e",
"timestamp": "2026-10-31T14:02:11.482Z",
"details": {
"action": "Use the agent assigned to this number, or reassign the number first."
}
}{
"success": false,
"error": "This Idempotency-Key was already used with a different request. Send a new key to make a new request.",
"code": "IDEMPOTENCY_KEY_REUSED",
"requestId": "1d2c3b4a-5f6e-4d7c-8b9a-0f1e2d3c4b5a",
"timestamp": "2026-10-31T14:03:40.120Z"
}Get a call
/v1/calls/{id}read keyReturns one call object. Use the callId from POST /v1/calls. It works from the moment the call is accepted.
Path parameters
idstringrequiredErrors
| Status | When |
|---|---|
400 | The ID isn't a valid UUID. |
404 | No call with this ID exists on your account. error is Call not found. |
curl https://backend.synthesys.app/api/v1/calls/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d \
-H "SYNTH_API_KEY: YOUR_API_KEY"{
"success": true,
"data": {
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"status": "completed",
"failure_code": null,
"failure_message": null,
"agent_id": "6f1d2a34-8b7c-4e21-9a10-3c5d7e9f1b20",
"phone_number_id": "b2c4d6e8-1a3f-4c5d-8e7f-90a1b2c3d4e5",
"provider_call_id": "2e4f6a8c-1b3d-4f5a-9c7e-0d2b4f6a8c1e",
"contact_phone": "+12025550123",
"contact_data": {
"first_name": "Jane",
"full_name": "Jane",
"email": "jane@example.com",
"timezone": "America/Chicago",
"order_id": "A-1042"
},
"lead_notes": "Asked for a call back after 3pm.",
"duration_seconds": 184,
"cost_usd": 1.84,
"created_at": "2026-10-31T14:02:12.103Z",
"completed_at": "2026-10-31T14:05:20.511Z"
}
}{
"success": true,
"data": {
"id": "7c6d5e4f-3a2b-4c1d-8e0f-9a8b7c6d5e4f",
"status": "failed",
"failure_code": "insufficient_balance",
"failure_message": "The wallet balance is too low to place this call.",
"agent_id": "6f1d2a34-8b7c-4e21-9a10-3c5d7e9f1b20",
"phone_number_id": "b2c4d6e8-1a3f-4c5d-8e7f-90a1b2c3d4e5",
"provider_call_id": null,
"contact_phone": "+12025550123",
"contact_data": { "first_name": "Jane", "full_name": "Jane" },
"lead_notes": null,
"duration_seconds": null,
"cost_usd": null,
"created_at": "2026-10-31T14:02:12.103Z",
"completed_at": "2026-10-31T14:02:13.871Z"
}
}