Calls
A call is a 1:1 audio and video call inside a conversation. Media runs on Agora. mondochat keeps the call record, rings the callee and logs durations that are good enough to bill minutes.
Before you start
Section titled “Before you start”- Turn on
calls.enabledin your settings. It is off by default. - Add
call: trueto thepolicyrow from the caller’s role to the callee’s role. - A conversation exists between the two users. See Conversations.
- Both users have a session token. Every route below is a user route.
- To charge by the minute you also need
calls.per_minute.enabled, an endpoint marked Answers callouts, and thecall_minutecallout. See Commerce and Webhooks overview.
export MC=https://app.mondochat.io/v1export SESSION=<session token>-
Start a call with
POST /v1/calls.videodefaults to false and only tells the callee’s client how to ring.Terminal window curl -X POST $MC/calls -H "Authorization: Bearer $SESSION" \-H 'content-type: application/json' -d "{\"conversation_id\": \"$ID\", \"video\": true}"The answer is 201 with a
CallJoin:{call, agora_app_id, channel, uid, peer_uid, rtc_token, expires_at}. The call isringing. mondochat posts acall_invitecard, publishescall.ringingto the callee and sends thecall.ringingwebhook. -
Join the Agora channel with
agora_app_id,channel,uidandrtc_token. React only to the user whose uid ispeer_uid: moderators watching and moderation bots also sit in the channel. The frame uses modertc, and thecodecandareafrom yourcallssettings. -
Ring the callee. The callee’s client gets
call.ringingover the realtime socket and your server gets thecall.ringingwebhook, so you can reach the callee when the chat is closed. A client that boots or resyncs asksGET /v1/calls/ringingfor the calls still ringing to the user, newest first. A call rings forcalls.ring_timeout_seconds(45 by default). When it runs out the call ismissedand acall_missedcard is posted. -
Answer with
POST /v1/calls/{id}/answer. The answer is aCallJoinfor the callee. The call becomesactive,answered_atis set andcall.updatedgoes to both users. Decline withPOST /v1/calls/{id}/decline. The call becomesdeclinedand acall_missedcard is posted. Declining answers theCall. -
End with
POST /v1/calls/{id}/end. Either user may end it. The call becomesended, durations are computed, onecall_endedcard is posted,call.updatedis published and thecall.endedwebhook is sent withanswered_at,ended_at,end_reasonand the seconds of each participant. A call that was never answered hasanswered_at: null. A participant who never left is closed at the call’s end time and markedestimated. -
Renew the token.
rtc_tokenexpirescalls.max_duration_minutes(240 by default) after the call started, or at the paid-until time when per-minute is on. Request a new one withPOST /v1/calls/{id}/token, which answers{rtc_token, expires_at}. Do it when the Agora SDK reportstoken-privilege-will-expire. -
Charge by the minute. Set
calls.per_minuteto{enabled: true, price_minor, currency, preauthorize_minutes, payer_role}.payer_roleempty means the caller pays. Prices are in minor units.- When both users have joined, mondochat calls your
call_minutecallout with{call_id, payer, minute, price_minor, currency}for the firstpreauthorize_minutesand setspaid_until. - Each
POST /v1/calls/{id}/tokencharges the next minute first. - Answer the callout with
{status: paid | insufficient | failed}. Anything butpaidends the call with the reasoninsufficient_funds. - The callout carries an
Idempotency-Keyof<call_id>:<minute>. Charge each key once.
- When both users have joined, mondochat calls your
-
Send call frames for moderation. With
moderation.calls.agoraon, mondochat starts Agora call moderation for each participant when the call becomes active. If a participant’s task did not start andmoderation.calls.fallback_captureis on, the other participant’s client getscall.capturewith{call_id, subject_id, interval_seconds, max_frames}. A client that implements this:- Draws the subject’s remote video to a canvas every
interval_secondsand encodes a JPEG of at most 480 px on the long edge. - Calls
POST /v1/calls/{id}/frameswith{subject_id, content_type: "image/jpeg"}. The answer is{frame_id, url, fields, expires_at}, a presigned POST valid for 2 minutes and 1 MB. - Uploads the JPEG as in Media, then calls
POST /v1/calls/{id}/frames/{frame_id}/complete, which answers 202. - Stops at
max_frames, on hang-up and when the video stops. Nothing is shown to either user.
The frame route accepts only a participant of an active call, naming the other participant, and only after
call.capturewas issued for that subject. Pastmoderation.calls.capture_max_frames(30 by default) it answers 429. - Draws the subject’s remote video to a canvas every
-
React to call webhooks.
call.ringingis{call_id, conversation_id, caller, callee, recipient_online, open_url, video}.call.endedis{call_id, conversation_id, caller, callee, answered_at, ended_at, end_reason, participants}.
Check it works
Section titled “Check it works”- Start a call from user A to user B. The answer is 201 and the call is
ringing. B’sGET /v1/calls/ringinglists it. - Answer as B. Both users get
call.updatedand the call isactive. - End it. The call is
ended, the thread has onecall_endedcard, and your endpoint got acall.endedwebhook.
When it fails
Section titled “When it fails”| Answer | Cause |
|---|---|
403 policy_denied, reason calls |
calls.enabled is off. |
403 policy_denied, reason call |
The policy row from the caller’s role to the callee’s has call off. Reason inbound when the callee takes messages from members only and the caller is a guest. |
403 forbidden, reason you_blocked |
The caller blocked the callee. |
409 busy |
The callee is in a ringing or active call, or was rung in the last 45 seconds (both users get a call_missed card), or the callee blocked the caller, which the caller is not told. |
409 conflict |
The caller already has an open call, or the call is not in a state the route works on: call is declined, for example. |
403 banned |
The caller is banned. |
502 upstream_failed |
Agora is not configured on the server. |
403 forbidden, reason insufficient_funds |
POST /v1/calls/{id}/token charged the next minute and your call_minute callout did not answer paid. The call is ended. |
403 forbidden |
A frame request for a participant that was never sent call.capture. |
429 rate_limited |
A frame upload is past capture_max_frames. |
404 not_found |
The call does not exist in your app, or the caller is not one of its two users. |
Call ends with insufficient_funds |
Your call_minute callout answered something but paid, or failed. |
Call ends with max_duration |
The call reached calls.max_duration_minutes. |
Limits
Section titled “Limits”- A ringing call times out after
calls.ring_timeout_seconds(45 by default) and an active call ends aftercalls.max_duration_minutes(240 by default). - A
call_invitecard stays joinable for 10 minutes while its call is ringing or active. - When a call ends, mondochat counts
ceil(seconds / 60)call minutes of usage from the seconds both participants overlapped. - Group calls, recording and screen share are not offered.
- A blocked pair cannot call each other. A call card for
call_endedis still delivered across a block. - Call frames come from the participants’ browsers, so a modified client can send clean frames. A case from client frames never holds, blocks or ends the call.
- With
calls.single_cardon, a call shows as one card that turns into Call ended, and an unanswered call postscall_endedinstead ofcall_missed. Only a busy callee postscall_missed.
Related topics
Section titled “Related topics”- Start a call, answer, decline, end, ringing calls, renew a token, request a frame, complete a frame
- call.ringing webhook, call.ended webhook, call_minute callout
- Moderation
- Settings reference for
callsandmoderation - Errors