Post a call event
https://app.wiseparts.ai/api/v1/voice/callCreates a call, or advances one that already exists.
Send one request per state change on the same call, reusing the same
id for its whole lifetime — WiseParts matches on that value. The match
is scoped to the integration the token belongs to, not to the whole
account, so two phone systems on the same account can use overlapping
identifiers without colliding.
GET is accepted with the same parameters in the query string, for
phone systems that can only fire a URL. It is not a read: both verbs
create and advance calls.
Two fields you may see named in an error, source_id and user_id, are
filled in by WiseParts — the first from the integration's configuration,
the second from the extension-to-user mapping. Sending them yourself has
no effect.
A call only ever moves forward
Each status carries a weight, and an event is written only when its
weight is at least the weight of the status already on the call. Statuses
of equal weight replace one another freely, so no-answer after
canceled is accepted, while ringing after answered is not. Equal
weight includes a status repeating itself: a second completed on a
completed call passes the gate exactly like the first one did.
The part worth designing around: when an event is rejected, nothing
on the call is updated — not merely the status. A late ringing that
also carried end_time, duration or caller_id loses all of them. Put
the values you care about on an event whose status is allowed to land.
A rejected event still answers 200, and so does an event a handler
decides is not relevant and skips outright. A success therefore confirms
receipt, not that anything was written.
Call statuses
| Status | Weight | Meaning | Ends the call? |
|---|---|---|---|
queued | 10 | Accepted into a queue, not yet offered to an agent. The one status that creates or advances a call without raising a realtime notification, so a call that only ever reports queued reaches reports and activity but never lights up the notification bar. | No |
ringing | 10 | Being offered to an agent's extension. | No |
jumped | 10 | The call moved off this extension to another. Shown as "Transferred". | No |
extension-cancel | 10 | This extension stopped ringing while the call continues elsewhere. Shown as "Extension cancellation". | No |
answered | 50 | An agent picked up. Shown as "In progress". Blocks every weight-10 status from here on. | No |
canceled | 100 | The caller hung up before anyone answered. Counts as a missed call. | Against weight-10 and answered only |
no-answer | 100 | Nobody picked up. Counts as a missed call. | Against weight-10 and answered only |
failed | 100 | The call could not be established. Counts as a missed call. | Against weight-10 and answered only |
completed | 150 | The call ran and hung up normally. Nothing of lower weight lands afterwards. | Yes, against every lower weight — but another completed is equal weight and is still accepted, rewriting duration, end_time and caller_id. Send it twice and the second one wins. |
Body
idstringRequiredYour identifier for the call, stable across every event of the same call. Letters, digits, spaces and the characters #, +, (, ), _, . and - only — anything else is rejected. It need only be unique within the integration sending it.
directionstringRequiredoutbound is accepted as an alias for outbound-dial.
inboundoutbound-dialoutbound-autostatusstringRequiredWhere the call has got to. Matched case-insensitively, with underscores treated as hyphens — NO_ANSWER and no-answer are the same value. Each value carries a weight that decides whether it may overwrite the status already on the call; the table above the parameters lists what each one means, which replace which, and which one ends the call for good.
queuedringingansweredcompletedno-answercanceledfailedjumpedextension-cancelfromstringOptionalThe caller's number. Required when direction is inbound. Full international format matches customers most reliably.
tostringOptionalThe number dialled. Required on outbound calls — that is, on anything whose direction is not inbound.
caller_idstringOptionalThe extension or agent identifier handling the call. WiseParts resolves which user took the call from this value, using the extension-to-user mapping on the integration screen. An unmapped value still records the call; it just is not attributed to a user. Letters, digits, spaces and #, +, (, ), _ only — narrower than id, which also allows dots and dashes.
start_timestringOptionalWhen the call was answered, in RFC 3339. The first value a call receives is final. Leave it out on an answered event and WiseParts stamps the current time in the account's timezone, which is the usual way to send it. A value that cannot be parsed is a 422.
end_timestringOptionalWhen the call finished, in RFC 3339. Leave it out on a completed event and WiseParts stamps the current time in the account's timezone. A value that cannot be parsed is a 422.
durationintegerOptionalCall length in seconds. Decimals are accepted but truncated, so send whole seconds unless you have a reason not to.
forwarded_fromstringOptionalThe number the call was diverted from.
parent_call_idstringOptionalThe original call's id, when this call is a transfer. Same character restrictions as id.
curl --request POST \
--url https://app.wiseparts.ai/api/v1/voice/call \
--header 'Authorization: Bearer REPLACE_BEARER_TOKEN' \
--header 'content-type: application/json' \
--data '{"id":"CALL-2026-08-12-0042","direction":"inbound","status":"answered","from":"+351210000000","caller_id":"201","start_time":"2026-08-12T14:33:07+01:00"}'
Responses
The event was accepted. An event rejected by the forward-only rule, and one a handler chose to skip, answer exactly the same way — treat this as an acknowledgement of receipt rather than proof that a call was written.
The token is missing, wrong, or belongs to an integration that is not active. Requests that fail here leave no entry in the request history, because the account could not be identified. POST /voice/call and /voice/priority always answer with this JSON body. GET /voice only does so when the request asks for JSON — send Accept: application/json or you will get a 302 redirect to a browser page instead of a 401, which is a confusing thing to debug from a script.
One or more fields of the call event were missing or invalid. errors is keyed by field name with one or more messages each, and the keys are the field names in this reference rather than whatever your phone system called them — WiseParts normalises the payload before validating it.
Two causes, and only one of them worth retrying. Events for the same call id are serialised behind a five-second lock, and a burst that cannot take it in time fails here — that one is transient, so retry it. The other is a token that matches active Voice integrations on two different accounts: WiseParts refuses to guess which account the event belongs to and fails the request. That is stored configuration, not a moment of contention, so retrying never succeeds — the token has to be changed on one of the two accounts. Two integrations on the same account sharing a token never reach this. WiseParts picks one of them without comment, so the event lands on whichever it picked; the X-Centrix-Request-Type header on the 200 is how you find out which.
Response fields
resultstringForty-eight random alphanumeric characters, generated fresh for every request and stored nowhere. It identifies nothing and correlates with nothing, so do not keep it as a reference to the call — your own id is that reference.
{
"result": "4HWYBVSKu4spptgZG9BKMszhlIWqlgp1bAJGtvUKpDHTSzjs"
}