Submitting a call
POST /calls, field by field.
POST /calls accepts a call for processing and returns immediately. Transcription and analysis
happen asynchronously; the result reaches you as a call.completed webhook.
Audio reaches Callens through an upload: request one, send the bytes, and present the ticket here.
{
"mode": "upload",
"upload_ticket": "eyJpdiI6…",
"started_at": "2026-08-05T09:14:00Z",
"employee_code": "EMP-1042",
"provider": "zoom",
"provider_call_id": "CA123",
"idempotency_key": "CA123"
}Required fields
| Field | Notes |
|---|---|
mode | Always upload. |
upload_ticket | The ticket from POST /uploads, verbatim. |
started_at | When the call started. |
employee_code or employee_id | The employee the call belongs to. Send exactly one of the two. See naming the employee. |
provider_call_id (max 255 characters) and idempotency_key (max 255 characters) are also
required if your key has webhooks enabled — see
correlation identifiers.
Naming the employee
Every call belongs to one employee, and the request says which one. Send exactly one of these two fields. Leave the other one out.
| Field | Notes |
|---|---|
employee_code | Your organization's own code for the employee: the Code on the People page in Callens. Matched exactly, including letter case. Max 255 characters. |
employee_id | The employee's Callens ID, an opaque string. Copy it from the People page in Callens. Max 64 characters. |
Use employee_code when your phone system already holds your own identifier for each person. Use
employee_id for an employee who has no code.
Callens refuses the request in three cases, and creates no call in any of them:
| Case | Response |
|---|---|
| The request sends neither field | 422 in the field-validation shape, with errors naming the fields. |
| The request sends both fields | 422 in the field-validation shape. Callens does not choose between them. |
| The code or ID names no employee, or names a deleted employee | 422 with error set to employee_not_found. |
In all three cases the recording you uploaded is kept. Correct the request and submit the same
upload_ticket again; you do not upload the file a second time.
Spaces before and after a code are ignored. Letter case is not: emp-1042 does not match
EMP-1042.
An employee who has left your organization, and has not been deleted, is still accepted. A historical import can name the person who took the call.
Callens checks for a replay before it looks up the employee, so a
retry of a call that was already accepted returns that call with 200.
identity_key is no longer read. A request that sends identity_key and neither employee field
is refused, like any other request that names no employee.
Optional fields
| Field | Notes |
|---|---|
ended_at | Must not be before started_at. |
duration_ms | Minimum 1. Callens measures duration itself and prefers its own observation. |
provider | The system the call came from. Max 64 characters. |
channel_layout | mono_mixed, dual_agent_customer, or multi. dual_agent_customer asks us to attribute speech by channel, which only some speech-to-text providers can do — if yours cannot, the call is refused with a 422 naming the provider rather than accepted and left unprocessed. Submit without this field, or as mono_mixed, to be transcribed with speaker diarization instead. |
direction | inbound, outbound, or unknown. |
counterparty_e164 | The other party's number. Max 32 characters. |
priority | live or backfill. Use backfill for historical imports so they yield to live traffic. |
provider_metadata | Any JSON object. Stored on the call as submitted. Not returned to you anywhere today — not in this response, not on call.completed. |
Responses
{ "success": true, "message": "Call accepted.", "data": { "id": "gQ7bYx", "status": "queued" } }201 on a new call, 200 on a replay. See idempotency.
The id is an opaque string. Callens never exposes internal numeric identifiers.
status is queued for every new call: the call is on its way through the pipeline. A replay
returns the stored call's current status.
A replay of a call that was submitted without an employee, before employees became required, can
still return identity_unresolved until someone in your organization assigns that call an
employee in Callens.
Refusals specific to the upload are listed under uploading audio.
Modes that no longer exist
Earlier versions of this API documented mode: "pull", which took a source_url, and
mode: "push", which took an audio_key. Both now return 422.
Neither could produce a transcribable call. Nothing ever fetched a source_url — the URL was
validated and discarded, so the call was created with no audio and failed as soon as the pipeline
reached it, having already consumed part of your ingestion allowance. push required write access
to Callens storage, which is not something an API key has ever granted.
If you built against either, switch to uploading audio. Nothing that worked before has stopped working.