Voicetta
    Voicetta.

    Voicetta Developers

    Read API Reference

    Every endpoint of the Voicetta call data API: bearer authentication, parameters, cursor pagination, error codes, rate limits, and a backfill recipe.

    Read API Reference

    The read API returns the same call objects the webhook pushes, on demand. Use it to backfill history, to recover deliveries your server missed, or as your only integration if you would rather poll than run an endpoint.

    Base URL:

    https://api.voicetta.com/v1

    The machine-readable OpenAPI 3.1 document is at /open-api. It is generated from the running API, so it is always an accurate description of these endpoints — point a client generator at it rather than writing request types by hand.

    Authentication

    Every request needs an API key from Settings → Developers, sent as a bearer token:

    Authorization: Bearer vk_live_xxxxxxxxxxxxxxxxxxxxxxxx

    Keys are shown once, at creation, and stored only as a hash. If you lose one, revoke it and create another.

    When you create a key, choose which field groups it may read — the same ten blocks as the outbound webhook. Call metadata (call) is always included. You can change an active key's permissions in the dashboard without rotating the key value.

    A key is scoped to exactly one workspace. There is no parameter that widens that scope: a request can only ever return calls belonging to the workspace that owns the key. If you operate several workspaces, you hold several keys.

    Choosing what you can read

    Each API key carries its own allowlist. A request may only return field groups granted on that key.

    BlockWhat is in itDefault on new keys
    callCall id, workspace, assistant, direction, duration, timestamps, end reasonAlways sent
    customerCaller name, phone, email, language, countryOn
    transcriptTurn-by-turn transcriptOn
    analysisSummary, intent, outcome, sentimentOn
    evaluationsEvaluation verdicts and reasoningOn
    recordingRecording status and download linkOn
    costsCost breakdown and the amount chargedOff
    performance_metricsSpeech, language model, and voice latencyOff
    config_snapshotAssistant configuration at the time of the callOff
    follow_upsRecovery and disconnect follow-up stateOff

    The webhook endpoint and the read API key are configured independently: what we push to your URL and what a key may pull do not have to match. Give a dashboard key only customer and analysis if that is all it displays; give your webhook everything you archive.

    Use the optional fields query parameter to narrow a response within the key's allowlist — for example, a key allowed to read customer, transcript, and analysis can request fields=transcript and receive only the transcript block. Requesting a block the key was not granted returns 403.

    GET /v1/calls

    A page of calls, newest first.

    ParameterTypeMeaning
    cursorstringThe next_cursor from the previous page
    limitinteger1–100. Default 25
    fromISO 8601Only calls that ended at or after this time
    toISO 8601Only calls that ended at or before this time
    assistant_idUUIDOnly calls handled by this assistant
    include_testbooleanInclude test calls. Default false
    fieldsstringComma-separated blocks, e.g. call,transcript. Must be a subset of the key's allowlist. Defaults to everything the key may read
    bash
    curl -H "Authorization: Bearer vk_live_your_key_here" \ "https://api.voicetta.com/v1/calls?limit=50&from=2026-08-01T00:00:00Z&fields=transcript"
    json
    { "calls": [ { "call_id": "9f8b1c4e-6d2a-4b7f-8e21-3c5a7d9e0f11" } ], "has_more": true, "next_cursor": "MjAyNi0wOC0yMFQxMDowMDowMCswMDowMHw5ZjhiMWM0ZQ" }

    By default you receive every block the key was granted — not every block that exists. Omit fields when you want the full allowlist; add it when you want less.

    call is always included, whether or not you name it in fields.

    Pagination

    bash
    cursor="" while :; do page=$(curl -sH "Authorization: Bearer $KEY" \ "https://api.voicetta.com/v1/calls?limit=100&cursor=$cursor") echo "$page" | jq -c '.calls[]' [ "$(echo "$page" | jq -r '.has_more')" = "true" ] || break cursor=$(echo "$page" | jq -r '.next_cursor') done

    Three rules:

    1. Loop on has_more, not on next_cursor. has_more is always present. next_cursor is absent on the last page, because null fields are omitted throughout this API.
    2. Cursors are opaque. Pass them back byte for byte. They encode both a timestamp and a call id, which is what keeps paging stable while new calls arrive at the top of the list. Do not decode or construct one.
    3. Do not use limit for pagination. There is no offset parameter, deliberately: offsets skip or repeat rows when the underlying list shifts under you.

    GET /v1/calls/{call_id}

    One call, in the same shape as the webhook payload's call object.

    bash
    curl -H "Authorization: Bearer vk_live_your_key_here" \ "https://api.voicetta.com/v1/calls/9f8b1c4e-6d2a-4b7f-8e21-3c5a7d9e0f11"
    json
    { "call": { "call_id": "9f8b1c4e-6d2a-4b7f-8e21-3c5a7d9e0f11" } }

    Also accepts fields within the key's allowlist.

    GET /v1/calls/{call_id}/recording

    A 302 redirect to a time-limited audio URL. Requires the recording field group on the API key.

    bash
    curl -L -o call.mp3 \ -H "Authorization: Bearer vk_live_your_key_here" \ "https://api.voicetta.com/v1/calls/9f8b1c4e-6d2a-4b7f-8e21-3c5a7d9e0f11/recording"

    Request it when you need to play or download the audio. Do not store the URL it redirects to — that one expires within the hour.

    This endpoint is what recording.download_url in the payload points at, and that indirection is the whole point: the link inside a payload you archived last year still works, because each request mints a fresh signed URL.

    Make sure your HTTP client follows redirects, or handles the 302 itself. Some clients do not by default.

    Errors

    Errors are JSON with a detail string.

    StatusMeaningWhat to do
    400Invalid cursor, or an unknown name in fieldsFix the request. Retrying will not help
    401Missing, malformed, unknown, or revoked API keyCheck the key and the Bearer prefix
    403The key is not permitted to access the requested field groupsRemove the block from fields, or grant it on the key in Settings → Developers
    404No such call in this workspace, or no recording on the callThe id is wrong, belongs to another workspace, or the call has no audio
    429Rate limitedBack off and retry
    503The API is temporarily disabledRetry with backoff

    A 401 has exactly two causes in practice: the key was revoked, or the Bearer prefix is missing.

    Note what 404 does not distinguish: a call id that does not exist and a call id belonging to someone else's workspace return the same response. That is intentional — the alternative would let anyone with a key confirm whether a given call id exists elsewhere in Voicetta.

    Rate limits

    LimitScope
    1,000 requests per hourPer endpoint, per caller
    100 requests per secondBurst protection, per caller

    Callers are identified by network address, so if several of your services share one outbound IP or NAT gateway, they share a bucket. Worth knowing before you fan out a backfill across a worker pool.

    Both limits are generous for normal use: 1,000 hourly requests at 100 calls per page is 100,000 calls an hour. If you are hitting them, you are almost certainly polling when you should be receiving webhooks.

    Backfilling history

    To load calls from before you integrated, or to catch up after your endpoint was down, page through GET /v1/calls with a from bound and feed the results into the same handler your webhook uses. Both produce identical call objects, so no separate code path is needed.

    bash
    # Everything since the start of August, oldest processed first. curl -H "Authorization: Bearer $KEY" \ "https://api.voicetta.com/v1/calls?limit=100&from=2026-08-01T00:00:00Z"

    Two things make this safe to run more than once:

    • Your handler is already idempotent, because webhook retries required it.
    • call_id is stable, so a call fetched twice is recognisably the same call.

    If you are recovering from a specific outage, bound both ends with from and to rather than re-reading your whole history.

    Versioning

    Every path starts with /v1, and within v1 we only add fields — never rename, remove, retype, or change the meaning of one. See the overview for what that guarantees and what it asks of your parser.