Answer this call
POST/api/calls/:callId/answer
The call-id keyed form of the conversation endpoint above, for clients
that can have more than one call in flight. Identical work, identical
authorization (reply on the call's own conversation) — the client
names the call instead of leaving the server to infer it from the
thread.
The answer is CLAIMED atomically before anything is sent to the media
sidecar or to Meta, so two surfaces answering the same ring produce
exactly one accept. The loser gets 409 with answered_by and has
touched nothing.
"Answer & hold" (SES-1 F2 dilim 2). Passing hold_call_id makes
this one compound verb: the named call is parked at the media bridge
FIRST, and only then is the provider asked to accept this one. The
order is fixed — reversing it would leave the agent's microphone live
on two customers at once. A park the bridge refuses cancels the whole
request (the ring stays live on every device); a park that SUCCEEDS is
written and broadcast even if the answer that motivated it then fails,
so no surface is ever left drawing a live call for a customer who is on
hold. Requires a bridge with the hold primitive — see the 501.
The per-agent ceiling. An agent already on calls.routing.agent_leg_limit
open calls (default 2, held ones included) is refused with 409
agent_leg_limit. Nothing is claimed and nothing leaves this server, so
the call goes on ringing for every colleague who could still take it.
Request
Responses
- 200
- 401
- 403
- 404
- 409
- 422
- 501
The call is active. When hold_call_id was given and a park was performed, data.held carries the other call's hold state in the same shape POST /api/calls/{callId}/hold answers with; it is null otherwise.
Not authenticated. code: account_disabled means the member's seat was deactivated in this workspace: the session is over even though the token was not revoked (clients sign out and clean up).
Authenticated but not allowed.
No such call for this tenant. Also the answer for a call belonging to another tenant — it is simply invisible.
The call is no longer ringing (data.answered_by says who holds it), carries no SDP offer, its channel is no longer configured for WhatsApp, or — code: agent_leg_limit — this agent already has the maximum number of calls open. That body carries limit and the call_ids filling it, so the panel can turn the refusal straight into the calls the agent has to choose between.
The sidecar or Meta refused (the claim is rolled back and the call rings on), or — code: announcement_unavailable — the channel requires a spoken recording notice and it could not be produced. In that case NOTHING was contacted: the call is still ringing on every device and no recording was started, because a recording that was never announced must not exist (SES-1 F3, fail-closed).
code: hold_failed is the compound's own refusal: the bridge would not park hold_call_id, so the answer was abandoned rather than performed with two live microphones. code: hold_call_is_target means hold_call_id named the call being answered.
code: hold_unsupported — hold_call_id was given but this deployment's media bridge has no hold primitive. The whole request is refused rather than answering without the park, which would be the exact privacy leak the compound exists to prevent. The same fact is published as meta.hold_supported on GET /api/calls.