Part 6 — Building the screens
← Contents · Previous: Part 5 — Call flow reference · Next: Appendix
How a client app turns the engine's API into screens for the businesses it serves: the flow editor, the calls page and
call detail, the reports, the live screens, and the screens around Agent Phone (§6.7). Each section names the endpoints to use, what to do, and what not to
do. The demo console (console/) is a working example of every screen here.
6.0 The shape of a client app
browser (business staff) ──► client app backend ──► Ringmaster engine (/v1, API key)
▲
└── webhooks (signed) from the engine
Do
- Call the engine only from the client app's backend. The browser talks to the client app, never to the engine.
- Map your business to an engine account once (
externalRef= your business id) and keep theaccountId. - Store what webhooks tell you: calls (
call.ended), voicemails, summaries, call-out results. Your own reports and search then run on your own database, and the engine stays the source for detail (timeline, recording). - On start-up, call
GET /v1/ping: it returns the client app'sfeatures. Show only the screens the app may use (no Call-outs screen withoutCALLOUTS, no AI blocks withoutAI). - Switch on the error
code, show themessage(README). Messages are written for people.
Don't
- Don't put the API key, the webhook secret or a lobby token for someone else in a page, a mobile app or a log.
- Don't poll the engine to find out that something happened. Webhooks tell you (§6.5).
- Don't fail on fields, enum values, timeline events or webhook types you don't know. The engine adds them within
/v1. - Don't show engine words to business staff when yours are clearer: "script" is "call flow" in this book;
MISSEDcan be "Nobody answered".
6.1 The flow editor
Load
| What | Endpoint | Cache |
|---|---|---|
| Block library (types, groups, exits, approval) | GET /v1/scripts/schema |
Per schemaVersion; reload when it changes |
| The account's flows and their versions | GET /v1/accounts/{id}/scripts (D1) |
— |
| A version's blocks | GET …/scripts/{ref}/versions/{v} (D6) |
Versions never change: cache forever |
| Pickers: departments, audio, knowledge docs, app actions | I2, E2, F2, GET /v1/actions |
Short |
| Other flows for Schedule call (OUTGOING) and Transfer (SHARED) | D1, then D6 of the chosen version | — |
Draw the blocks
Build the library from the schema's blocks, grouped by group, with ⚠ on needsApproval. Each block's ports
(its exits) come from its settings:
| Block | Ports |
|---|---|
menu |
each options[].id (default: the key) · noInput · notUnderstood (only when speech is on) · later |
collect |
ok · failed · later |
poll |
next · noInput · later |
pick |
chosen · empty · noInput · later |
ask |
answered · silence |
connect |
noAnswer · each wr.options[].exit |
schedule |
scheduled · notUnderstood (only with ask) · declined (only with confirm) · refused |
request |
mode: continue → next; else success · failure · timeout · each wr.options[].exit |
app_action |
from the registered action (GET /v1/actions): no wait → next; wait → each outcomes[] · failed · timeout · each wr.options[].exit |
condition |
test.kind: hours → open · closed; others → yes · no |
goto |
tooMany (when maxLoops > 0); draw target as a dashed line, not a port |
transfer |
the exit of each Return block in the chosen SHARED version |
ai_agent |
goal · human · vm · cb · nohelp · time · err |
say · optout |
next |
voicemail · goodbye · return |
none: an end block |
Show the later port as optional ("if the caller says later"). Show ports with nothing connected in a different
colour: the call ends there.
Keep the layout yourself
The engine stores the flow, not the drawing. A top-level layout key is dropped on push. Store block positions in the
client app, by account, flow ref and block id. Since ids stay the same across versions, the drawing carries over.
Edit
- Versions never change. "Edit" means: load the latest version into a draft kept by the client app, change it, and push it as a new version. Keep the draft in your database so a closed tab loses nothing.
- Forms per block follow Part 5: every setting there, with its default as the placeholder. Leave a field out instead of sending its default.
- Block ids: generate them (
b1,b2… or a slug of the name) and never change an existing block's id. Stats are counted by id. - Settings that point elsewhere use pickers, never free text: departments, audio, other flows, app actions.
- Variables: offer a picker with what the flow can use at that block (Part 5 §5.3).
- Send request secrets show as
(hidden). Leave them as they are to keep them; let the user type a new one to replace. - Without the
AIfeature, hide the AI Agent block and thesmartmatching choice.
Check, save, publish
every change (debounced ~1 s) → POST /v1/scripts/validate {definition} → show issues on the blocks
Save → POST …/scripts/{ref}/versions {version: latest+1, runnable, definition}
Approve (risky versions) → POST …/versions/{v}/runnable
Go live → PUT /v1/numbers/{numberId}/script {scriptVersionId} (incoming)
or pick the version in a call-out (outgoing)
- Show
errorsin red on their block,warningsin amber;block: nullissues at the top (Part 5 §5.9). Disable Save while there are errors. version= the highest existing version + 1. A409 VERSION_EXISTSmeans someone else saved first: reload, show their version, and let the user re-apply their change. A retry of the same push is safe (same document →200, not a new version).- Approval is the client app's rule (2B, D7). A common
rule: versions with
riskyBlocksare pushed withrunnable: falseand a manager approves them; the rest go outrunnable: true. - Saving never changes what callers hear. Only "Go live" does. Say so on the button.
- Roll back = put an older runnable version back on the number. No push needed.
- Stop a version that must not run any more:
POST …/revoke(D8). Numbers playing it refuse calls, so move them first.
Delete
DELETE …/scripts/{ref} (D4). A 409 SCRIPT_IN_USE lists what uses the flow (numbers, callouts, scripts):
show the list with links. A flow that has run calls is archived, not erased: its call history stays readable.
Per-block stats on the canvas
GET …/versions/{v}/stats (D9), for the version shown. Draw on each block:
visits(how many calls came through);- drop-off =
hangups / visits(callers who hung up here: a long or confusing prompt); - confusion =
retries / visitsandnoInput(callers who didn't know what to press); - Send request blocks:
reqFail,reqTimeout,reqMsP50; - Waiting rooms:
gaveUp.
Stats appear a few minutes after calls end, and only for that version: compare versions side by side, not mixed.
Try it
Push the version and put it on a test number, or call it with a quick call to your own phone (POST /v1/calls,
needs CALLOUTS). Then open the call's timeline (§6.2): it shows every block,
sentence, key and match.
Don't
- Don't edit the JSON of a stored version and push it under the same number.
- Don't let users type department ids, audio ids or flow refs.
- Don't push on every keystroke: check on every change, push on Save.
- Don't put a version on a number before the push returned
201(or200for the same document).
6.2 Calls page and call detail
The calls list
GET /v1/calls?accountId=…&page=…&size=50 (G1), newest first. Filters: from / to, type, result, status,
number, calloutId.
| Column | From | Show as |
|---|---|---|
| When | startedAt |
Local time of the business; "Today 14:05" |
| Who | from (incoming) / to (outgoing) |
Your contact's name if you have one, else the number |
| Line | to (incoming) / from (outgoing) |
The number's name in your app |
| Type | type |
Incoming · Call-out · OTP · Agent call |
| Result | result |
A coloured label (table below) |
| Handled by | handledBy (+ agent from the timeline) |
Flow · Agent Asha · AI · Voicemail |
| Length | durations.totalSeconds |
2:31 |
| 🔊 · 📝 | recorded · transcript |
Icons; play inline |
- Live calls:
status=LIVE, orstatusnotENDED. Refresh this tab oncall.started/call.ended, not with a timer. - For search over months (by contact, by tag), use your own copy of
call.endedevents.
Result labels
result |
Label | Colour |
|---|---|---|
HANDLED_BY_SCRIPT |
Served by the flow | green |
HANDLED_BY_AGENT |
Talked to an agent | green |
HANDLED_BY_AI |
Served by AI | green |
VOICEMAIL |
Left a message | blue |
MISSED |
Nobody answered | red |
ABANDONED |
Hung up while waiting | red |
HUNG_UP_EARLY |
Hung up at once | grey |
REJECTED |
Refused (limits, suspended) | grey |
ANSWERED (outgoing) |
Answered | green |
NO_ANSWER · BUSY · DECLINED |
Not answered · Busy · Declined | amber |
UNREACHABLE · INVALID_NUMBER · FAILED · NOT_DIALED |
Couldn't reach · Wrong number · Failed · Not called | grey |
Call detail
One page per call, from four calls, loaded together:
| Part | Endpoint | Notes |
|---|---|---|
| Header: who, when, result, lengths, flow version, outcome tags | GET /v1/calls/{id} (G2) |
durations as a bar: waiting · talking · on hold · AI |
| Call log | GET /v1/calls/{id}/timeline (G3) |
Below |
| Recording | GET /v1/calls/{id}/recording (G4) |
Proxy it through your backend; 404 = none or not ready |
| Transcript and summary | GET /v1/calls/{id}/transcript (G5) |
404 until summary.ready; show "Being transcribed…" if recorded |
providerCost is what the call cost to run (carrier, AI). Show it to the client app's own staff, not to the business,
unless that is how you bill.
The call log (timeline → readable lines)
The timeline is the call's log: every step with its time. Show it as lines, offsetMs as 0:12. Group lines under
the block they happened in (block). Show the readable ones by default; put the rest behind "Show technical details".
| Event | Line | Default |
|---|---|---|
CALL_RECEIVED / DIALING / ANSWERED |
"Call from +255745… to Mapokezi line" / "Calling…" / "Answered" | shown |
BLOCK_ENTER |
Block heading: the block's name and type | as heading |
SAY |
🔈 "Karibu PMK Chemicals." (data.text) |
shown |
PLAY |
🔈 the audio's name | shown |
KEY |
⌨ "Pressed 2" (red if valid: false) |
shown |
SPEECH / AI_HEARD |
🗣 "nataka kununua" | shown |
MATCH |
→ "Chose Mauzo (by voice, 92%)" | shown |
INPUT |
⌨ "Entered 1234 (valid)" or •••• |
shown |
NO_INPUT / NOT_UNDERSTOOD / RETRY |
"No answer — asked again (2)" | shown |
POLL / PICK |
"Answered 4 (Nzuri)" / "Chose Arusha" | shown |
CONDITION |
"Opening hours: open" | shown |
QUEUED / RING / ACCEPTED / RING_TIMEOUT |
"Waiting for Mapokezi" / "Ringing Asha" / "Asha answered" / "Asha didn't answer" | shown |
WAIT_OPTION |
"Pressed 9 while waiting → Voicemail" | shown |
VOICEMAIL |
"Left a message" with the player | shown |
CALL_SCHEDULED |
"Callback booked for tomorrow 09:00" | shown |
ACTION_REQUESTED / ACTION_RESULT |
"Asked the app: pay" / "pay → paid" | shown |
REQUEST |
"GET shop.example → 200 in 340 ms" | shown |
AI_SAID / AI_STAGE / AI_TOOL |
🤖 the sentence / stage / tool | shown |
EXIT_UNCONNECTED |
⚠ "The flow ends here (exit noInput not connected)" | shown: it's a flow bug the business can fix |
ERROR |
⚠ data.code explained |
shown |
ENDED |
"Ended: caller hung up" (endReason, endedBy) |
shown |
WORKER_* · AI_USAGE · WAIT_UPDATE · TRANSFER_IN/OUT · VARIABLE_MISSING · others |
as they are | hidden |
Link each block heading to the flow editor at that block, on the version the call ran (script.version from G2).
Don't
- Don't send the recording's URL with the API key to the browser. Stream it through your backend, after checking that the user may hear it.
- Don't show a sensitive value; the engine already masks it as
••••, and so should anything you add. - Don't poll for the transcript: wait for
summary.ready.
6.3 Reports
Account dashboard
GET /v1/accounts/{id}/stats?from=…&to=…&groupBy=day (N1).
| Tile / chart | Formula |
|---|---|
| Calls | incoming + outgoing |
| Didn't get a person | missed + abandoned: callers who waited for an agent and gave up or weren't answered; as a share of incoming |
| Left a message | voicemail |
| Served without a person | (byScript + byAi) / (byScript + byAgent + byAi + voicemail) |
| Hung up at once | hungUpEarly / incoming (a long or unclear greeting) |
| Average call length | seconds / answered (answered counts incoming calls and answered outgoing ones) |
| Busiest hours | groupBy=hour: a heat map by hour |
| Per line | groupBy=number |
| By type | groupBy=type |
Range: at most 400 days. Default view: the last 7 days.
Flow performance
- Funnel per block: D9 on the canvas (§6.1).
- Outcome tags (
countAs, App action outcomes): per call inoutcomes(G2,call.ended), per call-out inbyOutcome(H8). For account-wide numbers by tag, count them in your own copy ofcall.ended, or from the CSV export (outcomescolumn).
Call-outs
| Screen part | Endpoint |
|---|---|
| List and status | GET /v1/callouts?accountId=… (H5) |
| Header: status, counts, pause reason | GET /v1/callouts/{id} (H7) |
| Progress bar, by result, by try, by hour | GET /v1/callouts/{id}/stats (H8) |
| People and their result | GET /v1/callouts/{id}/recipients?status=… (H6) |
| Download | GET /v1/callouts/{id}/export.csv (H9) |
While it runs, update the page from callout.progress (every 30 s) instead of polling. Show callout.paused reasons
as actions: VERSION_REVOKED (choose another version), FEATURE_OFF (ask the owner), ACCOUNT_SUSPENDED /
CLIENT_SUSPENDED (resumes when lifted), CAN_CALL:<reason> (your own can-call check said no). Quiet hours don't
pause a call-out: it waits and goes on by itself.
Agents
GET /v1/accounts/{id}/agents/stats (I13): per agent offered, answered, missed, declined, talkSeconds, and
the declines by reason (declinedWithCustomer, declinedBreak, declinedWrongDepartment, declinedOther).
Answer rate = answered / offered. Show the reasons next to declined, so a manager sees why (four "with a
customer" in a busy shop is not the same as four "other"). Live status comes from agent.status; live declines from
call.ring_declined.
Exports
GET /v1/calls/export.csv?accountId=…&from=…&to=… (G6) and H9. Stream them through your backend as downloads; up to
100,000 rows, oldest first.
Don't
- Don't compute reports by walking
GET /v1/callspage by page. Use the stats endpoints, the exports, or your own copy ofcall.ended. - Don't mix time zones: stats days and hours are in the account's time zone, timestamps are UTC.
6.4 Live screens
The agent desk
Everything is in Part 2D and the desk signals in Part 3. In short:
- Agent signs in to the client app → backend
PUT …/agents/{ref}(once) andPOST …/agents/{ref}/lobby-token. - The page joins the LiveKit lobby room with that token and listens for
ring,ring_cancel,connecting. - Start shift button: unlocks the browser's sound (a ring needs a user gesture) and sets status
AVAILABLE. - On
ring: show the caller and the flow's notes, ring, flash the title. Accept → backendPOST /v1/calls/{id}/accept→ join the call room with the returned token. Decline →POST …/decline. - In the call: Hold, Transfer (to a department), Hang up, each through the backend.
- Renew the lobby token before it expires (600 s).
- Next to Agent Phone: stay quiet when a ring says
"audible": "phone", and offer Take on waiting callers (§6.7.11).
Queue wall
GET …/departments/{id}/queue (I6) for the waiting callers. Refresh it when agent.status, call.started or
call.ended arrive for that account, with a slow timer as backup (≥ 10 s).
Don't
- Don't let the browser call
acceptorhangupon the engine directly. The backend checks the agent is who they say. - Don't ring with an
<audio>element created before the user clicked anything: browsers block it.
6.5 Which webhook updates which screen
| Webhook | Update |
|---|---|
call.started / call.answered |
Live calls list; queue wall |
call.block |
The caller's answers as they happen (a menu choice, a number, a poll answer); act on them in your app (open a ticket) |
call.ended |
Calls list, call detail, your own call store (reports, search) |
call.missed |
"Missed calls" inbox; notify the business |
call.scheduled |
Scheduled callbacks list |
voicemail.received · recording.ready |
Voicemail inbox; the call's player |
summary.ready |
Transcript and summary on the call; voicemail preview |
ai.unanswered |
"Questions the AI couldn't answer": suggestions for the knowledge base |
action.requested |
Not a screen: do the action and answer (2E) |
request.failed |
An alert for the business's integration |
callout.progress · callout.paused · callout.completed · callout.recipient_results |
Call-out page; your own record of who was reached |
agent.status |
Agent list, desk, queue wall, the agent's status switch |
call.ring_declined |
Manager's live board (declines with reasons) |
agent.device_paired · agent.device_revoked |
Connect dialog, device lists, security notices (§6.7) |
agent.shift_started · agent.shift_ended · station.paired · station.removed |
Shared computers page; "Start my shift" on the agent's home |
media.ready · media.failed |
Audio library (processing → ready) |
Webhooks can arrive twice or out of order: store by id, and compare createdAt before overwriting newer data.
6.6 Don't, in one place
- Never put the API key or webhook secret in a browser, app or log.
- Never edit a stored version: push a new one.
- Never change an existing block's id: stats and links are by id.
- Never let users type ids (departments, audio, flows, actions): use pickers.
- Don't poll for what a webhook tells you (calls, recordings, summaries, call-out progress, agent status).
- Don't fail on unknown fields, values, events or webhook types.
- Don't offer what the client app's features don't allow (
GET /v1/ping→features). - Don't put a version live that is waiting for approval; don't approve in the same click as saving a risky version.
- Don't show
••••values unmasked, or engine costs to businesses unless you bill by them. - Don't build reports by paging through
GET /v1/calls. - Don't send or store Agent Phone pairing codes, and don't ignore a
TOKEN_REUSEDrevoke (§6.7.12).
6.7 Screens for Agent Phone
Agent Phone is the desktop app agents keep running to pick up calls (Part 2F, AGENT-PHONE.md). You don't build the app. You build the screens around it on your dashboard: signing an agent's computer in, the business's shared computers, shifts, the business's switches, and the notices. Everything here goes through your backend with your API key, like every other screen in this part.
your dashboard (browser) ──► your backend ──► engine /v1/accounts/… (API key: codes, devices, stations, shifts, settings)
▲
└── webhooks: agent.device_*, agent.shift_*, station.*, agent.status, call.ring_declined
Agent Phone (desktop) ───────────────────────► engine /v1/agent/… (its own device token; you never see it)
The dashboard and the app never talk to each other. They meet in the engine: you start something (a pairing, a shift, a setting), the engine tells the app within a second, and the engine tells you (webhook) what the app did.
6.7.1 Who sees which screen
| Screen | Agent (signed in) | Manager / owner | Section |
|---|---|---|---|
| Download Agent Phone | ✓ | ✓ | 6.7.2 |
| Connect Agent Phone (my computer) | ✓ for themselves | ✓ for any agent | 6.7.3 |
| My devices / an agent's devices | ✓ their own | ✓ all | 6.7.4 |
| Shared computers (add, settings, remove) | – | ✓ | 6.7.5 |
| Start / end my shift on a shared computer | ✓ for themselves | ✓ for any agent | 6.7.6 |
| Agent Phone settings (the business's switches) | – | ✓ owner | 6.7.7 |
| My status (Available / Away) | ✓ | ✓ for any agent | 6.7.8 |
Your own login and roles decide this; the engine only knows your API key. Always check on your backend that the signed-in person may act for the agent ref they send.
6.7.2 Download Agent Phone
- Until releases are published from the engine (planned: AGENT-PHONE-RELEASES.md), link to the installers the Ringmaster owner gives you.
- Planned:
GET {engine}/downloads/agent-phone/latest/{windows|macos|linux}redirects to the newest installer, so the button never needs changing. - Detect the visitor's system from the browser and show that button first; keep the other two as links.
6.7.3 Connect Agent Phone
Where: the agent's profile ("My calls" / "My computer"), and on a manager's agent page.
Flow
[Connect Agent Phone] → your backend: POST /v1/accounts/{accountId}/agents/{ref}/devices/pairings (2F K1)
← { code: "482913", link: "ringmaster-agent://pair?code=482913&host=…", expiresAt }
┌──────────────────────────────────────────────┐
│ Connect Agent Phone │
│ │
│ [ Open Agent Phone ] ← the link │
│ │
│ or type this code in Agent Phone: │
│ 4 8 2 9 1 3 │
│ Expires in 4:52 · [New code] │
│ │
│ Don't have it yet? Download Agent Phone │
└──────────────────────────────────────────────┘
- The Open Agent Phone button is the
link. On a computer with Agent Phone installed it opens the app and pairs it at once; elsewhere nothing happens, which is why the code is shown too. - Show the code large, in two groups of three, with a countdown to
expiresAt(5 minutes). When it runs out, replace the code with "This code expired" and a New code button (a newPOST). - Wait for
agent.device_pairedfor that agent. Then close the dialog with "Connected: Agent Phone on reception-pc" and refresh the device list (6.7.4). Push it to the open page over your own socket; don't poll the engine.
Do
- Ask the person to sign in again (or confirm 2FA) if their session is old before showing a code. The code is what connects a computer to their calls.
- Show the code only to the person it's for, on screen. One code is for one agent.
Don't
- Don't send the code by SMS, e-mail or chat, and don't log it. It works once, for 5 minutes, but anyone holding it in that time can connect a computer to that agent.
- Don't create codes in bulk "for later": make one when the person clicks.
Errors: 404 agent not found. When pairing is refused at the app (shown in the app, not to you):
TOO_MANY_DEVICES (the agent already has 5: remove one in 6.7.4), expired or used code.
6.7.4 Devices
Where: under Connect Agent Phone on the agent's profile, and on the manager's agent page.
GET /v1/accounts/{accountId}/agents/{ref}/devices (2F K2):
| Column | From | Show as |
|---|---|---|
| Name | name |
"Agent Phone on reception-pc" |
| System | platform + appVersion |
"Windows · 0.2.0" |
| Last seen | lastSeenAt |
"2 minutes ago" (relative, the viewer's time zone) |
| Where | lastIp |
for managers only |
| Remove |
Remove → confirm ("Asha's laptop will be signed out at once and stop ringing") → DELETE …/devices/{deviceId}
(2F K3). The app goes back to its pairing screen within a second. Use it for a lost laptop, a person who left, or a
device the agent doesn't recognise.
Keep the list current from agent.device_paired / agent.device_revoked.
Show revoked reasons in words:
reason |
Say |
|---|---|
REMOVED |
"Removed by Juma" (who clicked: your own audit) |
SIGNED_OUT |
"Signed out on the computer" |
UNUSED |
"Signed out: not used for 30 days" |
AGENT_REMOVED |
"Signed out: the agent was removed" |
TOKEN_REUSED |
"Signed out for safety: this computer's sign-in was copied." Alert the owner/manager; ask the agent to check the computer before connecting again |
6.7.5 Shared computers
For a computer several agents use in turns (the reception PC). Where: a manager's "Shared computers" page.
| Action | Endpoint (2F K4) |
|---|---|
| List, with who is on shift | GET /v1/accounts/{accountId}/stations |
| Add shared computer | POST …/stations/pairings → show the code and link exactly as in 6.7.3, with the note "Open Agent Phone on the shared computer and type this code" |
| Shift ends after N minutes without calls | PATCH …/stations/{stationId} {"idleEndMin": 120} (0 = never) |
| Shift ends at a time of day | PATCH …/stations/{stationId} {"endAtTime": "17:30"} ("" = never) |
| End the current shift | DELETE …/stations/{stationId}/shift |
| Remove the computer | DELETE …/stations/{stationId} (signed out at once) |
Shared computers [Add shared computer]
┌──────────────┬──────────────────────────┬───────────────┬──────────┬────────────┐
│ Computer │ On shift │ Ends if idle │ Ends at │ │
├──────────────┼──────────────────────────┼───────────────┼──────────┼────────────┤
│ Reception PC │ Asha · since 08:02 │ 120 min │ 17:30 │ End · ⋯ │
│ Store PC │ nobody │ 60 min │ – │ ⋯ │
└──────────────┴──────────────────────────┴───────────────┴──────────┴────────────┘
Keep it live from station.paired, station.removed, agent.shift_started, agent.shift_ended
(Part 3).
6.7.6 Start my shift
Where: the agent's dashboard home, near the top, while they are not on shift anywhere:
Start my shift on: [ Reception PC (nobody) ▾ ] [Start]
POST /v1/accounts/{accountId}/stations/{stationId}/shift{"agentRef": "<the signed-in agent>"}.- The computer switches to that agent's desk by itself within a second, and the agent becomes Available.
- If someone else is on shift there, ask first: "Baraka is on shift on Reception PC. Start yours instead?" Starting ends theirs.
409 SHIFT_BUSY: "Baraka is on a call. Try again when it ends." A call is never cut.- While on shift, replace the box with "On shift on Reception PC since 08:02 · End my shift"
(
DELETE …/stations/{stationId}/shift). The agent can also end it on the computer. - A shift also ends by itself (idle time, end of day, another agent, the computer removed):
agent.shift_endedwith thereasontells you which. Show it to the agent next time they look ("Your shift on Reception PC ended at 17:30").
Don't let one agent start a shift for another unless the signed-in person is a manager.
6.7.7 Agent Phone settings
Where: the business's settings, a section "Agent Phone" (owner or manager). One PATCH /v1/accounts/{accountId}
with all five switches together (they are replaced as a whole) and, if you show it, the recording notice
(Part 2A):
{ "agentPhone": { "showTeam": true, "showCallerNumbers": true, "allowDecline": true, "allowCallBack": true, "allowDialPad": true },
"recording": { "mode": "LIVE_PARTS", "noticeText": "Mazungumzo haya yanarekodiwa kwa ajili ya ubora wa huduma.",
"noticeMediaId": null, "transcribe": true, "agentCallNotice": false } }
| Switch | Label to show | What it means for agents |
|---|---|---|
showTeam |
Show the team | Agents see who else in their departments is available, on a call or away |
showCallerNumbers |
Show caller numbers | Off: numbers appear as +2557•••••250 everywhere in the app; names from your contacts still show |
allowDecline |
Agents may decline calls | Off: no Decline button; calls are assigned (big call centres) |
allowCallBack |
Agents may call people back | Call-back buttons on recent calls |
allowDialPad |
Agents may dial any number | The dial pad and "New call" in the app |
recording.agentCallNotice |
Say the recording notice on calls agents make | Off by default; turn on where the law asks people to be told |
- The engine enforces every switch (a refused action answers
409), not only the app. Your own screens must hide the same buttons (a browser desk's Decline, a call-back link). - Agents' apps update within a second of saving (the engine sends
config). Save on change and show "Saved". recordingis replaced as a whole too: send the current policy with onlyagentCallNoticechanged.
6.7.8 Status
Agents go Away and Available on your dashboard (PUT …/agents/{ref}/status, 2D I10).
Agent Phone shows the status and only offers I'm back when the agent is away; it never sets Away.
- Put the status switch where agents look all day (the top bar), and the team's statuses on the manager's wall.
- Keep both live from
agent.status; an agent's status also changes by itself (on a call, wrap-up, missed rings, "Going on a break" when declining, starting a shift).
6.7.9 Caller names (your contacts URL)
If your app knows its customers, answer the engine's contacts URL (Part 3 §3.4) and agents see "John Mushi · VIP customer" instead of a number, on the ring, the call, the queue and in search; flows can greet callers by name. The Ringmaster owner sets the URL once.
Do
- Check the signature, and look up only within the
accountRefyou're asked about (two businesses can share a customer's number). - Answer within 1 second for
?number=(an index on the normalised number); slow answers are dropped and the call goes on without a name.404for unknown. - For
?q=: match the start of names and any part of the digits; return at most 10, best first. - Keep
labelshort (≤ 20 characters shows well) andnoteto one line the agent can read during a ring.
Don't put things in note the agent shouldn't see (balances, medical details) unless they need them on the call.
6.7.10 Notices from the engine
| Webhook | Tell | How |
|---|---|---|
agent.device_paired |
The agent (and their manager): "New computer connected: Agent Phone on reception-pc. Not you? Remove it." | In-app notice; e-mail if you e-mail security notices |
agent.device_revoked with TOKEN_REUSED |
Owner/manager: security alert (6.7.4) | Prominent, until read |
agent.device_revoked (other reasons) |
Update the device list | Quiet |
agent.shift_started / agent.shift_ended |
Shared computers page; the agent's home | Quiet |
station.paired / station.removed |
Shared computers page | Quiet |
call.ring_declined |
Manager's live board: "Asha declined (with a customer)" | Live counter; the reasons add up in agent stats |
6.7.11 A browser desk next to Agent Phone
If you also keep the browser agent desk (6.4), it must behave when the agent has Agent Phone open:
- Each browser tab gets its own identity from
POST …/agents/{ref}/lobby-token; tabs and Agent Phone no longer push each other out. - When a
ringhas"audible": "phone", don't play a sound in the browser: show a quiet card "Ringing on Agent Phone" with Accept still there (whichever accepts first wins). - Waiting callers can be answered from the queue with Take (
POST /v1/calls/{callId}/take, 2D J9), even while the agent is away.
6.7.12 Don't (Agent Phone)
- Don't send pairing codes by message or keep them; show them once, on screen, to the right person.
- Don't let a signed-in person pair, remove devices, start shifts or set switches for others unless your roles allow it.
- Don't ignore
TOKEN_REUSED: someone copied a sign-in. - Don't ring in the browser when the ring says
audible: "phone". - Don't keep a device token or a station token anywhere: your app never needs one.
- Don't block a call on your contacts URL: answer fast or not at all.