Speko turns audio and video recordings into searchable transcripts and structured meeting insights. Transcribe conversations, identify speakers, generate summaries and action items, extract key topics, and search across recorded discussions to retrieve specific information.
Try asking
Encrypted at rest, isolated from the model
Resolved from an AES-256-GCM vault at the moment of the call and attached to the request — the model never sees the secrets.
Get the current workspace and its capability flags: features.buyPhoneNumbers, sms10dlc, imessageIntegration, agentDuplication and others, each as an {enabled} object. features.smsSending is {enabled, reason}: every workspace receives SMS, and sending starts once a number is on an approved 10DLC campaign; reason is no_approved_campaign or campaign_billing_suspended while it is off. smsMessaging is deprecated and mirrors smsSending.enabled. retrievalProvider and imessageProvider are null when the corresponding integration is not configured.
Get the current workspace prepaid credit balance in USD, as of updatedAt. Requires the speko:billing scope, unlike most reads on this surface.
List recent credit movements for the current workspace, newest first. Filter with kind as a comma-separated list of "grant", "debit", "topup", "refund" and "adjustment". Page with the returned nextCursor; it is null on the last page. provider, sessionId and stripePaymentIntentId are populated only on the entry kinds that produced them.
Get workspace usage, managed cost and current balance. from_ and to bound the window; omit both for all-time. totalCost = minutesCost (the per-minute call rate) + addOnCost (telephony, speech-to-speech backend model tokens and other charges on top of the minute). breakdown lists cost per provider and metric, with keySource "BYOK" or "MANAGED" showing whether the workspace supplied its own provider key, and chargeKind "minutes", "add_on", "included" (covered by the minute), "free" (absorbed by Speko, e.g. post-call summaries) or "byok".
List agents in the current workspace without their full system prompts. Each item includes voice, turnHandling and runMode as currently stored. Use agents.get for one agent when the full system prompt is also needed.
Preview the THREE voice-stack options before creating an agent — so the user picks. Returns the same recommendation the dashboard's agent-create shows, as three tiers. Present them to the user with these labels and each tier's STT / LLM / TTS: premium -> "Quality" balanced -> "Fastest" cost_optimized -> "Cheapest" Ask which one they want (and confirm the region — default USA). Then call create_agent with the chosen objective mapped to intent.optimizeFor: Quality -> 'quality', Fastest -> 'latency', Cheapest -> 'cost' plus intent.region. The server then pins that tier's failover stack automatically, so the created agent matches exactly what you previewed.
Create a Speko agent. Never send endCall: the end_call tool is always on and {'enabled': false} is rejected with 400. To keep the agent from hanging up early, say so in systemPrompt. The agent is pinned to the failover stack matching intent.optimizeFor (quality / latency / cost) and intent.region. Stack tiers and their components for a given description are reported by preview_stacks.
Get one agent and its complete editable configuration, including voice, turnHandling (profile, keypad, onMachine, dtmfToolDescription, amdPrompt) and system prompt. Read this before diagnosing a silent call or a missing keypad response. Voice is not validated against what the TTS provider owns. The keypad tool is built for every phone agent unless turnHandling.keypad is false; it arms when turnHandling.profile is "ivr" or the call meets an automated menu, and presses only turnHandling.keypadAllowedKeys when set.
Update one Speko agent. Only the fields sent change. Never send endCall: the end_call tool is always on and {'enabled': false} is rejected with 400. To keep the agent from hanging up early, say so in systemPrompt.
Delete one Speko agent.
List tools registered on an agent, most recently created first. A call already in progress does not see a tool registered after it started. If a call reports an unknown tool, confirm it is listed here, then start a new call.
Register a new tool on an agent. A call already in progress will not see it; a new call will, once list_agent_tools shows it registered.
Get one agent tool by id, as currently stored in the registry. The row includes `simulation` when the tool overrides the default simulated-run policy; its absence means the default (every simulated run mocks the tool).
Update one agent tool's description, parameters, source, or simulated-run behaviour. A call already in progress keeps using the version it started with.
Delete one agent tool. A call already in progress may keep using it until the call ends; new calls stop seeing it immediately.
Deploy a SessionConfig as a new immutable agent version.
Roll an agent back to a historical version.
List versions for an agent.
Start an agent-to-agent test call. Puts the agent under test and a caller (a persona synthesized from `objective`, or another agent via `caller_agent_id`) in ONE LiveKit room with NO phone/SIP leg, so it cannot hairpin like dialing the agent's own number. Returns at once with a RUN id in status 'queued' - NOT a session id. Never pass the run id to a call or session tool. Review in THREE separately bounded phases, polling about every 5 seconds. (1) Session id: poll agents.test_call.get(agent_id, runId) until `run.result.testCall.agentSessionId` appears (once the call starts). RECORD that id the first time you see it: the worker REPLACES `run.result` on timeout, retry and abort, so a later read can lose the id while the session lives on. If `run.status` reaches passed, failed or incomplete with no id seen, the call never started - report what `run.result` says. If it reaches aborted (deadline or retries exhausted), the result was overwritten and a session may well exist, so recover it before reporting none: list agents.calls.list(agent_id, since=<dispatch time>) and read calls.get on each until `metadata.evalRunId` equals this runId and `metadata.runKind` is 'test_call'. Never just take the newest - another call on the same agent can start after your dispatch. Otherwise give up after ~24 checks. (2) Call: poll calls.get(agentSessionId) until it reports the call ended, then read sessions.transcript.get(agentSessionId). Bound THIS phase by the run's own deadline, ttl_seconds PLUS ~60s of worker startup headroom, never by a check count. (3) Recording: it finalizes AFTER the call ends, so KEEP polling calls.get for a FURTHER ~24 checks until `recording_status` reads 'ready', and only then call calls.recording.get(agentSessionId) - earlier it returns 404 by design. Stop at once if `recording_status` is failed, suppressed or discarded: that recording will never exist. Provide exactly one of objective / caller_agent_id / caller_system_prompt.
Read one agents.test_call run, to get its agentSessionId. agents.test_call answers with a run id and nothing else: the room and session are created by the worker afterwards, so `run.result.testCall.agentSessionId` - the id every review step needs - only appears here once the call has actually started. Poll this until it does. `run.status` is queued / running / passed / failed / aborted / incomplete, so this is also how you learn the call ended. `run.result` is NOT append-only. The worker overwrites it wholesale when a run times out, is requeued, or is abandoned after its retries, leaving only an error - so an agentSessionId that was published earlier DISAPPEARS from later reads even though the session, its transcript and its recording all still exist. Record the id the first time you see it; never treat its absence from a settled or requeued run as proof that no call ever ran.
Create a browser/WebRTC or server-to-server voice session.
Create an outbound phone session, dialing `body.to` from an owned or auto-provisioned number. Use maxDurationSeconds to set a hard call limit. ChatGPT and connector calls default to five minutes. Use sessions.end to hang up immediately when the call is stuck or the user wants to stop; sessions.get confirms that it ended. This tool has the highest error rate on this surface. Read the failure modes below before retrying. A rejected call returns one of these codes in the error body: AGENT_NOT_FOUND (agentId not in this workspace), PHONE_NUMBER_CONSENT_REQUIRED (the connector's phone-use authorization needs reacceptance), PHONE_NUMBER_KYB_ACCESS_SUSPENDED (compliance review pending, retrying will not help; check phone_numbers.kyb.get for status), PHONE_NUMBER_PROVISIONING_CREDITS_REQUIRED, _PENDING, and _FAILED (auto-provisioning a number failed or needs credit; see the returned balance and price fields), and INSUFFICIENT_CREDITS. A 2xx response does not mean the call was answered. No-answer, voicemail, and carrier rejection all settle normally and appear afterward in get_call or sessions.transcript.get, not as a tool error here. A 403 with code PHONE_NUMBER_SCOPE_REQUIRED means this connection predates phone calling and needs a full reconnect (a token refresh cannot add the permission). A 403 with code PHONE_NUMBER_CONSENT_REQUIRED means the workspace needs to accept the phone-use consent screen instead - no reconnect needed. `body.voice`, if set, is not validated against what the selected TTS provider owns before dialing. An unrecognized or foreign voice id can produce a dead-air leg with no error. Omit `voice` unless it came from this agent's own configuration; there is no voice-lookup tool on this profile. A Paxa voice speaks only on the models its GET /v1/voices entry lists in `models`; any other Paxa voice is replaced by a default one.
List sessions for the authenticated organization.
Get one session.
Get the ordered transcript and per-turn latency for a workspace session, including any tool calls made mid-call (toolCalls, by name and args) and a latencyStatus per turn ("partial"|"complete"|"interrupted"|"error"). Use this to confirm whether a keypad press (send_dtmf) was invoked, or whether a turn with no agent text and an "error" latency status points to a synthesis failure rather than the callee hanging up.
Get a signed recording URL for one session.
List recent calls for an agent.
Get call detail including transcript.
Get a signed recording URL for one call.
List phone and SIP numbers owned by the current workspace. For OAuth connectors, the first outbound call automatically provisions a dedicated number from workspace credits when none exists. Manual dashboard/API users keep the explicit declaration and purchase flow.
Search phone numbers available to buy. PRICES are per number and are the ones a purchase charges: ``upfrontCostUsd`` is the one-time setup and ``monthlyCostUsd`` the monthly rental, both debited from prepaid credits. Most numbers are $1 setup and $2/month; a number the carrier charges more for (``aboveStandardPrice: true``, e.g. a vanity toll-free number) costs the carrier's price and can only be bought by the owner in the dashboard. Quote them from the result, not from memory. Results with ``warm: true`` come first: Speko already holds them activated, so they can place calls the moment they are bought. Prefer one when the user will call right away. Searching is read-only, but it answers 403 until the workspace's business verification (KYB) is approved and number purchasing is enabled. The error names the step; the user completes it on the Phone numbers page, and retrying before then fails the same way. BUYING one does not follow from it. `phone_numbers.create` debits the workspace's prepaid credits (setup plus the first month; it is not a card payment or a checkout). OAuth connector users normally do not need this search: their first outbound call automatically purchases a dedicated US number after the phone authorization accepted at sign-in. Manual dashboard/API callers retain the declaration and purchase flow. Never collect or submit KYB fields in chat.
Buy (provision) a phone number. PAID — tell the owner the price and get their OK first. Check phone_numbers.list before buying: when the workspace already has a number, use it (link an agent with phone_numbers.update). A purchase while the workspace has a number is refused with 409 PHONE_NUMBER_ADDITIONAL_PURCHASE_UNCONFIRMED and the owned numbers; send confirmAdditionalNumber: true only after the owner agreed to another one. Setup is never refunded. The search result's upfrontCostUsd (setup) and monthlyCostUsd (rental) are the exact prices, $1 setup plus $2/month for most numbers, debited from prepaid workspace credits (setup plus the first month now). A number with aboveStandardPrice cannot be bought from here: it is refused with 403 PHONE_NUMBER_PRICE_REQUIRES_DASHBOARD and its quote, so tell the owner its price and that they can buy it on the Phone numbers page of the dashboard, or pick a standard-price number. The number renews monthly until it is released with phone_numbers.delete (or receptionist.cancel for a receptionist's number). Calls are billed at $0.09 per connected minute from prepaid credits.
Get one phone or SIP number owned by the current workspace. setupStatus.status is "ready" only when inboundReady, outboundReady and agentReady are all true; check the setupStatus.issues array for what is still missing. A number can also carry suspendedAt with a suspensionReason of "billing" or "compliance", which blocks calls independently of setupStatus. Use phone_numbers.list to find a number id first.
Update one phone number.
Release and delete one phone number.
Create a knowledge base.
List visible knowledge bases, optionally filtered to one agent with agent_id. Each entry carries documentCount and chunkCount as current totals, not a snapshot from creation. Use knowledge_bases.documents.list on a specific base to see why those counts are what they are.
Get one knowledge base owned by the current workspace, including its embeddingModel and current documentCount and chunkCount. Use knowledge_bases.list to find a base id first.
Delete one knowledge base.
List documents in a workspace knowledge base, each with an ingestion status of "pending", "processing", "ready" or "failed", and its own chunkCount. Use knowledge_bases.documents.get on a "failed" entry to read its errorMessage.
Create a knowledge document row and upload URL.
Get one document from a workspace knowledge base. status is one of "pending", "processing", "ready" or "failed"; errorMessage is set on "failed" and ingestedAt stays null until the document reaches "ready". Use knowledge_bases.documents.list to find a document id first.
Delete one knowledge document.
Finalize a knowledge document and enqueue ingestion.
List evals for an agent.
Create an eval for an agent.
Run one agent eval.
Get eval detail and recent runs.
List an agent's alert monitors — rules that watch an eval metric on scored production calls and notify when it crosses a threshold.
Create an alert monitor on an agent (fires when a metric crosses its threshold).
Update an alert monitor (threshold, channels, status, etc.).
Delete an alert monitor.
List a monitor's firing history (breach events + observed values).
List production calls scored by online monitoring (verdict + scores per call).
Inspect a voice-agent codebase and return migration recommendations.
Build a Speko SessionConfig draft from prose and hints.
Parse an external voice-agent config into a Speko SessionConfig draft. Output is a scaffold: verify it against the raw config and check `warnings` and `unmappable_tools` before creating anything.
Render briefing markdown for an agent/version.
Create a public share card for an agent build.
Synthesize speech from text, returning a download link and base64 audio. Routed across TTS providers by the intent. The result carries a signed download URL (valid 15 minutes) that can be shared or passed as `audio_url` to audio.transcribe, plus the audio as base64 with its content type and sample rate when the clip is small enough to embed. Served by the Speko Router when the request is one the Router's speech body can express, and by the Platform endpoint otherwise — a request naming `speed`, `instructions`, `spokenForm`, a bare `model` or more than one allowed provider keeps the knob it asked for rather than losing it.
Transcribe audio to text. Speech to text only: no audio is generated and none is returned. Google Drive's "can't scan for viruses" confirmation pages are refused as web pages; use a link that downloads the audio without a confirmation form.
Search the actions available to the current principal and MCP profile. An empty query (the default) returns everything visible, up to limit. Filter by effect to find only "read", "write", "destructive" and so on. Each result is a summary; pass its id to capabilities.describe for the full input and output schema.
Return the exact input schema, output schema and authorization policy for one action, by id. The natural second call after capabilities.search, when a summary is not enough to construct the arguments. Covers actions from capabilities.search only: a tool already in your tool list carries its own input schema, so do not describe it.
List recent durable action executions in the current workspace: the operations that run async rather than returning inline, in "claimed", "queued", "running", "succeeded", "failed" or "cancelled" status. Filter by actionId or status. Pass an id to operations.get for the full step-by-step detail.
Read one durable action operation by id, including its steps array (each with its own status and optional progress) and, once failed, an error object with a code, message and whether it is retryable. Poll this or use operations.wait to watch it settle.
Wait briefly for a durable operation to change or reach a terminal state, up to timeoutMs (0 to 30000, default 10000). Returns as soon as the operation moves, or once the timeout elapses, whichever comes first. Returns the same shape as operations.get either way.
Request cancellation of a cancellable operation. This is a request, not a guarantee: an operation past its cancellable point keeps running. Requires speko:execute, unlike the speko:read reads in this group. Check operations.get or operations.wait afterward to see whether it actually reached "cancelled".
List OAuth client grants and product API keys for the current workspace control plane. oauthGrants carry the scopes each connected client holds; apiKeys carry a revoked flag and lastUsedAt (null if never used). Pass an oauthGrants id to agent_access.oauth_grant.revoke to remove one. OAuth-user principals only.
Revoke one OAuth client grant for the current user and workspace. consentId is the id field from agent_access.overview's oauthGrants array, not the client id. Requires speko:credentials, a scope not used elsewhere on this surface.
List product API keys in the current workspace, each with its scopes, revocation state, and resourceVersion. resourceVersion is literally "active" or "revoked", not an incrementing version, so it stays valid for api_keys.revoke as long as the key remains active; there is no need to re-read it immediately before revoking.
Create a product API key and return its secret in `key` exactly once; it is never retrievable again, so store it immediately. `scopes` is 1 to 6 values from speko:read, write, execute, billing, credentials, compliance, and the key can only do what those scopes allow. `routing_policy`, if set, pins this key to a specific stt/llm/tts provider chain and objective instead of the workspace default; omit it to inherit the default. Available to any workspace member, unlike other credential actions on this surface which require an admin; granting compliance still requires an owner or admin.
Revoke one product API key. `expected_resource_version` must be "active", the only value an unrevoked key ever has; it does not need a fresh read from api_keys.list first, since the value cannot go stale while the key stays active. Concurrent revokes are safe: only one succeeds, the other is refused. Revocation cannot be undone.
Get one snapshot of the current workspace Gateway: session counts (total, last 24h, active), 24h error count, active key count, workload and instance counts, and settled spend in micros. Each figure has its own detail action when the summary number is not enough: gateway.workloads.list, gateway.instances.list, gateway.activity.list, gateway.usage.get.
List Gateway workloads in the current workspace, each identified by type and id, with its total and active session counts and when it was last seen.
List Gateway runtime instances in the current workspace, each with its active and pending session counts, capacity, and whether it is online, draining, or already offline. `draining: true` means the instance is finishing its current sessions and refusing new ones ahead of a planned shutdown. `offlineAt` is null while online and set once it drops off.
List recent Gateway activity events for the current workspace, newest first, optionally filtered to one workload, one instance, or one event_type. Each row carries the provider and session/attempt ids. `attribution`, when present, names which client made the request under a fixed evidence classification. A malformed attribution on an otherwise-valid event silently drops to undefined rather than failing the row, so an absent attribution does not mean the event is invalid.
Get aggregated Gateway usage for the current workspace, broken down by provider: spend in micros of the given currency, session count, and the metering source behind the number when more than one exists.
List legacy Runtime API keys still valid during the migration window, each with its expiry, revocation state, and resourceVersion. `legacyAcceptUntil`, when present, is the workspace-level deprecation cutoff date for these keys, set by the Runtime service rather than this action. gateway.legacy_keys.revoke needs the resourceVersion from this list as a concurrency token.
Revoke one legacy Runtime API key. `expected_resource_version` must match the key's current resourceVersion from gateway.legacy_keys.list; a stale value is refused rather than revoking blind, so read the key immediately before revoking. Revocation cannot be undone.
List Relay models with their provider, kind (stt, tts, llm, s2s, or evaluation), routability, and benchmark and ranking data for the given objective and language. Filtering by `language` narrows to models that actually serve it; omit both filters to see the full catalog and every language it covers. gateway.relay.tts_preview.create and other Relay-routed actions expect a (provider, model) pair taken from this list, not guessed; a preview speaks only the row's `default_voice`.
List recent Relay requests, newest first, each with its full attempt chain: every provider tried, in order, with an outcome of served, failed, or superseded. `superseded` marks an attempt that a later, faster attempt beat rather than one that errored, so counting it as a failure overstates the failover rate. `routing_mode` is auto when Relay chose the chain and explicit when the caller pinned it.
Get Relay usage summed over a bounded recent window (1 to 2160 hours, default 168, one week), with total requests, failures, and failovers, the same three broken out as a time series, and again split by kind and by model. Failures and failovers are reported as separate counters, not combined into one rate.
Get the Runtime credit snapshot Relay uses to admit new requests: available balance in micros, open reserved micros not yet settled, and unexported settled micros not yet reflected in the platform balance. `source` is "platform" when the balance comes from the platform ledger and "runtime_default" when Runtime is using its own fallback figure instead.
List Relay provider credentials for the current workspace without exposing stored key values; only the last four characters, if configured, are shown. `resource_version`, present when configured, is the concurrency token gateway.provider_credentials.replace and .delete require.
Create a Relay provider credential, or replace the existing one for that provider. `expected_resource_version` defaults to null, meaning no credential is expected to exist yet; set it to the current resource_version from gateway.provider_credentials.list to replace one that does, and the request is refused if the stored value has moved on since. The submitted `api_key` is never echoed back; only its last four characters appear afterward in gateway.provider_credentials.list.
Delete a Relay provider credential. `expected_resource_version` must match the current resource_version from gateway.provider_credentials.list; a stale value is refused rather than deleting blind, so read the credential immediately before deleting rather than reusing an earlier version.
List recent Gateway profiler conversations, each with its turn counts (total, evaluated, good) and good_turn_rate when evaluation has run, plus how it ended: hangup, transfer, error, shutdown, or unknown. gateway.profiler.conversations.get returns one conversation's full turn-by-turn detail; gateway.profiler.turn_trace.get goes one level deeper into a single turn.
Get one profiler conversation with every turn assembled: latency breakdown (ear-to-mouth, endpointing, LLM time to first token, tool time, TTS time to first audio), barge-in and overspeech timing, and a `confidence` of complete, degraded, or minimal reflecting how many of the expected markers actually arrived. `violations` and `missing_markers` name specifics when confidence is not complete. gateway.profiler.turn_trace.get returns the raw event trace behind one turn when this summary is not enough.
Get the raw, ordered event trace behind one profiler turn: every marker event with its type, sequence number, and millisecond timestamp, alongside the same turn summary gateway.profiler.conversations.get already returns. Use this when a turn's `confidence` is degraded or minimal and the summary alone does not explain which marker was missing or out of order.
Create a bounded Relay TTS preview and return a short-lived audio resource link. Pick the provider and model from a tts row of gateway.models.list. A preview speaks only that model's `default_voice`; any other voice, including the row's language-specific `voice`, is rejected. Omit `voice` and the default is used.
Duplicate an agent and its copyable tools, knowledge, evaluations, version, and graph. The copy carries the source voice and turnHandling settings forward unchanged. Check the returned warnings array. TOOL_AUTH_REQUIRED means a copied tool is registered on the new agent but needs its credentials or auth headers reconnected, so a duplicated agent can show a tool as present while it silently fails until that happens. WEBHOOK_AUTH_REQUIRED is a separate case: it applies to lifecycle webhooks (preCall, postCall, status, analysis, recording), not tools, and those are copied already disabled rather than left silently broken, so each one needs its auth fixed and then re-enabled by hand. INTEGRATION_DISCONNECTED and GRAPH_NOT_COPIED name the other two things that did not carry over.
Create an AI phone receptionist for a small business in one step, without deploying it. Reads the business website when website_url is given (public http/https pages only; a page that cannot be read is reported in website.status = "failed" and setup still completes from the notes). Builds the system prompt from mode and the facts, a short greeting that says it is an AI (and that the call is recorded when recording is on for the workspace), and a transfer to owner_phone; emergency_escalation also escalates to backup_phone when the owner does not answer. No knowledge base or search tool is created when there are no facts. Returns the new agent id, a preview of exactly what it will say and will never do, and missingFacts: the questions to ask the owner, whose answers go to receptionist.update_facts. The agent is NOT live: next call agents.test_call (a simulated call: an AI caller talks to the agent, no phone rings; review the transcript and recording), then receptionist.go_live. Repeating a call with the same idempotency key returns the first result instead of creating a second receptionist. Errors: RECEPTIONIST_RUNTIME_UNSUPPORTED (receptionists are not available on the call runtime of this workspace yet; nothing was created, contact support).
Correct or add business facts on a receptionist made by receptionist.setup, in plain text (for example "we close at 6 on Fridays, a haircut is $40"). The correction is applied to the stored facts, the prompt and greeting are rebuilt, and the same preview shape as setup is returned so the owner can check it again. Corrections are kept in order and the newest wins when two disagree. Returns RECEPTIONIST_NOT_FOUND (404) for an agent that was not made by receptionist.setup in this workspace. Does not deploy.
Read the authored execution graph stored for one agent version. Omit `version` for the agent latest. Returns graph:null when the version has no graph yet (not an error). Read this before agents.graph.replace so an edit starts from the current graph, never a guess.
Publish an authored execution graph as a NEW agent version. A graph change is a change to the agent, so it cuts a version the same way a config change does: the live config is carried forward unchanged and only the graph differs, which is what makes agents.rollback able to restore a previous workflow. WHOLE-OBJECT REPLACE — the graph you send becomes the graph, so read agents.graph.get first and send the full modified object, never a partial patch. Marks the graph source as "authored", which permanently protects it from the seeder. Rejects with INVALID_GRAPH plus the exact per-field reasons when the graph fails structural validation, and with VERSION_CONFLICT when a precondition failed — the message says whether the agent was published since you read it, the version you sent is not the live one, or the graph itself changed under you. Re-read with agents.graph.get and retry rather than forcing. A graph that saves can still come back with `warnings` (code GRAPH_LINT): legal shapes the validator flags, each naming the node, the edge and the fix. The graph IS saved; read them and replace again if the fix applies.
Derive a first execution graph for an agent version from its system prompt (falling back to the version derived flow). Idempotent: an existing graph is returned unchanged with status "exists". `force` re-derives ONLY over a previously seeded graph — an authored or healed graph is never clobbered. Use this to bootstrap, then agents.graph.replace to edit. `warnings` carries GRAPH_STALL_RISK when a fresh seed can hang, and GRAPH_LINT for validator findings on the graph returned, fresh or existing.
Partially update one test case. Only the fields you send are written; omitted fields are left untouched. `description` and `source_call_id` accept null to clear them. Sending `input_payload` replaces it wholesale, so read the case first if you mean to keep its other keys — to change only the caller character use agents.evals.character.set instead.
Set or clear JUST the simulated-caller character on one test case. Merged into the existing input_payload without touching its other keys — this is the safe way to change a persona. Pass null to clear it back to a clean caller.
Delete one test case. Historical runs survive — the run rows reference the case through a nullable column, so the track record is not destroyed with the case.
The five most recent runs of ONE test case, newest first, each with the verdict in its result. Poll this after agents.evals.run to watch a case settle.
Author test cases for an agent from its own configuration — system prompt, intent, tools, knowledge-base titles and first message — using the scenario generator. Returns the generated cases WITHOUT saving by default; pass persist:true to also insert them as test cases. Calls an LLM, so it is slower than the other actions here.
Pass-rate over time for one agent, as a contiguous zero-filled series plus window totals. Manual test calls are excluded — they are hardcoded-pass and would inflate the rate. Out-of-range `days` clamps rather than erroring.
Ask for a minimal system-prompt revision that would make a FAILED run pass. The failures are extracted from the run itself, so you only need the run id. Read-only — it proposes, it does not deploy; pass the result to agents.apply_prompt_fix to ship it. Returns status "no_fix_needed" when the run has no failures.
Ask for a minimal system-prompt revision from failures you supply yourself, when the failures did not come from one stored run. Prefer agents.evals.runs.suggest_fix when you have a run id. Read-only — it proposes, it does not deploy.
Deploy a revised system prompt as a NEW agent version. Takes the current live config (or the agent as it stands, if it was never deployed), swaps the prompt, and deploys. No re-test runs automatically; re-run the Test Set to check the fix. Send the FULL prompt, not a diff.
Re-pin ONE pipeline layer (stt, llm or tts) to a different provider and deploy that as a new agent version — the fix that is not a prompt change. Every other layer and config key is carried forward. Like agents.apply_prompt_fix, no re-test runs automatically.
Read the agent's LIVE system prompt back as a structured decision view — its goals, the rules it follows, and its conditional branches, plus a best-effort flow. Derived on demand, not stored. Returns status "empty" when the agent has no prompt yet.
List the organization scenario library, newest first. A scenario is one thing a caller is trying to do, reusable across agents. Pass `agent_id` to list only the scenarios attached to that agent.
Author a scenario into the organization library and optionally attach it to agents. Deduped by content: an identical scenario already in the org (for example one the reliability gate derived) is reused and only the attachments are added, so re-running this never creates a twin. Scenario content is FROZEN at creation — there is no update action by design; evolve a scenario by creating a new one and calling scenarios.archive on the old one with superseded_by.
Attach a library scenario to an agent so the reliability gate drives it there. Supply `persona_id` to pin the caller character this agent runs the scenario with (persona x scenario compose); pass null to clear it; omit it to leave an existing pin unchanged.
Detach a library scenario from one agent. The scenario itself and its history survive — only the agent link is removed.
Archive a scenario — the only lifecycle transition there is. An archived scenario stops being driven by the gate but keeps its run history. Pass `superseded_by` with the id of the scenario that replaces it to record the lineage; the replacement must already exist in this organization and cannot be the scenario itself.
List up to 50 recent reliability runs that exercised this scenario, each with the per-scenario verdict it produced — the scenario track record across agent versions. Only runs written after the gate began stamping scenarioId carry the link, so older runs will not appear.
Create a short-lived Stripe Checkout handoff for adding workspace credits. This is a ONE-TIME purchase: the owner pays amount_usd once and nothing renews; tell them the amount before creating the link. Returns a url, its purpose, and an expiresAt timestamp; the caller opens the url to complete payment, this action does not itself move any money. amount_usd is a whole dollar figure between 5 and 10,000. Requires a human OAuth principal, not an API key.
Create a short-lived Stripe Billing Portal handoff for the workspace. Returns a url, its purpose, and an expiresAt timestamp; the portal itself lets the account owner manage payment methods and view invoices on Stripe. Requires a human OAuth principal, not an API key.
Create a short-lived Stripe handoff for authorizing an auto-top-up card. Returns a url, its purpose, and an expiresAt timestamp; completing it on Stripe attaches a payment method without itself enabling auto-top-up. Call billing.auto_topup.update afterward with enabled true plus a threshold and amount to turn charging on. Once enabled that is a RECURRING charge: the card is charged the top-up amount every time the balance drops below the threshold, until billing.auto_topup.update sets enabled false. Tell the owner before starting. Requires a human OAuth principal, not an API key.
List recent Stripe invoices for the current workspace, newest first. limit caps the page size at 100 and defaults to 25. Each invoice reports amountPaid and amountDue in the smallest currency unit, plus nullable hostedInvoiceUrl and invoicePdf links once Stripe has generated them.
Get the current automatic credit top-up configuration and payment-method status. hasPaymentMethod and the nested paymentMethod fields cover the card Stripe has on file, separate from whether auto-top-up is enabled. A nonzero failureCount with pausedAt and lastFailureReason set means a top-up attempt failed and charging is paused. Call billing.auto_topup.resume to clear that once the underlying issue is fixed, or billing.auto_topup.update to reconfigure a new threshold or amount.
Replace the automatic credit top-up configuration for the workspace. This is a whole-object replace, not a patch. enabled, thresholdCents, and amountCents must all be sent together even to change only one of them, and thresholdCents must be strictly less than amountCents or the call is rejected. With enabled true this arms RECURRING card charges: amountCents is charged every time the balance drops below thresholdCents, with no end date, until enabled is set false. Tell the owner the amount and threshold and get their OK first. Requires a human OAuth principal, not an API key.
Clear a paused auto-top-up failure state so charging may resume, without changing the threshold or amount on file. Check billing.auto_topup.get first to confirm pausedAt is actually set; calling this when auto-top-up was never paused is a no-op. Requires a human OAuth principal, not an API key.
Get the workspace monthly spending cap and how much of it this month has used. monthlyCapUsd is null when no cap is set. While reached is true, new sessions, outbound calls and test calls are refused with SPENDING_CAP_REACHED and inbound calls go unanswered instead of being charged; calls already in progress are not cut. Spend is managed usage charged in the current UTC calendar month and resets at resetsAt.
Set the workspace monthly spending cap in whole US dollars (1 to 100,000), or null to remove it. Takes effect immediately: if this month has already spent the new cap, new sessions and calls stop until the month resets or the cap is raised. Requires a human OAuth principal, not an API key.
List the integrations this workspace can use (calendars, CRMs) with each one's connected installations. connectable is true when integrations.connect can start an OAuth connection for the app; tool_modes lists the modes agents.integration_tools.attach accepts for it. An app with an empty installations array is not connected yet. Installation status "disconnected" or "error" means the owner has to connect it again before an agent can use it. On the Muse surface only Google Calendar and HighLevel are listed.
Start connecting an OAuth integration (e.g. Google Calendar, HighLevel). Returns a short-lived url (ten minutes, single use) to a Speko page that names this workspace and asks the account owner to confirm; Continue takes them to the provider to sign in and grant access, Cancel ends the link. Nothing is connected until they finish there. The browser does not need a Speko session. Give the url only to the owner of this workspace. Afterwards integrations.list shows the new installation, and agents.integration_tools.attach gives an agent its tools. Errors: INTEGRATION_APP_NOT_FOUND (unknown or not offered on this surface), INTEGRATION_NOT_OAUTH (configured in the dashboard instead), OAUTH_NOT_CONFIGURED (this deployment cannot connect the app). Requires a human OAuth principal, not an API key.
Give an agent the tools of a connected integration installation. Replaces that app's integration tools on the agent with the set for mode, so calling it again switches modes cleanly; other tools are untouched. Modes: availability_only (only checks free times, books nothing; the owner confirms every booking), booking_requests (auto-booking: the agent checks availability and creates new appointments by itself; HighLevel also creates a new contact for the caller and logs a note; it never looks up, edits or cancels an existing contact or appointment), all_actions (every action the app offers, with no booking guardrails). The default is availability_only where the app supports it; use booking_requests only when the owner explicitly asks the agent to book by itself. Booking tools are described so the agent checks availability before offering any time and never invents one; guardrail_prompt is the matching system-prompt section. For a receptionist (receptionist.setup) the agent's instructions are rebuilt to match the new tools and instructions_updated is true; detach rebuilds them again. calendar_id pins which calendar the tools use: optional for Google (defaults to the primary calendar), required for HighLevel booking modes unless the sub-account has exactly one calendar, in which case it is picked automatically; CALENDAR_CHOICE_REQUIRED lists the choices. pipeline_id (and optional pipeline_stage_id) adds HighLevel opportunity creation. Changes apply to the next call. Errors: AGENT_NOT_FOUND, INTEGRATION_NOT_FOUND, INTEGRATION_DISCONNECTED (connect it again with integrations.connect), INTEGRATION_MODE_UNSUPPORTED, CALENDAR_CHOICE_REQUIRED, TOOL_NAME_TAKEN (the agent already has a non-integration tool with the same name), RECEPTIONIST_UPDATE_FAILED (tools changed but the receptionist's instructions did not; call again), CALENDAR_ALREADY_ATTACHED (a receptionist uses one calendar app; detach the current one first).
Remove an integration app's tools from an agent (only tools of kind integration for that app; webhook and other tools are never touched). The installation stays connected for other agents. Returns the removed tool names; an empty list means the agent had none. Changes apply to the next call.
Read this workspace's phone compliance status. OAuth connector workspaces submit automatically from the phone authorization accepted during sign-in; do not collect or submit declaration fields in chat. `submissionMode` identifies automatic OAuth, manual dashboard/API, or grandfathered migration handling.
Put an agent live as the business’s phone receptionist in one call: get a number (buy a new US/Canadian number, optionally by area code, or reuse one the workspace owns), link the agent to it for inbound calls in the same database write, enable outbound calling from it, deploy the agent, and return carrier-specific call-forwarding codes plus the exact codes that undo them. A purchased number always has the agent attached; there is no state in which it exists without one. PRICE — tell the owner and get their OK before calling with number_source "new": a new number costs $1 setup plus $2/month, debited from prepaid workspace credits (setup plus the first month at purchase); a named e164 the carrier prices higher is refused with PHONE_NUMBER_PRICE_CONFIRMATION_REQUIRED. It renews monthly until released with receptionist.cancel or phone_numbers.delete; pausing keeps the number and keeps billing it. Calls are billed at $0.09 per connected minute from prepaid credits. number_source "existing" buys nothing. status "action_required" means nothing was bought or changed and requiredAction says what the owner must do: PHONE_NUMBER_KYB_REQUIRED / _ATTESTATION_REQUIRED / _REJECTED / _REVOKED / _REVIEW_HOLD (business verification, with a link), BUY_PHONE_NUMBERS_DISABLED_FOR_ORG, or INSUFFICIENT_BALANCE (add credits with billing.checkout.create, then call again). Errors: AGENT_NOT_FOUND, AGENT_ALREADY_HAS_NUMBER (the agent already answers another number; pass that number as phone_number_id), PHONE_NUMBER_IN_USE (the chosen number answers to another agent or a person), NO_NUMBERS_AVAILABLE (try another area code), DEPLOY_BLOCKED, KEPT_NUMBER_IN_USE (the kept number of a paused receptionist now answers another agent or a person; pass a number explicitly), RECEPTIONIST_RUNTIME_UNSUPPORTED (receptionists are not available on the call runtime of this workspace yet; nothing was bought or deployed). Next: receptionist.verify_forwarding once the owner has dialed the forwarding code.
Check that calls to the owner’s business number actually reach the receptionist. Speko cannot place a test call into a carrier’s forwarding, so this is a two-step check: the first call (or restart: true) opens a 15-minute check window and returns status "waiting_for_test_call" with what the owner should do — call the business number from a DIFFERENT phone and, in overflow mode, let it ring out. Calling again reports what arrived on the receptionist number since the window opened: "verified" (a forwarded call reached the agent; evidence.forwardedFrom matches the business number), "probably_verified" (a call reached the agent but the carrier did not say it was forwarded), or "not_seen" (nothing yet; check the forwarding code or wait for the rings to run out). Errors: RECEPTIONIST_NOT_FOUND (run receptionist.go_live first), OWNER_NUMBER_REQUIRED (set owner_business_number via receptionist.settings.update).
Instant off switch: the receptionist stops answering its number right away. The number is kept (and still billed monthly) so receptionist.resume brings it back exactly as it was. While paused, calls that reach the Speko number are not answered, so the owner should also undo carrier forwarding (the returned undoForwarding) if callers should only ring the business phone. Safe to repeat.
Turn a paused receptionist back on: re-links the agent to its kept number so it answers again. Safe to repeat. Errors: RECEPTIONIST_NOT_FOUND, PHONE_NUMBER_IN_USE (someone linked the number to another agent or a person while paused), PHONE_NUMBER_GONE (the number was released; run receptionist.go_live).
Change a receptionist’s owner settings: who gets the post-call summary (email, text), privacy mode (link only, no caller details), per-agent recording, and the owner’s business line and forwarding mode. Partial update: omitted fields keep their value; null clears. Returns the settings and, when a business line is known, fresh forwarding codes. Errors: RECEPTIONIST_NOT_FOUND.
Cancel the receptionist so the owner stops paying for it, in one approval: it stops answering, the number go_live bought for it is released (which ends the monthly number charge; a released number cannot be recovered), and the result says how to undo carrier forwarding on the business phone. The agent itself is kept; delete it with agents.delete if it is no longer wanted. A number the owner brought (an existing workspace number or an imported number), or one given to another agent or a person while paused, is never released: it is only unlinked and reported in keptNumber. Errors: NOT_FOUND (no receptionist for this agent), PHONE_NUMBER_RELEASE_FAILED (the number could not be released; nothing was charged and the receptionist stays paused, so retry).
Read this workspace's static-IP SIP gateway setup: connection details (gateway IP, SIP domain/realm, ports), the SIP login username and auth mode (never its password), the numbers routed through the gateway, numbers awaiting ownership verification by a test call, and the PBX outbound calls from gateway numbers go through (host, port, login; never its password). The SIP gateway is only for numbers that sit behind the customer's own PBX or phone system. To use a number from a carrier or VoIP provider (Twilio, Telnyx, Vonage, a SIP trunk provider), no access request, SIP gateway or support ticket is needed: connect the carrier with phone_numbers.sip_connections.create, then attach the number with phone_numbers.import (or the dashboard: Phone numbers > Import number > Add carrier connection; Twilio numbers can use Connect Twilio there). Requires SIP gateway access on the workspace. Logins and numbers are managed only in the dashboard; the outbound PBX only by Speko staff.
Connect the customer's own carrier or SIP trunk (Telnyx, Twilio, Odorik or any VoIP provider) as a carrier connection. This is how a customer brings their own number: create the connection here, then attach the number with phone_numbers.import. Self-serve: no static-IP SIP gateway (a separate, gated feature) and no support ticket; same as Phone Numbers → Import number → Add carrier connection in the dashboard. host is the carrier SIP domain or IP without sip:// or a port; port defaults to 5060 and transport to udp. At least one of allow_inbound and allow_outbound must be true, and a number imported later can only use the directions enabled here. auth_username and auth_password are the SIP digest login the carrier issued; auth_password is a secret, stored encrypted and never returned by any tool (has_auth_password says whether one is set); do not repeat it in replies. Outbound calls send the number in E.164 as the SIP From user and the destination in E.164; a carrier that identifies the account by the From user (voip.ms) needs from_user set to that account, usually with caller_id_header "p-asserted-identity" so the number still reaches it, and dial_format "e164_without_plus" when it wants digits without the plus; caller_display_name is the name callees see instead of from_user. Inbound calls are challenged for auth_username/auth_password by default, so allow_inbound on a generic carrier needs both unless inbound_auth is "ip_allowlist"; a carrier that cannot answer a digest challenge (voip.ms SIP URI routing) needs inbound_auth "ip_allowlist" with its signaling IPs in inbound_allowed_addresses. The carrier decides which destinations and caller IDs it accepts. Calls over a connection like this carry no Speko telephony fee and the number no monthly fee; only the per-minute agent rate applies. Creating a connection places no call and charges nothing. Existing connections are sip_connection installations in integrations.list. Errors: ACTION_INPUT_INVALID with the failing field.
Import a number the customer already owns at their own carrier, routed over a carrier connection from phone_numbers.sip_connections.create. Self-serve: no SIP gateway, ownership test call or support ticket is involved, nothing is bought, and the number carries no monthly fee (its nextChargeAt is never charged); calls over it bill only the per-minute agent rate, with no Speko telephony fee. direction (inbound, outbound or both; default both) must be enabled on the connection. Outbound calls from the number go through the carrier, which decides allowed destinations and whether it accepts the number as caller ID. agent_id links the agent that answers inbound calls. For inbound, the carrier must also send calls for the number to Speko: the result setupStatus.forwardingSipUri is the SIP URI to give the carrier, and setupStatus.issues lists anything still missing (missing_inbound_dispatch_config means no agent_id yet). Change the agent or label later with phone_numbers.update; release it with phone_numbers.delete, which leaves the number at the carrier. Errors: SIP_CONNECTION_NOT_FOUND (unknown or disconnected connection id), SIP_DIRECTION_NOT_ALLOWED (the connection does not allow that direction), NUMBER_ALREADY_EXISTS (the number is already registered in Speko), LIVEKIT_TRUNK_SYNC_FAILED (the SIP routing could not be built; check host, port and transport, then retry).
Immediately request hang-up of a live voice or outbound phone session in this workspace. Use this when a call is stuck, no longer useful, or the user asks to stop it. Safe to repeat. Status ending means hang-up was requested; check sessions.get to confirm the session ended. Already ended sessions return already_ended.
Open a support ticket with the Speko team on behalf of the signed-in user. Only for problems with Speko itself (calls, agents, phone numbers, billing, the dashboard or API); never for problems with the assistant, its app or any other product, which Speko cannot see or fix. Not for turning on the SIP gateway to bring the user's own carrier or number: that needs no enablement (dashboard Phone Numbers → Import number, or phone_numbers.sip_connections.create then phone_numbers.import where available). Before calling, show the user the exact summary and details you will send and get their explicit yes; never file a ticket the user did not confirm. Each ticket is emailed to the user and read by the Speko team, so leave out personal, legal, medical or financial details that the Speko problem does not need. One problem, one ticket: add new information to an open ticket with support.ticket.reply instead of opening another. summary is one line (3-200 characters); details is the full description (up to 4,000 characters). category is billing, calls, setup or other (default other). related optionally carries ids the team should look at: sessionId, callId, agentId, phoneNumber. Ids only: recordings and transcripts are never attached. Returns ticketId, expectedResponse and ticketUrl, the dashboard page where the user can follow the conversation; share that link with the user. The user also receives a confirmation email. The team replies on the ticket: read replies with support.ticket.get and answer with support.ticket.reply. Returns 429 SUPPORT_TICKET_RATE_LIMITED after too many tickets in a short time; the error says when to retry, or names the tickets the organization just opened: reply on one of those instead. A ticket is stored even when the team notification is delayed, so a success never needs a resend.
List the organization's support tickets, most recent activity first. status filters to open (waiting on Speko), answered (Speko replied last), closed, or all (default). limit is 1-100 (default 50). stage is where the ticket stands for the user: sent, in_review (the team opened it), replied or resolved. Each ticket carries unreadReplies, the number of Speko replies the user has not read yet, and ticketUrl, the dashboard page for it. Read a conversation with support.ticket.get. An organization with no tickets returns an empty list.
Read one support ticket and its conversation. ticket carries the original summary and details the user sent; messages holds every reply after that, oldest first, each with author customer or team (the Speko support team). timeline holds when the latest message was sent and, when they happened since, when the team reviewed it, replied and resolved the ticket. rating is the rating given with support.ticket.rate, null until then. Reading marks the team replies as read, so unreadReplies drops to 0. Answer with support.ticket.reply. Returns 404 SUPPORT_TICKET_NOT_FOUND for an id outside this organization.
Add a message from the user to a support ticket (1-4,000 characters). The Speko team sees it in the same thread, and the ticket moves to open (waiting on Speko); a closed ticket is reopened. Use it to answer a question the team asked or to add details, instead of opening a new ticket. Ids only: never paste recordings, transcripts or secrets. Returns 404 SUPPORT_TICKET_NOT_FOUND for an id outside this organization and 429 SUPPORT_TICKET_RATE_LIMITED after too many messages in a short time.
Rate how the Speko team handled a resolved support ticket. score is 1-5 (1 very poor, 5 excellent); comment is optional (up to 2,000 characters) and says what could have been better. Rate from what the user told you, or from what you saw while working the ticket with them; if you are not sure, ask the user. A rating is for a resolved ticket only: returns 409 SUPPORT_TICKET_NOT_RESOLVED while the ticket is open. Rating again replaces the earlier rating. Returns 404 SUPPORT_TICKET_NOT_FOUND for an id outside this organization.
List SMS threads in this workspace, most recent activity first. A thread is one contact (remote_phone_number) texting one workspace number (phone_number_id). assigned_agent_id on each thread is the agent handling it now: the thread override (agent_override_id) when set, otherwise the agent on its number. Filter agent_id is an agent inbox: threads that agent handles now plus threads it ever sent or received a message in, so a thread can appear for an agent whose number has since moved to another agent (assigned_agent_id then names the new one). Filter assigned_agent_id matches only threads the agent handles now. An empty data array is a normal result. next_cursor is null on the last page; pass it back as cursor for the next one. Read a thread with sms.conversations.messages.list.
Get one SMS thread. assigned_agent_id is the agent handling it now (agent_override_id when set, otherwise the agent on its number; null means no agent and no auto-replies). automation_status "paused" means a person took the thread over. Returns CONVERSATION_NOT_FOUND (404) for an id outside this workspace.
Read the messages in one SMS thread. Each page is in chronological order (oldest first) and holds the newest messages not yet returned; next_cursor pages back to older messages and is null once the start of the thread is reached. agent_id on a message is the agent that handled the thread when it was written, which can differ from the thread's current agent. origin tells a person (dashboard, api) from an agent (agent_auto_reply, agent_tool) and the contact (telnyx). text is null after the thread was redacted. Returns CONVERSATION_NOT_FOUND (404) for an id outside this workspace.
Partially update one SMS thread: only the fields sent are written. assigned_agent_id pins an agent to this thread; null clears the pin so the thread follows the agent on its number again. automation_status "enabled" lets the handling agent auto-reply to inbound texts, "paused" records a human takeover, "disabled" turns replies off. INVALID_ASSIGNEE (422) means the agent or user is not in this workspace; CONVERSATION_NOT_FOUND (404) means the thread is not; SMS_THREAD_AGENT_CHANGED (409) means expected_agent_id no longer handles it.
Queue a text in an existing SMS thread, from the thread number to its contact. When the contact texted within the last 24 hours the reply is conversational: no consent record or timezone is needed. Otherwise it is proactive and needs an active consent record (CONSENT_REQUIRED, 422) and a recipient timezone (RECIPIENT_TIMEZONE_REQUIRED, 422). The result has status "queued", or "scheduled" with effective_send_at at the next allowed time when the message falls inside quiet hours. A queued reply is checked again just before submission and can still be deferred by quiet hours or throughput limits, or rejected, so "queued" does not mean sent; sms.messages.get reports whether it was sent and delivered. Sending requires an approved 10DLC registration (organization.get features.smsSending). Refusals: SMS_SENDING_NOT_APPROVED (403, no number in the workspace is on an approved campaign yet; register in the dashboard under Settings > SMS compliance), CAMPAIGN_BILLING_SUSPENDED (402, the campaign fee is unpaid), PHONE_NOT_FOUND (404), PHONE_NOT_SMS_READY / CAMPAIGN_NOT_READY / MESSAGING_PROFILE_NOT_READY (409, this number is not ready on the approved campaign), RECIPIENT_SUPPRESSED (409, the contact texted STOP; nothing can be sent until they opt back in), INSUFFICIENT_CREDITS (402), IDEMPOTENCY_CONFLICT (409). Each distinct message needs its own idempotency_key; a retry with the same key and input returns the original message instead of sending twice.
List SMS messages across the workspace, newest first. Filter agent_id returns messages written while that agent handled the thread. origin tells a person (dashboard, api) from an agent (agent_auto_reply, agent_tool) and the contact (telnyx). An empty data array is a normal result; next_cursor is null on the last page. For one thread in reading order use sms.conversations.messages.list.
Get one SMS message and its delivery state. status moves queued or scheduled -> submitting -> accepted -> sent -> delivered; delivery_failed and rejected carry error.code and error.detail from the carrier; delivery_unconfirmed means the carrier never confirmed and is not a failure. Returns MESSAGE_NOT_FOUND (404) for an id outside this workspace.
Send a proactive text from a workspace number to a contact, starting a thread or adding to the existing one for that number and contact. Proactive sends always need an active consent record for the contact on the number campaign (CONSENT_REQUIRED, 422; consent_id picks a specific record) and a recipient timezone from recipient_timezone, the consent record, the thread, or the workspace default (RECIPIENT_TIMEZONE_REQUIRED, 422). A send requested inside quiet hours is not refused: it returns status "scheduled" with effective_send_at at the next allowed time. To answer a contact who texted in the last 24 hours without consent, sms.conversations.reply applies. Sending requires an approved 10DLC registration (organization.get features.smsSending). Refusals: SMS_SENDING_NOT_APPROVED (403, no number in the workspace is on an approved campaign yet; register in the dashboard under Settings > SMS compliance), CAMPAIGN_BILLING_SUSPENDED (402, the campaign fee is unpaid), PHONE_NOT_FOUND (404), PHONE_NOT_SMS_READY / CAMPAIGN_NOT_READY / MESSAGING_PROFILE_NOT_READY (409, this number is not ready on the approved campaign), RECIPIENT_SUPPRESSED (409, the contact texted STOP; nothing can be sent until they opt back in), INSUFFICIENT_CREDITS (402), IDEMPOTENCY_CONFLICT (409). SMS_THREAD_AGENT_CHANGED (409) means expected_agent_id does not handle the thread. Each distinct message needs its own idempotency_key; a retry with the same key and input returns the original message instead of sending twice. Delivery progress is on sms.messages.get.
List the workspace's custom (cloned) voices. Each voice lists its provisions: one per TTS provider it was cloned onto. A provision with status "ready" carries provider_voice_id, which is the voice id to pass as voice to audio.synthesize (pinned to that provider with constraints.allowedProviders.tts) or to an agent. voices.list shows only catalog voices, never these. An empty list means no voice was cloned yet; create one with voice_clones.create.
Clone a voice from a recording, in one call: creates the voice, downloads audio_url as its reference clip and clones it onto provider (default elevenlabs). It is the same as Voices → Create voice in the dashboard. The result is the voice with its provision; provisions[0].provider_voice_id is the id to use. To speak with it, call audio.synthesize with voice set to that id and constraints.allowedProviders.tts set to [provider]: a cloned voice exists only on the provider it was cloned onto, and any other provider refuses the id. Cloning itself is free; speech with the voice is billed at the normal rate of the provider. consent_confirmed must be true: only clone the user's own voice or a voice whose speaker agreed. Cloning usually takes a few seconds, up to a minute. If any step fails the voice is removed again, so the same call can be retried. Errors: VOICE_CLONE_NAME_TAKEN (pick another name, or reuse the existing voice from voice_clones.list), VOICE_CLIP_FETCH_FAILED (audio_url is not a public link that returns audio; share links that open a web page do not work), VOICE_CLIP_TOO_LARGE (over 25 MB), VOICE_CLIP_UNSUPPORTED_TYPE, VOICE_CLONE_MISSING_TRANSCRIPT (csm without transcription), VOICE_CLONE_PROVISION_FAILED (the provider rejected the audio; the message says why).
Clone an existing custom voice onto one more TTS provider, using the reference clip it already has (for example a voice made on elevenlabs, now also on cartesia). Returns the voice with all its provisions; the new one carries provider_voice_id for audio.synthesize, pinned to that provider. Errors: VOICE_CLONE_ALREADY_PROVISIONED (the voice is already on that provider; its id is in voice_clones.list), VOICE_CLONE_NO_READY_CLIPS, VOICE_CLONE_MISSING_TRANSCRIPT, VOICE_CLONE_PROVISION_FAILED (the provider rejected the audio).
Delete a custom voice: removes its reference clips and deletes the cloned voice at every provider it was provisioned to. Agents or requests that still use its provider voice id stop working with that voice. Cannot be undone.
Search bundled Speko docs. Returns slug, title, score, snippet. Use this to look up SDK usage, API request/body shapes, and migration steps without leaving the MCP session. For example, after a Speko API validation error, search the failing field name to find the correct shape. Each hit's `slug` can be opened as the `spekoai://docs/{slug}` resource; `spekoai://docs/index` lists every bundled doc.
List the Speko TTS voice catalog: voices (vendor, id, name) plus TTS providers with their models. Use a returned voice id as the `voice` field on agents.create or POST /v1/sessions bodies. ElevenLabs voices are not in this list (its provider entry has voicesFetchedLive: true); they are in the voice index, GET /v1/tts/voices?vendor=elevenlabs.
Search every vendor's TTS voices that Speko can route, including ElevenLabs on a Speko-managed key. Each result carries `use_with` ({provider, model, voice}): pass `voice` as audio.synthesize's `voice` and pin the provider in constraints.allowedProviders.tts. Results are paginated; follow `next_cursor`.
List the STT/LLM/TTS/S2S provider and model catalog. Each entry's `id` ('vendor' or 'vendor:model') is the literal string accepted by `allowedProviders` pins in agent and session configs; `benchmarked` marks entries with live Speko benchmark scores. STT and TTS entries carry `priced`: on audio.synthesize and audio.transcribe, a `priced: false` model is skipped on Speko-managed keys (the request fails if no priced model is left) and works only with the caller's own provider key, so pin a `priced: true` model for managed usage.
Get ready-to-paste Speko integration code for a web voice call. Returns correct, compilable code for the canonical Speko runtime flow: the app's server mints a session via POST /v1/sessions with the secret SPEKO_API_KEY, then the browser connects with @spekoai/client's VoiceConversation.create using the returned short-lived transportToken and transportUrl. Use this INSTEAD of guessing Speko API shapes when generating app code. Note: generated apps cannot call MCP tools at runtime - runtime integration is exactly this code plus a SPEKO_API_KEY environment variable.
One endpoint, the same key, whichever client you use.
~/Library/Application Support/Claude/claude_desktop_config.json (Mac) · %APPDATA%\Claude\claude_desktop_config.json (Windows)
Replace API_KEY with your own key.
Already have an "mcpServers" section in your config? Just add the server entry inside it.
Discovery, routing, credentials, tool scoping and execution logs all happen at the gateway→connections stay ACTIVE with no work from you
Speko MCP runs through a gateway that holds the credentials, scopes the access and records every call.
Managed auth, hosted MCP servers, and every Speko MCP tool your agent needs.
Free to start.