The portal
Every screen of the console, control by control — the agent builder and each of its eight tabs, the queue of calls, what every page shows and what each of its states means. Written for whoever runs the account rather than for whoever writes the code.
Getting in
One screen with several states, each named at the top.
| Screen | What it asks |
|---|---|
| Sign in | Email and password. |
| Create an account | Your name, work email, organisation and a password. |
| Create the first administrator | The same, on a deployment that has no users yet. |
| Check your email | A six-digit code sent to the address. It lasts ten minutes and takes five tries; a wrong one says how many tries are left. After signing up, Resend code unlocks after a minute (the button counts down) and sends a fresh code — only the newest works, and an address gets at most five an hour. A code that has expired says that code has expired — request a new one. The sign-in code has no resend: start again to get a new one. |
| Accept your invitation | Confirms the invitation and takes a name and password. A link missing its token says that invitation link is incomplete. |
| Reset your password | Your email, then one screen with the six-digit code we emailed, the new password and its confirmation. The code follows the same rules as a signup code, with the same Resend code countdown and Use a different address. Setting it signs you in and ends every other session. |
Back to sign in returns from any of them. /invite lands directly on the invitation screen. Password reset has no link to land on: the code is typed into the same screen that asked for it.
An account that belongs to no client yet gets a page saying so rather than an empty console. It tells three situations apart, because only one of them needs you to do anything: a platform operator is meant to be there and has every account in the switcher in front of them; somebody with accounts but none entered can pick one; and somebody with no accounts at all is told plainly that the fix is a row in somebody else's members table, rather than being pointed at a switcher that is empty and disabled.
The shell around every page
The sidebar
Six groups. Which entries appear depends on your role and on what the deployment has switched on.
| Group | Entries |
|---|---|
| Account | Overview |
| Observe | Request log, Alerts, Call ratings |
| Build | Dialer, Agents, Ideas, Agent groups, Knowledge, Phone numbers, Campaigns, Journeys, Calls |
| Money | Projects, Usage, Billing |
| Access | Team, API keys, Webhooks, Audit |
| Platform | Clients, Announcements, Users, Pricing, Integrations, Product feedback — super admin only, and folded away by default |
Two old bookmarks still work: /providers and /models both land on Integrations, which is what they were merged into. A dead bookmark reads as the feature having been removed rather than renamed.
The topbar
Names the page, and under it which account that page is about — the question a platform operator with six clients open asks every time they look up.
On a Platform page it reads Platform · every account instead. That is not decoration: you read "Acme Logistics" over a list of eight accounts and cannot tell whether you are about to change one of them or all of them.
On the right, five things that are reachable from everywhere because none of them is a place — they are actions you take while looking at something else:
| Feedback | Send product feedback without losing your place. |
| Docs | The API reference, served by this deployment itself. |
| Compliance | The compliance reference. |
| Alerts | With a count when there is one. |
| Your account | Profile, theme (light / dark / system), sign out. |
The project lens
When an account has more than one project, pages that hold work show a project scope bar: which cost centre the page below is showing, changed in place. The topbar carries a chip saying the same thing, and rows in tables carry a coloured dot and the project's name — never the dot alone, because colour is what makes forty rows scannable and it is also the one channel a reader may not have.
A project that has since been deleted still shows on the row it was recorded on, as Deleted project, rather than vanishing.
The portal is the API
Nothing in the console is a private feature. Every screen here reads and writes the same endpoints your own code can, with the same permissions — the portal signs a person in where an API key authenticates a program, and that is the whole difference.
Which matters in two directions. Anything you can do here you can automate; and anything that looks wrong here can be checked against the API without waiting for a screen to be fixed. The Dialer places exactly this call:
curl -X POST https://voice.sphoro.com/v1/calls \
-H "Authorization: Bearer $SPHORO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"agent_id": "agt_...", "to": "+919000000000"}'Keys are minted on API keys. The full surface is the API reference, and authentication covers the header, the roles and the error codes.
Roles: who sees what
Five roles. Authorization is decided on the server for every request; the portal hides what you cannot use rather than letting you press it and take a refusal.
| Role | |
|---|---|
| Operator | Places and answers calls, and nothing else. Cannot create or change anything. Gets its own console rather than this one. |
| Member | Builds agents and makes calls. Cannot manage access or mint API keys. |
| Admin | Manages members, API keys and settings. Cannot delete the account or add owners. |
| Owner | Full control of this account, including billing, ownership and deleting it. |
| Super admin | Operates the platform. Sees and manages every client account. Held by whoever runs Sphoro Voice — not by a customer. |
The Operator role exists because "member" used to be the floor, and a member builds agents: there was no way to give somebody a handset without also giving them the configuration system behind it. A support desk hiring twenty people to take calls had to make twenty people who could each rewrite the agent that takes them.
You can never grant a role above your own, and a role picker only offers what you may actually assign.
Overview
The page the portal opens on: what this account is, what it is doing right now, and who has been in it. Overview and Monitor used to be two pages that opened on the same question and answered half of it each. They are one page now, and an old link to /portal/monitor lands here. Analytics followed for the same reason, and an old link to /portal/analytics lands here too.
The account, in one line: status, plan, members, active API keys and your role. Plan, members and keys link to the page that changes them, when your role can open it.
Right now is refreshed every five seconds, with the age of what you are reading and a Refresh now beside it. If refreshing stops working a warning says so, because a page that has quietly stopped updating looks exactly like a healthy one.
| Panel | |
|---|---|
| Tiles | Calls in progress, calls, errors and API failures. Calls, errors and API failures each open the page with the detail — Calls, Alerts and Request log. |
| Recent calls | The latest calls, running ones first, each named by the agent that took it. A call still running after twenty minutes is flagged: that is almost never a conversation, and far more often a leg the carrier never tore down. |
| Pipeline latency | Each stage of the turn — speech-to-text, retrieval, the model, synthesis — in the order a turn runs through them, with p50, p95 and p99. This is the panel that says which stage is slow, which is the only version of "calls feel laggy" anybody can act on. |
Each panel says plainly when it has nothing: no calls yet, no latency samples yet. An empty panel is not the same as a healthy one. Right now appears for roles that can read the account's calls.
Over time is what the account's traffic did over a window you choose, and whether it went well. It is loaded when the page opens rather than refreshed, and it leaves out what Right now already shows: the call and error counts and the latency by stage are the same figures, and two copies of one count on one page disagree as soon as a call starts. It appears alongside Right now.
| Panel | |
|---|---|
| Time window | Last 24 hours or last 14 days — the span the traffic chart and the talk-time sparkline are drawn over. |
| Tiles | Talk time, with the average call, and turns per call. |
| Traffic | Calls, minutes, turns, errors or cost across the window — one at a time, because one plot gets one axis. |
| How calls ended | Completed, ended early and live now. Ended early is a hang-up, a transfer that did not land, or a failure; the end reason for any one call is on Calls. |
| Quality signals | Tools succeeded and Retrievals found context — the two numbers that say whether an agent's tools and knowledge base are actually load-bearing or merely configured. |
| Busiest agents | The eight that answered the most calls, with their errors. The table lists every agent. |
Every chart has a table one button away, with the numbers it is drawn from.
Under it, Team — the five members who signed in most recently — and Recent activity, the tail of the audit log. Each links to its full page.
For a super admin it is also the platform's front page, with four counts that are each a link: clients, suspended (nobody in them can sign in), without a member (created, never handed over) and users. Two counts would say how big the platform is; these say what needs doing.
Observe
Request log
Every API request this account made — requests, failed and the failure rate — and, in its own panel, recent failures. The failures panel is separate because a failure buried in a list of successes is a failure nobody finds.
With nothing wrong it says nothing has failed. This is the good outcome rather than showing an empty table you have to interpret.
Alerts
What the platform has raised about this account.
Call ratings
What callers thought, when they were asked.
Scores — average out of 5, responses, response rate as a share of completed calls, and promoters — beside what callers said.
On an account that has never collected one, the page explains how ratings arrive rather than showing an empty chart. An empty quality page reads as "nobody complained", which is not what it means.
Build
Dialer
A telephone in the portal: type a number, place a call, and hear it in the browser. On a call replaces the form while one is in flight.
The number is entered in two halves — a country and the national number — because that is what a person actually chooses and types. Pasting +44 20 7946 0958 is handled, so it is not dialled as an Indian number.
Style decides who takes the call:
| Style | |
|---|---|
| Agentic — the agent handles it | It talks, looks things up and can call your functions. You listen in. |
| Simple — I talk | You take the call yourself. Nothing here listens, and there is no transcript. |
| Custom | One of the agents you have built. |
Agent appears only for Custom — it is the one style that cannot work without it, and the other two have nothing to guess. With no agents built yet, it says so and points at Agents.
Use this to hear an agent before pointing a phone number at it.
Agents
The list, with bulk selection: select the agents shown, move to group, delete.
Recently deleted is a folded panel at the bottom with restore and delete for good. It is folded because it is a list of things somebody has already decided against, and absent entirely when it is empty. It is here rather than on a settings screen because the operator who deletes the wrong agent looks for it where the agents are.
Making a new agent
Start a new agent asks the shortest useful question first — who takes these calls? — and then how do you want to start?
| Route | |
|---|---|
| Describe the line | What does this line do? in a sentence, plus the language and direction, and the platform writes the first version. It then puts it in front of you under the heading "Read it before it answers a telephone" — name, greeting and instructions, all editable — because a generated prompt nobody read is how an agent says something you would not have. |
| From a template | Pick one, name it, and choose the language and any knowledge bases. |
| Blank | Name, language, knowledge bases. |
| New line you answer | A person-held line rather than an agent-held one. |
Creating an agent hands over to the builder with the "which numbers reach it" dialog already open, because the form that made it no longer asks.
The agent builder
Eight tabs. Arrow keys move between them; on a phone the bar scrolls and the edges are marked when there is more.
Fields you are not allowed to change are locked rather than hidden, so the form is the same form for everybody. Where a value comes from a group it says so, with what it inherited.
On an agent that was built for you, editing a locked field says so in full rather than failing quietly: this agent was set up for you, so its <field> is not yours to change here — and then names what is: you can point numbers at it, choose the caller IDs and the carrier, and decide whether calls are recorded.
1. Agent — what it is and what it says
| Field | |
|---|---|
| Name | |
| Description | For your team. Callers never hear it. |
| Project | Which cost centre its calls are attributed to. |
| Group | Which agent group it belongs to. |
| How it takes input | The agent's mode. |
| Who holds this conversation | Agent-held or person-held. |
| What it may do | Its autonomy. |
| What it says | The greeting and the opening lines. |
| Filler phrase | What it says while it is thinking, so a pause is not silence. |
2. Languages — every language it speaks
One chip per language, the one calls open in marked as the default. Everything below the chips belongs to whichever chip is selected — you pick a language and then configure that language, rather than deciding for every field whether it means the default or one of the others.
Per language:
| Field | |
|---|---|
| Language | |
| Synthesizer | The TTS vendor. |
| Voice | |
| Recognizer | The STT vendor. |
| Greeting |
Plus, for the agent as a whole:
| Field | |
|---|---|
| How it speaks | Speed and pitch. |
| Cached audio | Fixed lines rendered once instead of on every call — see voice-lines.md. |
Which vendors appear in the per-language pickers depends on what the deployment has connected under Integrations. A vendor with no key is not offered.
3. Intelligence — the model and what it knows
| Field | |
|---|---|
| Model | |
| Max tokens | |
| Knowledge bases | Which corpora it may retrieve from. |
A Voice engine card picks how the agent runs. If it is set to the realtime engine on a deployment with no realtime provider connected, the card says so in the error tone rather than letting you find out on a live call: calls to it will connect and then say nothing.
4. Calling — the telephone line
| Field | |
|---|---|
| Rings for | How long before it answers. |
| Record calls | |
| Transcribe calls |
Below the tabs, the line itself:
| Field | |
|---|---|
| Direction | Answers calls, places calls, or both. |
| Numbers that reach this agent | |
| Caller IDs | What it shows when it dials out. |
| Carrier |
5. Engine — how it listens, when it gives up, what it keeps
How it listens — the keypad menu:
| Field | |
|---|---|
| Keypad menu | The IVR tree. |
| When nothing matches | |
| When nobody presses | |
| Wait for a keypress | |
| Reminders before hanging up |
Conversation:
| Field | |
|---|---|
| Words before it stops talking | Barge-in sensitivity. |
| Extra stop words | |
| Ask if they are still there after | |
| Hang up on a quiet line after | |
| Longest call | |
| When an answering machine picks up | And the message to leave. |
Hang up when the conversation is finished — with when is it finished?, which is the condition it is judged against.
6. Tools — what it can reach mid-call
Tools this agent carries, laid out as a catalogue rather than one form, because the interesting decision is which kind of tool this is and each kind then needs a different half-dozen fields. One form covering all of them would show a calendar timezone to somebody configuring a transfer.
| Kind | What it is for |
|---|---|
| Calendar availability | Read the open slots before offering a time. |
| Book appointment | Write the agreed time into the calendar. |
| Transfer call | Hand the caller to a person. |
| Custom function | Any HTTP endpoint of your own, described to the model as a function. |
Every tool has these two, whatever its kind:
| Field | |
|---|---|
| Task name | What the model calls, and what you will see in transcripts and analytics. |
| Description (prompt) | Say when to use it and when not to. This is the single biggest lever on whether the tool fires at the right moment — it is the whole of what the model has to go on, and a blank one produces an agent that books appointments nobody asked about. |
| Pre-tool message | Spoken while the tool runs, so the caller hears what is happening rather than silence. Empty uses the agent's own filler phrase. |
Calendar tools add:
| Field | |
|---|---|
| API key (Cal.com) | Generated under Settings → Developer → API keys on cal.com. Encrypted before storage and never shown again. |
| Event type | Which bookable thing on that calendar. It carries the length, the availability schedule and the questions asked at booking, so it is most of the configuration. |
| Timezone | The caller's zone, not the business's — a time read back to somebody is only right in theirs. |
Transfer call adds:
| Field | |
|---|---|
| Transfer to | Fixed here, never chosen by the model: a destination a model could name is a toll-fraud primitive wearing a feature's clothes. |
| Tell your server before the transfer | Posted the transfer's details before the call moves, so whatever picks up knows what is arriving. Waited on for three seconds at most — a slow endpoint must not be able to hold a caller. |
Custom function adds:
| Field | |
|---|---|
| Parameters | What the model is asked to supply. Everything not listed is fixed, and the schema is closed — a model that invents an argument is corrected rather than having it forwarded to your endpoint. |
| Body | Empty sends the parameters as a JSON object. Fill it in for an endpoint whose body is mostly fixed, using {{parameter}} where a value varies; the caller's words are escaped into it. |
| Headers | Tick secret for anything that is a credential. Secret values are encrypted before storage and never shown again. |
| Timeout | How long this may run. A caller is waiting on it with nothing but the pre-tool message over the gap. |
Paste the command takes a curl and fills the form in from it: method, URL, headers and body are read out, the values in the body become parameters, and anything that looks like a credential is lifted into the sealed half of the headers.
Secrets go out and never come back — a key field shows a hint and stays empty, and an unedited one is not re-sent, which is what makes changing a description possible without retyping a key.
Below the tools, transfer number is the agent-wide fallback. See functions.md.
7. Flow — the steps a call moves through
| Field | |
|---|---|
| Use a flow | Off, and the agent is a single prompt. |
| Calls open on | The node a call starts at. |
| Definition | The same flow as JSON, parsed when you leave the box — the escape hatch for anything the visual editor cannot express. |
Per node:
| Field | |
|---|---|
| Node id, Kind | |
| What this node is for | Read by the router as the objective when the node has no prompt. |
| Instructions for this step | Appended to the agent's prompt while the call is here. {{variables}} captured earlier are filled in. |
| The line | Read out exactly as written. |
| Re-prompt after silence | Seconds. 0 leaves it to the agent's own silence settings. |
| Must call this tool | Forced until it has run once on this visit. |
Per edge:
| Field | |
|---|---|
| Go to | |
| When | The condition. |
| Which is | Written as the caller's behaviour: "the caller confirms the amount". |
| Capture | Values to pull from what they said, as name:type pairs — date:string, amount:number. All of them are required for the edge to fire. |
| Event name | What an outside system posts to /v1/calls/{id}/events to move the call from outside. |
Full reference: flows.md.
8. After the call — what is written down once it ends
| Field | |
|---|---|
| Summary | |
| Fields to extract | Each with a name, a type, and options where the type takes them. |
| Model | Which model does the analysis. |
Full reference: post-call.md.
Trying it
Three ways, offered according to what the agent is:
| Mode | |
|---|---|
| Phone | Type a number; it dials. |
| Web | The call is held in this browser. |
| Chat | Text, no audio. |
Any {{variables}} the agent expects are asked for first — the panel will not place a call with one missing. On a person-to-person line with no number, the two modes meet in the browser: this tab joins on one URL and whoever answers joins on the other.
Deleting
At the bottom, behind a typed confirmation.
Ideas
An idea is a script several agents share — the instructions, the greeting and the languages, and the voice, knowledge, tools and flow behind them — kept apart from the telephone, so one idea can run an inbound line, an outbound campaign and a web widget.
The list shows each idea as a tile: its greeting (or the start of its instructions), its languages, and the agents used by it, each a link to that agent. Search matches names, greetings, instructions and agent names. An account with no ideas sees the three steps instead.
New idea starts one in either of two ways:
- Write it — a name, an optional greeting, and the instructions.
- Copy an agent — copies an agent's instructions, languages, voice, knowledge, tools and flow into a new idea and moves that agent onto it, so nothing it says changes. Agents already on an idea are not offered.
Opening an idea edits its name, description, greeting and instructions — per language, when it speaks more than one. The save bar says how many agents a save reaches, and a refused save lists each field to fix. Used by links every agent on the idea; Also part of this idea summarises the model, knowledge, tools and flow, which are edited on any agent using it (saving there saves to the idea). An idea can be deleted only once no agent uses it.
An agent picks its idea on its Agent tab, under Idea, where the idea's name links back here. See personas.md.
Agent groups
Settings shared across a fleet of agents. Joining a group changes nothing on its own — see agent-groups.md.
New group: a name and a colour, picked from the server's palette so the page cannot offer one a save would refuse.
Inside a group:
| Card | |
|---|---|
| What it is | Name, description, colour. |
| What it sets | The defaults: model, per-vendor models, temperature, reply length, speaking speed, pitch, what it does besides talk, what it may do, tool rounds per turn (0 uses the default of three — every round is another wait with the caller on the line), knowledge lookup budget in milliseconds (50–2000, or 0 for the default; it is spent inside the turn, with the caller waiting), call recording, call transcription (billed by the minute and by the person), and a webhook URL where the record of every call these agents take is posted. |
| N of M agents differ from these defaults | Each difference shown the way an operator reads one: field — from → to. Select the ones you want and adopt the group's value. |
| Members | With add an agent. |
| Delete this group | Behind a typed confirmation. |
Knowledge
The knowledge bases this account's agents may retrieve from.
Knowledge workspace names where all of this actually lives. Every account has exactly one workspace in the knowledge platform, created with the account and named after it, and its id is this account's id — so a document added on either side is answerable on both with nothing to sync. While the account exists that workspace cannot be deleted or suspended over there; deleting the account releases it. If the panel warns that the knowledge platform does not have the workspace marked as this account's, say so: it means somebody there could remove it, and agents would keep answering and quietly stop citing documents.
New knowledge base creates one. Already in the knowledge platform lists corpora that exist on the knowledge platform but have no row here — almost always somebody who built one over there and came here looking for it. Add adopts it: its documents are already indexed, so agents can use it immediately.
Inside one, add a document:
| Field | |
|---|---|
| Where it comes from | Text, a web address, or a file. |
| Title | |
| Text / Web address / File |
Plus the Documents list. Full behaviour: knowledge-base.md.
Phone numbers
| Field | |
|---|---|
| Connect a number | The number itself. |
| Carrier | Answers itself when the deployment has only one — a field with one possible answer is not a question. |
| Direction | Inbound, outbound or both. |
| Answered by | Which agent. Every agent is offered, not just the current project's: a number in one project may perfectly well be answered by an agent in another, and narrowing the picker would make a legal choice unreachable. |
| Belongs to | A member's name, making it their direct line — or blank for the account's pool. One name, not a set. |
Whether this account may bring a number of its own is decided by the same rule that would refuse the save, so the form never offers a control that is going to be turned down.
Campaigns
Run a journey or an agent over a list of contacts.
New campaign:
| Field | |
|---|---|
| Name | For your team. Nobody called ever hears it. |
| Agent | |
| Contacts | A file upload, or numbers typed one per line or comma-separated. |
| When it is ready | Start now, or hold it. |
The list carries search, bulk selection, and per-campaign add contacts.
A running campaign reports as it goes. Contacts outside the recipient's local calling window are marked as such — they are dialled when it opens, not dropped. Read compliance.md before running one.
Journeys
Multi-step automation across many calls and days: call, branch on the outcome, wait, retry, fall back to a message.
New journey takes a name. Inside, the definition — nodes and edges — on a canvas: click a box to edit it, drag from a box's right edge onto another to connect them, drag the background to pan, scroll to zoom. A This journey panel beside it counts the nodes, names what calls start on, and counts the problems.
A separate panel lists things to fix before publishing, so a draft cannot be published into a state that would fail on a real contact.
Per node, depending on its kind:
| Field | |
|---|---|
| Description | What this step is for. Shown on the contact's timeline. |
| Agent | Whose voice makes this call. |
| Wait for the call to be analysed | |
| Wait for | A duration: 30m, 24h, 72h. |
| Only resume between | A window, so a retry does not fire at 3am. |
| Method, URL | HTTPS only. {{variables}} are filled in per contact. |
| Channel, Message | For the message fallback. {{variables}} filled in per contact. |
| Workflow | One of your post-call workflows. |
| Outcome | What this ending is counted as: converted, unreachable, opted out. |
| Try this node at most | The retry ceiling. |
Put one contact through takes a single E.164 number — +919000000000 — and runs the journey against it. The field says plainly that this places real calls.
Full reference: journeys.md.
Calls
Every call, with a detail drawer.
| Control | |
|---|---|
| Search | Number, id, agent or summary. Press /. |
| Outcome chips | With counts. Chips rather than a dropdown, because the counts are half the answer — "how many needed somebody and got nobody" is read off the row without opening anything. Only outcomes that have actually happened are offered, plus whichever one is selected. |
| Export CSV | The calls the filters are showing, not the whole table — the button's tooltip says which. |
| N live | Refreshes every five seconds while a call is in flight. |
| Updated <when> | With the exact time on hover. |
The filter controls only appear once there is a first call, so a new account meets a list rather than a toolbar.
Tiles: Calls, Talk time, Answered, Turns, Recorded.
A row marks whether it has a recording and which digits the caller pressed, in order. Opening one gives the transcript, then What was said — the post-call summary and the extracted fields, stamped with when the analysis ran — and the call id with a copy button. That id is the thing to quote in a support ticket.
Money
Projects
Cost centres inside the account: what work is attributed to, with a monthly budget and a decision about what going over does.
Cost centres lists them. Each has a name, a description, a colour — which is what the dot beside a row in every other table is drawn from — and a monthly budget. Each can be edited, and deleted behind its own confirmation.
One row is marked · default: the project everything unfiled is attributed to. It has no Delete button and cannot be archived, because a control that only ever refuses is worse than no control. Make default on any other row hands it the marker, which is also how the current default is freed to be deleted — nothing is re-attributed, and the old project keeps everything it has already cost.
Work that names no project shows as Unassigned rather than being hidden. See projects.md.
Usage
| Panel | |
|---|---|
| Call volume | Calls over time. |
| Where the tokens went | |
| Where the minutes went | |
| Latency by stage | |
| Quality signals | Tool calls, retrievals and turns per call. |
| Against your <plan> plan | What is used against what is included. |
Each panel names its own empty state — no calls in this window yet is a different statement from no calls recorded yet, and the page keeps them apart.
Billing
At the top, the balance as arithmetic: what was granted, what has been spent, and tokens remaining — with that last figure also expressed as ≈ N minutes and about N days, since a token count is not something anybody can plan around.
| Panel | |
|---|---|
| What the pending charge is made of | Only when the account is billed on more than conversation time. Every driver, with what was used, the rate and the tokens. |
| What you are charged | Per minute — metered by the second, not rounded up. Token price per 1,000. Plan fee per month, or by contract. Monthly grant, as tokens and as minutes. Billed in — the currency. Last settled. |
| Usage against your plan | Gauges over the stated window. |
| Ledger | Every movement. An account where nothing has moved says so. |
| Add tokens | A pack, or a specific number of tokens, with a reference — e.g. Invoice 1042. |
| Have a plan code? | PLAN-XXXX-XXXX-XXXX-XXXX. Redeeming one is how a plan changes; ask for a code is how you get one. |
The page recommends a plan when it should, and says which of two reasons it is: the account has outgrown this plan and the exceeded limits are in red, or at this volume another plan would simply cost less. Those are different arguments and it does not blur them.
Owner only.
The controls a super admin also sees
Four more cards appear on this page for whoever operates the platform.
| Card | |
|---|---|
| Adjust tokens | Grant or claw back tokens outside a purchase — goodwill after an outage, a correction, a migration balance. Positive adds, negative removes, and a reason is required. This is not a top-up: the ledger keeps the two apart so "did we ever charge for this?" stays answerable. |
| Billing currency | What this account pays when it buys tokens. Balances are market-neutral, so this changes the price of a pack and nothing else. Locked once credit has moved — changing it then would reinterpret what every past purchase cost. |
| Set the plan | Applies immediately, settling the meter at the current rate first. The only route to a custom tier, which cannot be issued as a code. |
| Plan codes | Mint a code for a plan, valid for a period, with a note. Use this to let the customer pick the moment instead of switching them yourself. |
Access
Team
Invite someone — an address and a role. Invite a team takes several at once. An address that already had an account is added straight away and says already had an account — added, rather than sending an invitation nobody needs.
Project access decides which cost centres one member may reach: every project, or only these. An owner or admin reaches every project by role, so on their rows the control is said rather than simply not offered — a missing control reads as a bug. An account with no projects yet says so instead of offering an empty picker.
What the roles mean sits under the table: each role with the sentence describing it, so the picker is not a question you cannot answer.
API keys
Create an API key:
| Field | |
|---|---|
| Name | What will use it — production-api. |
| Expiry | Either days until the key expires or a date, whichever way you think about it. |
| Tags | Free labels — staging — for finding a key later. |
The secret is shown once, on a panel that says so. Revoke from the list. An account with none says no keys yet.
On a platform login with no account selected the page says choose a client account first, because a key belongs to an account and there is nothing to mint one against. See authentication.md.
Webhooks
A name (Zendesk ticket sync), an endpoint (https://example.com/webhooks/voiceai), and which events it wants — everything, or only these. Creating one shows a signing secret, once.
Deliveries lists what was sent and what came back, which is where a webhook that "never fires" is actually diagnosed. Deleting one asks first.
As with keys, a platform login with no account selected says choose a client account first. See webhooks.md.
Audit
Who did what.
Filtered three ways: free text, and by area — the prefix of the action, client, user, key — which is the only grouping the log has and the one people read it by: what did we do to accounts this week. A super admin also gets a platform-wide scope.
Platform
Super admin only. The server refuses these to everyone else regardless of what the browser renders.
Clients
Every client account.
Create a client takes a name and a plan. The list filters by name, slug or plan, and marks:
- suspended — nobody in these accounts can sign in.
- no owner — nobody can sign in to this account; invite an owner.
Inside one: Settings, Plan, Activity, its members and its API keys, and a danger zone with a permanent delete behind a typed confirmation. An account nobody has been added to says nobody has access to this client yet, and one with no keys says no keys — both of which are states worth seeing rather than blank space.
Creating an invitation from here reports invitation created.
Announcements
What the portal says to customers.
| Field | |
|---|---|
| Where it appears | |
| Message | One line. It shares a row with the link on a phone. |
| More, if it needs it | |
| Tone | Each with what it is for, so a palette is not picked by hue. |
| Colour | The tone's colour, or one of your own as hex. |
| Icon | |
| Link | |
| Can they close it? | |
| Runs | A first and last day. Blank first day means as soon as it is published. |
| Who sees it | Including plans, drawn from the billing catalogue so it cannot name a tier that does not exist. |
Bump the revision shows it again to people who had closed it.
Users
Every person on the platform, across every account.
Create a user makes one directly, rather than by invitation. Filter by email or name.
Each row marks whether they are a super admin — full access to every client account — or reach only the accounts they are a member of, and whether they cannot sign in.
Row actions: disable — signs them out everywhere and blocks the next sign-in, reversible — and grant super admin.
Pricing
What a token costs, and what a plan costs.
| Field | |
|---|---|
| Monthly fee — INR / — USD | Per plan, in both currencies, because accounts are billed in one or the other. |
| Monthly token grant | What the fee includes. |
| Pack price — INR / — USD | |
| Tokens per pack | |
| Rates — tokens per unit | What each metered thing costs in tokens. |
| Included | What the plan carries without metering. |
Reset to shipped pricing puts the defaults back. Only a platform administrator can see this page at all — the note on it says so.
Integrations
Every external service this deployment can reach, on one page, in the order a deployment is set up — because the call has to reach you first:
Telephony · LLM · TTS · STT · Knowledge · Notifications
One card per vendor, connected or not, with its models on it, and a filter box over them. A vendor with no credentials reads not connected; a deployment that can reach nothing at all says nothing can be connected yet.
Nobody types a model in. Which models a vendor publishes is a fact about the vendor rather than a preference, so connecting one offers everything this build knows it serves, and disconnecting takes it back out. There is no half-filled state in between.
Connected means in service. A vendor with the credentials it needs serves calls; removing the key is how you take it out. A row disabled by the old switch shows as paused with the one button that fixes it, rather than being quietly dropped from the chain forever.
What this page decides is which vendors are reachable and which of their models are on offer. Which model actually answers is decided on the agent, every time — no card here carries a fallback, because the words a caller hears must not change when a super admin edits a credential on a page the person who built the agent has never opened.
Product feedback
What customers have sent, via the topbar's Feedback button.
Your account
| Card | |
|---|---|
| Your details | Including platform access: either super admin — every client, or client-scoped. |
| Your access | Every client account you belong to, with your role in each. |
| Change password | Current password, then a new one of at least 12 characters, confirmed. Changing it signs out every other session you have open — which is the point of doing it when you think one is compromised. |
| Theme | Light, dark or system — in the topbar's account menu. |
The operator console
A separate site for the Operator role: a telephone on a screen, and specifically the telephone everybody already owns.
Sign in, type a number, press call, talk; when somebody rings, pick up. That is the whole application. There is no agent, no prompt, no model, no voice, no knowledge base, no group, no campaign and no settings page — not because they are hidden from the role, but because a person whose whole working day is this screen should not have to look past a keypad to find the keypad.
The layout is a mobile dialer's on purpose: a number at the top, a 4×3 grid of round keys with the letters under the digits, a round green call button, and a tab bar switching between the keypad and the calls you have had. A call takes the whole screen, as it does on a phone.
* and # are on the pad because a keypad without them is not a keypad anybody recognises. They are live only where they mean something — on a call, navigating somebody else's phone menu — and on the dial screen they say why they are not, rather than sitting there dead.
Everything it can ask for is re-authorized on the server against the session cookie, and the operator role refuses every write in the product.
When something looks wrong
| What you see | What it means |
|---|---|
| A section is missing from the sidebar | Your role does not carry it. See Roles. |
| A field in the agent builder is locked | It is set by the agent's group, or your role may not change it. The form says which. |
| A vendor is missing from the voice or model picker | It is not connected under Integrations. A vendor with no key is not offered, so a picker that lost a vendor means a credential was removed. |
| An agent fails on its first live call with a vendor error | The model it names is from a vendor that is no longer connected. Integrations shows which cards are in service. |
| Calls feel laggy | Overview → Pipeline latency says which stage. "Slow" without a stage is not actionable. |
| A webhook "never fires" | Webhooks → Deliveries shows what was sent and what came back. |
| Campaign contacts are not being dialled | They are outside the recipient's local calling window and are marked as such — they go when it opens. See compliance.md. |
| A journey will not publish | The things to fix before publishing panel lists exactly what. |
| A flow edge never fires | Every value in its Capture list is required. One missing value holds the call at the node. |
| An account exists but nobody can sign in | It has no members — the invitation was never accepted. Clients marks these; invite an owner. |
| An API key stopped working | It expired or was revoked, or the account was suspended. API keys shows the first two. |
| Ingestion is not configured on this deployment on Knowledge | No knowledge provider is connected, so documents cannot be added. Existing bases still work. Integrations → Knowledge. |
| Telephony is not configured on this deployment on Phone numbers | No carrier is connected. Integrations → Telephony. |
| No connect a number form on Phone numbers | This account may not bring its own — its numbers were bought on our carrier accounts and handed over, so the way to get another is to ask. The page says so rather than leaving the form absent. |
Quote the call id — copyable from any call — and the account name in a support ticket. Your deployment serves its own support page at /docs/support, which names the two identifiers that let us find your call in seconds.