REST endpoints

The key REST endpoints — agents, web/trial calls, numbers, calls, messages, and wallet/usage.

No MCP client? Every capability is also a plain JSON REST endpoint under /v1. Authenticate with the same scoped tai_ key as a Bearer token. The OpenAPI spec is the machine-readable source of truth; this is the human map.

Building a customer application?

Start with Customer applications, then connect live data through Integrations. The Generated API reference lists the current customer SDK operation set from the canonical OpenAPI snapshot.

Agents

MethodPathPurpose
POST/v1/agentsCreate an agent (external_audio, external_text, hosted).
GET/v1/workspace-contextDiscover the key's own organization and a bounded project page.
GET PATCH/v1/agents/{id}/voice-stackRead or set the hosted agent's STT/LLM/TTS stack.
GET/v1/agents/{id}/workflowRead a draft/revision and bounded published-version summary before editing or publishing.

Bootstrap and configure a hosted agent

Start a machine client with GET /v1/workspace-context and account:read. The bearer key decides the organization; this route does not accept an organization_id selector. It returns only the safe organization id/name plus a stable, explicitly bounded page of project id/name/environment values. Use the returned page metadata to continue discovery. It also works for a new workspace with no agents.

For a hosted agent, GET /v1/agents/{id}/voice-stack needs agents:read and PATCH /v1/agents/{id}/voice-stack needs agents:write. Select platform-managed STT, model, and TTS components by their catalog selectors. For BYOK, first create the credential through the separate write-only hosted-AI credential API, then supply only that existing credential reference in component_credential_bindings. Voice-stack reads never return a secret, ciphertext, credential-binding ID, or provider credential; unsupported component, language, and credential combinations are rejected on update.

Voice settings belong to the voice-stack route

PATCH /v1/agents/{id} edits the agent's own fields: its name, status, webhook and audio-stream URLs, prompt, first message, mode, tools, metadata and recording flag. It does not save voice settings.

These six names belong to PATCH /v1/agents/{id}/voice-stack (MCP tool set_agent_voice_stack):

SettingWhat it configures
turn_takinginterruption sensitivity, endpointing, and the automatic call limits
voice_polishend-of-call phrase, delivery profile, acknowledgements, tool-wait
audio_environmentthe pinned ambience preset
follow_callerthe automatic multilingual policy
languagesthe selected language set
language_profilesper-locale voice, transcriber and greeting overrides

Sending any of them to PATCH /v1/agents/{id} returns 400 naming the route that does save them. Until this release the same request returned 200 and silently discarded the setting, so a limit you believed was saved was not, and the call then ran on the platform default. If you have code that sets turn_taking through the plain agent route and appears to succeed, it has never been taking effect; move it to the voice-stack route.

The MCP tool update_agent is the same route and behaves the same way: it is documented as applying "the same generic PATCH fields and validation as PATCH /v1/agents/{agent_id}", and from this release that includes the refusal. An agent script that sets turn_taking through update_agent now gets an error where it used to get a success it should never have had.

Unrelated fields your client happens to send are still accepted and ignored, as before. Only these six recognised-but-misplaced names are refused — they are not "unknown fields", so REST's no-op compatibility for genuinely unknown keys does not cover them.

A voice selection must name an exact voice

PATCH /v1/agents/{id}/voice-stack returns 400 if you send a TTS provider and model without a voice_id, whenever that provider has voices to choose from. The message asks you to choose an exact provider voice for the selected TTS model.

Until this release such a save could return 200 while storing a stack with no voice. Nothing reported a problem until the agent was called, and the call then failed before the caller heard anything. Refusing the save is how you find out at the moment you make the change rather than on the next live call.

Two things are unchanged. Saving only a transcriber, with no voice selection in the request, is unaffected. And editing an older agent whose stored voice predates this rule still works: that repair path resolves the provider's own default voice rather than refusing your edit.

Before a workflow PATCH or publish, use GET /v1/agents/{id}/workflow?version_limit=20&version_offset=0 with agents:read. It gives you the editable draft/revision, validation, safe canvas layout, current published-version metadata, and a newest-first bounded version page to compare. An existing agent with no draft returns workflow_state: "not_created"; the read never creates a blank workflow, compiles, repairs, calls AI/providers, or writes state.

The Generated API reference and live OpenAPI document carry the exact request and response contracts.

Audio environments

MethodPathPurpose
GET/v1/audio-environmentsRead the immutable, versioned licensed ambience catalogue for the workspace.

GET /v1/audio-environments needs account:read. It is a local catalogue read: it does not contact a voice provider, call a carrier, change a selected environment, or return audio bytes. See Audio environments for the outgoing-ambience and caller-input contracts. A 503 means the packaged catalogue failed its integrity check; retry rather than inventing a preset or substituting another version.

Calls

MethodPathPurpose
POST/v1/trial-callsCreate an offline fixture with synthetic_only: true; not a live phone trial.
POST/v1/calls/outboundPlace a policy-gated outbound call.
GET/v1/callsList calls.
GET/v1/calls/{id}Call status + recording state.
GET/v1/calls/statsCall volume, outcome, talk time and spend over one or more date windows.

What a call-stats window counts

GET /v1/calls/stats needs calls:read. Each window you ask for comes back with since and until plus five integers:

FieldWhat it counts
callsEvery call created in the window, including ones a policy blocked and ones that were never billable.
answeredCalls somebody was on: completed, or answered, in progress, or with media started right now. Never a call the platform merely accepted.
failedCalls that ended with nobody on them: failed, busy, no answer, canceled, or refused by a policy or live-call gate.
talk_secondsTotal talk time across the window's recorded calls.
spend_minorThe wallet reservation estimate made when each call was admitted, in the wallet's minor units. This is not settled billing.

answered and failed do not have to add up to calls. A call that is still dialing or still queued is in neither, and a call waiting on operator review is in neither. Read the two figures the endpoint gives you; do not derive one from calls minus the other.

Numbers

MethodPathPurpose
GET/v1/coverageCountry coverage matrix.
POST/v1/number-requestsRequest a managed number (operator-approved).
GET/v1/numbersList numbers and registration status.
GET PUT/v1/numbers/{id}/rulesRead / update number rules.

How long a regulated number takes to activate

Some countries require the carrier to approve a registration before a number carries traffic. Two fields describe that wait, and both are deliberately silent rather than guessing.

activation_estimate, on the requirements for a country and number type, is not always a duration, and must be checked before it is displayed as one. It holds a duration only where the number needs a carrier registration and the carrier publishes an activation time for that market: that published figure, passed through unchanged, usually measured in weeks. Where registration is required and the carrier publishes nothing — Estonia among them — it is empty, because OpenPhonex does not substitute a figure of its own.

The other two values are routing states, not times:

  • instant_after_payment_and_policy_checks — the number needs no carrier registration at all.
  • manual_review — whether it needs one is not yet established.

Either describes the market, never your particular request.

The headline on GET /v1/end-users/{end_user_profile_id}/registration-status says how many of that profile's number registrations OpenPhonex has submitted to the carrier. It is empty before anything has been submitted, and empty while a refusal or a request for more information is outstanding, because that is what needs your attention instead. The per-number headline on GET /v1/numbers/{number_id}/activation reports the same submission for one number and adds the carrier's published activation time when that market has one.

An empty headline is not a missing value: it means nothing has been submitted that it could report. For the stage a request has actually reached, read regulatory_status on the number, and blocking_items on the activation endpoint for what it is waiting on.

next_action on the activation endpoint is held to the same rule. It repeats the first blocking item when there is one. With nothing blocking it tells you to wait for carrier approval only when the registration really is open at the carrier; where nothing has been sent it says so, and where a registration exists but is not showing as open it says that instead of guessing either way.

Messages

MethodPathPurpose
GET/v1/conversationsList SMS conversations.
POST/v1/messagesSend a policy-gated SMS.

Capabilities

Outbound calls and SMS authorize against the workspace's capabilities record. Machine clients can inspect that record, manage optional destination restrictions, or request higher limits with these commands. See Policy & outbound for what each field means.

MethodPathPurpose
GET/v1/organizations/{organization_id}/capabilitiesThe record, its calling rules, the selectable profiles, and live usage against its caps.
POST/v1/organizations/{organization_id}/capabilitiesRequest or replace the selection. profiles is required, and so is a destination choice: all_countries: true or a countries allowlist.
POST/v1/organizations/{organization_id}/capabilities/accept-policyAccept a policy version without changing what the record authorizes.
POST/v1/organizations/{organization_id}/capabilities/destination-restrictionsSave optional customer narrowing with the current destination_revision; countries: null removes only this extra restriction.
POST/v1/organizations/{organization_id}/capabilities/limit-requestsRequest higher limits with a reason; does not change caps, prices or wallet funds.

For an explicit profile-selection request, the destination choice is all_countries: true (every destination except sanctioned countries) or countries naming at least one ISO 3166-1 alpha-2 code. A request with neither, with an empty list, or with an entry that is not a two-letter code once normalized answers 400, names the entry, and writes nothing. The check is on shape, not on membership of the assigned ISO list, so a well-formed but unassigned code is stored as sent and then matches no destination — see Destination controls.

Wallet & usage

MethodPathPurpose
GET/v1/account/overviewWallet, agents, numbers, usage rollup.

The wallet's purchased availability and browser-only promotional credit are separate. See Billing and browser credit before treating a balance as available phone spending.

Keys & scopes are identical to MCP

REST and MCP share the same scoped keys and the same fail-closed scope checks. See Authentication & keys.

On this page