Mint a key
- Open Settings → Integrations → API Call Upload and click Create API key.
- Give it a descriptive name (e.g.
prod-twilio-uploader). - Copy the key once — Zelto only stores a hash.
owner or admin
role — a key reads every conversation in the
organization, so it isn’t a member-level credential.
Set up with a coding agent
Don’t want to wire this up by hand? Paste the prompt below into the coding agent in your editor — Claude Code, Cursor, Codex, or similar. It points the agent at this guide and the API reference, spells out the full upload contract, and has it send each finished conversation following your own codebase’s conventions. You still supply the key: mint one above and expose it asZELTO_API_KEY — the
prompt tells the agent to read it from the environment, never hardcode
it.
Paste into your coding agent
Upload a conversation
POST the whole conversation — agent, transcript, optional recording, and metadata — in one request to/webhooks/calls. Zelto acknowledges
immediately and processes it into a conversation and transcript in the
background. The API Reference tab covers the REST conventions and has a request
playground for verifying a payload before you wire up production.
The example below is a voice call; a text conversation is the
same shape with the audio fields left out.
call.json
200 with { "received": true }.
Only call.externalId and an agent reference are required; everything
else is optional. Provide either agent.externalId (Zelto finds or
creates the agent for you) or agentId to attach the call to an agent
you already created in the dashboard. Include call.recordingUrl and Zelto
re-hosts the audio so the player keeps working past your source’s recording TTL.
Set version to label the agent build or prompt revision behind the call
(e.g. "v3"); Zelto tracks it as the call’s agent version so you can compare
performance across versions on the agent’s page. It’s optional and works with
either agent reference — send the same string for calls on the same build.
Agent groups
Sendagent.groups to organize agents that belong together — for example the
several agents you run for one customer, or one campaign. Each name is a group
(a company, area, or campaign); Zelto finds-or-creates it in your organization
and adds the agent to it. An agent can belong to more than one group, and the
field is additive — a later upload never removes a group the agent already has,
so it’s safe to send on every call. Once an agent is in a group you can filter
the whole dashboard by that group from the sidebar, and filter the
conversations list by group. You can also manage an
agent’s groups by hand on its settings page in the dashboard.
Tool calls
If your agent invokes tools mid-call (function calls — check availability, book an appointment, look up an order), forward each one as atool turn carrying a
structured toolCall. Zelto shows tool calls inline in the transcript, records
them as queryable tool-call traces, and feeds
the structured call — name, arguments, and result — into its AI analysis
(summaries, findings, scorecards, metrics). So you send the tool call once,
as structured data; you do not need to also hand-render it into the turn’s
content.
A toolCall is { name, arguments?, result?, status?, toolCallId?, errorMessage? } — only name is required. arguments and result are any
JSON; status is success, error, or pending; toolCallId correlates an
invocation with its result if you send them as separate turns. The turn’s
content may be empty when a toolCall is present (Zelto labels it with the
tool name), and startSeconds still orders it in the timeline.
Text and messaging conversations
The same endpoint ingests text conversations — a chatbot or messaging agent (web chat, SMS, WhatsApp, in-app). Send the messages astranscript.turns and
omit the audio and telephony fields; there’s no recording to re-host. See
Chatbot & text conversations
for the full text-specific contract and a chat.json example.
Handle errors
Errors come back as JSON with anerror string; validation and provider errors
add a details object naming what to fix.
A
200 means Zelto accepted the conversation and is processing it in the
background — not that ingestion has finished. Bad auth and malformed bodies
fail synchronously with a 4xx, but a semantic problem like an agentId that
doesn’t exist in your org still returns 200 and then fails quietly, so it
never appears. Prefer agent.externalId (find-or-create) to avoid that, and
confirm conversations land under Conversations.A delivery that carries neither a transcript nor a recording is treated as
an unconnected dial: Zelto acks it with 200 but intentionally creates no
conversation, since there’s nothing to transcribe or analyze. Send the call
once it has a transcript or recordingUrl (a later
enrichment re-send with one of them ingests
normally).Idempotent re-uploads
call.externalId is the dedup key. POSTing the same id again updates
the existing conversation in place — refreshing the recording, transcript,
duration, or cost — and never double-charges. Send the call once when it
ends, then re-send later if you enrich it (e.g. once the recording
finishes uploading).
Already on Vapi or Retell?
The same endpoint also accepts native Vapi and Retell webhook payloads — set theX-Zelto-Provider header to vapi or retell and POST that
provider’s body unchanged. Omit the header (or send zelto) to use the
canonical shape above.
Verify the first call
After your first POST, open Conversations — the call appears within a few seconds with its transcript. The API Reference tab’s request playground is the fastest way to send a test payload. If a call doesn’t show up, check the response your POST got —{ "received": true } means Zelto accepted
it, while a 4xx returns the reason in the body (an unsupported X-Zelto-Provider
value, a failed field validation, or an invalid key). See
Connect a voice provider.
When to use this vs a native integration
If you already run on Vapi or Retell, use the native integration — webhooks land for free and recordings are fetched for you. This page is for everything else: Bland, Pipecat, a text or messaging agent, in-house stacks, or anything we haven’t shipped a first-class card for yet. Running on LiveKit? It has a dedicated LiveKit page.Related
- Chatbot & text conversations — the same endpoint for text, no audio.
- Connect a voice provider — pick the right path.
- LiveKit — the same endpoint, with a worker example.
- API reference — REST conventions and the full request shape.
- Conversations — where uploaded calls land.

