API reference

Messages

Send, list, get, and cancel channel-native WhatsApp and Instagram messages.

Send a message

POST/v1/messages202 Accepted

Request body

FieldTypeNotes
No fields.

Response

FieldTypeNotes
idrequiredstring
objectrequired"message"
statusrequiredstring
status_reasonrequiredstring | null
Why this message is in its current status, when there is a reason worth naming. The only value emitted today is "messaging_limit_pacing": the message is queued because the sending number has used up WhatsApp's daily allowance of NEW recipients, so Tyxter is holding the send rather than pushing it into a rejection. Nothing is lost and no action is needed — the message sends by itself once allowance frees up, and re-sending it would only duplicate the message. Null otherwise, including for a message queued for any other reason, so null on its own never means "not paced" — read it together with status. Treat the set of values as open: more reasons may be named later without notice.
channelrequired"whatsapp" | "instagram" | "whatsapp_channel"
environmentrequired"sandbox" | "production"
template_idrequiredstring | null
template_version_idrequiredstring | null
template_versionrequiredinteger | null
created_atrequiredstring<ISO-8601>
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
trace_idrequiredstring

Template sends use message.type = "template". Set message.template.language to a string WhatsApp language code such as pt_BR; do not send a nested language object.

Before a WhatsApp template send is accepted, Tyxter resolves its approved version and checks modeled BODY and text-HEADER placeholders, media headers, and dynamic URL-button parameters. The send request has no parameter_format: the immutable approvedTemplateVersion chooses it. POSITIONAL BODY variables use exact numeric keys such as "1"; NAMED BODY variables use exact approved parameter-name keys such as "customer_name". A named textHEADER or dynamic URL button uses an explicit text parameter carrying the matching parameter_name. A mismatch returns 400 template_param_mismatch with a corrective param such as message.template.variables, message.template.components, or message.template.header_media. The request did not create a message, consume billing, enqueue work, or call Meta. This modeled preflight complements, rather than replaces, the asynchronous failed-message fallback for a later approval change or an unmodeled Meta rule.

WhatsApp recipients accept the compatible recipient.id = "+E.164" form or the additive structured form with country_calling_code and national_number. Structured inputs are checked against the international numbering plan and can return invalid_phone_country_calling_code, invalid_phone_national_number, or invalid_phone_e164. Legacy id acceptance is unchanged.

{
  "channel": "whatsapp",
  "sender": { "type": "whatsapp_phone_number", "id": "pn_..." },
  "recipient": {
    "type": "phone_e164",
    "country_calling_code": "55",
    "national_number": "11903244174"
  },
  "message": { "type": "text", "text": { "body": "Olá" } }
}
{
  "channel": "whatsapp",
  "sender": { "type": "whatsapp_phone_number", "id": "pn_..." },
  "recipient": { "type": "phone_e164", "id": "+5511999999999" },
  "message": {
    "type": "template",
    "template": {
      "name": "order_tracking_update",
      "language": "pt_BR",
      "variables": { "customer_name": "Ana", "order_id": "ORD-123" },
      "components": [
        {
          "type": "header",
          "parameters": [
            { "type": "text", "parameter_name": "order_id", "text": "ORD-123" }
          ]
        },
        {
          "type": "button",
          "sub_type": "url",
          "index": 0,
          "parameters": [
            { "type": "text", "parameter_name": "order_id", "text": "ORD-123" }
          ]
        }
      ]
    }
  }
}
{
  "channel": "whatsapp",
  "sender": { "type": "whatsapp_phone_number", "id": "pn_..." },
  "recipient": { "type": "phone_e164", "id": "+5511999999999" },
  "message": {
    "type": "template",
    "template": {
      "name": "order_update",
      "language": "pt_BR",
      "variables": { "1": "Ana" }
    }
  }
}

WhatsApp media accepts image, document, audio,video, and sticker. For WhatsApp audio, set message.media.voice to true to request voice-note rendering. Omit it or use false for ordinary audio. Meta requires OGG/Opus mono audio for a voice note. When Tyxter knows the resolved bytes' MIME type, it requires the normalized base type audio/ogg before provider I/O; it does not inspect codecs or channels, and does not convert media. If that known MIME type is not audio/ogg, delivery fails with media_voice_requires_ogg_opus before a provider call. Use OGG/Opus mono audio or omit message.media.voice. Provide exactly one of media.id, media.link, media.asset_id, media.inline, or media.source. Use media.asset_id for single-use or library uploads created through the Media API. A media.source with type = "tts" accepts immediately, renders caller-supplied text into an audio attachment on the media-render worker, and then delivers through the normal media path; it does not invoke an agent workflow or generate the reply text.

{
  "channel": "whatsapp",
  "sender": { "type": "whatsapp_phone_number", "id": "pn_..." },
  "recipient": { "type": "phone_e164", "id": "+5511999999999" },
  "message": {
    "type": "media",
    "media": {
      "kind": "audio",
      "link": "https://cdn.example.com/voice.ogg",
      "voice": true
    }
  }
}
{
  "channel": "whatsapp",
  "sender": { "type": "whatsapp_phone_number", "id": "pn_..." },
  "recipient": { "type": "phone_e164", "id": "+5511999999999" },
  "message": {
    "type": "media",
    "media": {
      "kind": "image",
      "asset_id": "mda_..."
    }
  }
}
{
  "channel": "whatsapp",
  "sender": { "type": "whatsapp_phone_number", "id": "pn_..." },
  "recipient": { "type": "phone_e164", "id": "+5511999999999" },
  "message": {
    "type": "media",
    "media": {
      "kind": "audio",
      "source": {
        "type": "tts",
        "provider": "openai",
        "text": "Your order is out for delivery.",
        "voice": "marin",
        "language": "en-US"
      }
    }
  }
}

Direct quick replies are WhatsApp interactive button messages. They require one to three reply buttons and must be sent inside the customer-service window.

{
  "channel": "whatsapp",
  "sender": { "type": "whatsapp_phone_number", "id": "pn_..." },
  "recipient": { "type": "phone_e164", "id": "+5511999999999" },
  "message": {
    "type": "interactive",
    "interactive": {
      "type": "button",
      "body": { "text": "Confirm delivery?" },
      "action": {
        "buttons": [
          { "type": "reply", "reply": { "id": "confirm", "title": "Confirm" } }
        ]
      }
    }
  }
}

Native Brazil Pix uses the strict interactive.order_details subtype. Send it only for a WhatsApp Business Account eligible for Meta Payments API in Brazil. As a free-form interactive message, it requires an open 24-hour customer-service window; otherwise Tyxter rejects synchronously with service_window_required. Tyxter returns 202 Accepted only after contract, authentication, feature, and service-window prevalidation. Meta then checks WABA and message eligibility at provider-send time, so acceptance does not promise delivery by Meta.

{
  "channel": "whatsapp",
  "sender": { "type": "whatsapp_phone_number", "id": "phone_number_id" },
  "recipient": { "type": "phone_e164", "id": "+5511999999999" },
  "message": {
    "type": "interactive",
    "interactive": {
      "type": "order_details",
      "body": { "text": "Review and pay your order." },
      "action": {
        "name": "review_and_pay",
        "parameters": {
          "reference_id": "ord_123-20260807",
          "type": "digital-goods",
          "payment_type": "br",
          "payment_settings": [{
            "type": "pix_dynamic_code",
            "pix_dynamic_code": {
              "code": "00020101021226700014br.gov.bcb.pix...",
              "merchant_name": "Example Store",
              "key": "39580525000189",
              "key_type": "CNPJ"
            }
          }],
          "currency": "BRL",
          "total_amount": { "value": 12900, "offset": 100 }
        }
      }
    }
  }
}

The caller supplies the reference, dynamic Pix code, merchant name, key, and key type. Tyxter does not resolve a payment_id into this message and does not expose an order_status update. Meta transports the details but does not reconcile settlement; confirm settlement with the merchant or PSP. Most integrations should collect Pix with a Tyxter payment request instead — the Pix payments recipe documents the recommended transparent Abacate Pix path alongside this native message.

Send a published WhatsApp Flow with message.type = "flow". Address the flow with flow.action.parameters.flow_id (the published flow's provider_flow_id, read from GET /v1/flows/{id}) or with flow.action.parameters.flow_name — provide exactly one. flow_token is your required opaque correlation token and comes back as submission.provider_token on the flow.completed webhook; flow_cta is the required button label (max 30 chars). See the Flows guide for the full create → publish → send → complete loop.

{
  "channel": "whatsapp",
  "sender": { "type": "whatsapp_phone_number", "id": "pn_..." },
  "recipient": { "type": "phone_e164", "id": "+5511999999999" },
  "message": {
    "type": "flow",
    "flow": {
      "body": { "text": "Tap below to complete your booking." },
      "action": {
        "name": "flow",
        "parameters": {
          "flow_id": "1234567890",
          "flow_token": "booking-7f3a",
          "flow_cta": "Book now"
        }
      }
    }
  }
}

Retrieve a message

GET/v1/messages/{id}

Response

FieldTypeNotes
idrequiredstring
objectrequired"message"
channelrequired"whatsapp" | "instagram" | "whatsapp_channel"
directionrequiredstring
typerequiredstring
statusrequiredstring
status_reasonrequiredstring | null
Why this message is in its current status, when there is a reason worth naming. The only value emitted today is "messaging_limit_pacing": the message is queued because the sending number has used up WhatsApp's daily allowance of NEW recipients, so Tyxter is holding the send rather than pushing it into a rejection. Nothing is lost and no action is needed — the message sends by itself once allowance frees up, and re-sending it would only duplicate the message. Null otherwise, including for a message queued for any other reason, so null on its own never means "not paced" — read it together with status. Treat the set of values as open: more reasons may be named later without notice.
environmentrequired"sandbox" | "production"
senderrequiredobject | object
Sender on message list and retrieve responses. The phone-less inbound WhatsApp fallback is the only arm with an empty id; outbound request identities remain non-empty.
recipientrequiredobject
providerrequiredstring | null
provider_message_idrequiredstring | null
template_namerequiredstring | null
template_idrequiredstring | null
template_version_idrequiredstring | null
template_versionrequiredinteger | null
mediarequiredobject | object | object | object | null
unsupportedrequiredobject | null
Set when the provider refused to deliver this inbound message (type "unsupported"). The content never reaches Tyxter; ask the sender to resend in a supported format.
unknownrequiredobject | null
Set when the provider delivered an inbound type Tyxter does not project yet (type "unknown"). The content is in the raw payload; only the typed view is missing.
payloadrequiredunknown | null
Raw provider/request payload for diagnostics. Provider-specific fields, including private short-lived media URLs, are unsupported integration handles. Use the first-class media.asset_id descriptor for inbound attachments.
metadatarequiredunknown | null
error_coderequiredstring | null
error_messagerequiredstring | null
provider_errorrequiredobject | null
trace_idrequiredstring
created_atrequiredstring<ISO-8601>
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
updated_atrequiredstring<ISO-8601>
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
redacted_atrequiredstring<ISO-8601> | null
delivery_unconfirmed_atrequiredstring<ISO-8601> | null
eventsrequiredobject[]

Inbound attachments expose a first-class media descriptor on this response: its asset_id is the Tyxter mda_* handle accepted by GET /v1/media/{asset_id}/download-url and by the message-bound transcription flow. The same descriptor appears on list rows and on message.received, with one status switch across all three surfaces. When status is consumed, media.download contains the relative GET method/path for that authenticated download hop, never a signed URL.

The detail response also includes payload as a raw provider/request diagnostic window. Provider-specific fields are unsupported integration contracts. In particular, a Meta lookaside.fbsbx.com URL is private, short-lived, and bearer-authenticated; do not render or fetch it. Follow media.download.path and mint a fresh Tyxter download capability instead.

A message that stays queued longer than you expected may be paced rather than stuck. status_reason names the cause when there is one worth naming: today the only value is messaging_limit_pacing, meaning the sender has no slot available for this unique recipient outside their customer-service window. It is not a total-message cap: recipients with an existing rolling slot and recipients who messaged this sender in the last 24 hours keep flowing. Nothing is lost — the message sends by itself once allowance frees up, and re-sending it would only duplicate it. A nullhere does not mean “not paced” on its own; read it together with status. Treat the set of values as open, check remaining_messaging_allowance_estimate on the sending number, and read the pacing guide for the exact counting rules.

List messages

GET/v1/messages

Query params

FieldTypeNotes
limitrequiredinteger
Maximum number of items to return. Defaults to 20; maximum 100.
range 1..100
starting_afterstring
Opaque cursor from the previous response next_cursor.
statusstring
batch_idstring
direction"inbound" | "outbound"
Return only messages in this direction: "inbound" or "outbound".
include"payload"

Response

FieldTypeNotes
objectrequired"list"
datarequiredobject[]
has_morerequiredboolean
next_cursorrequiredstring | null

List responses omit large message bodies by default: each item returns payload: null and metadata: null. Pass include=payload when an inspector or agent needs both fields. The first-class media descriptor is not part of that expansion: it is present by default for a settled inbound attachment so a polling inbox never needs to inspect provider payloads or match against the media list. For inbound WhatsApp audio, optional media.voice is true for a Meta voice note and false for an ordinary audio file; omission means a legacy row or absent provider signal.

Pass direction=inbound (or outbound) to filter server-side. An inbox-style reader should use it rather than over-fetching and filtering locally: on a busy account, outbound traffic can otherwise fill the whole first page.

Phone-like sender.id and recipient.id values are normally returned in strict +E.164 with every subscriber digit preserved. One output-only case is intentionally unresolved: when Meta withholds the customer phone on a production inbound WhatsApp message, reads return { type: "phone_e164", id: "" } for its sender. Do not use that empty id as a recipient or replace it with a synthetic identifier. Recipients and outbound request identities remain non-empty. Tyxter may maintain separate internal identity evidence, but list and retrieve responses never substitute it for the public identity. Opaque Meta phone-number ids remain unchanged.

const page = await tyxter.messages.list({
  status: 'failed',
  include: 'payload',
});

// page.data[0].media is always the supported inbound attachment handle.
// page.data[0].payload and .metadata now also contain raw diagnostic data.

Cancel a message

POST/v1/messages/{id}/cancel

Response

FieldTypeNotes
idrequiredstring
objectrequired"message"
channelrequired"whatsapp" | "instagram" | "whatsapp_channel"
directionrequiredstring
typerequiredstring
statusrequiredstring
status_reasonrequiredstring | null
Why this message is in its current status, when there is a reason worth naming. The only value emitted today is "messaging_limit_pacing": the message is queued because the sending number has used up WhatsApp's daily allowance of NEW recipients, so Tyxter is holding the send rather than pushing it into a rejection. Nothing is lost and no action is needed — the message sends by itself once allowance frees up, and re-sending it would only duplicate the message. Null otherwise, including for a message queued for any other reason, so null on its own never means "not paced" — read it together with status. Treat the set of values as open: more reasons may be named later without notice.
environmentrequired"sandbox" | "production"
senderrequiredobject | object
Sender on message list and retrieve responses. The phone-less inbound WhatsApp fallback is the only arm with an empty id; outbound request identities remain non-empty.
recipientrequiredobject
providerrequiredstring | null
provider_message_idrequiredstring | null
template_namerequiredstring | null
template_idrequiredstring | null
template_version_idrequiredstring | null
template_versionrequiredinteger | null
mediarequiredobject | object | object | object | null
unsupportedrequiredobject | null
Set when the provider refused to deliver this inbound message (type "unsupported"). The content never reaches Tyxter; ask the sender to resend in a supported format.
unknownrequiredobject | null
Set when the provider delivered an inbound type Tyxter does not project yet (type "unknown"). The content is in the raw payload; only the typed view is missing.
payloadrequiredunknown | null
Raw provider/request payload for diagnostics. Provider-specific fields, including private short-lived media URLs, are unsupported integration handles. Use the first-class media.asset_id descriptor for inbound attachments.
metadatarequiredunknown | null
error_coderequiredstring | null
error_messagerequiredstring | null
provider_errorrequiredobject | null
trace_idrequiredstring
created_atrequiredstring<ISO-8601>
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
updated_atrequiredstring<ISO-8601>
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
redacted_atrequiredstring<ISO-8601> | null
delivery_unconfirmed_atrequiredstring<ISO-8601> | null
eventsrequiredobject[]

Show a typing indicator

POST/v1/messages/{id}/typing202 Accepted

Response

FieldTypeNotes
objectrequired"typing_indicator"
message_idrequiredstring
statusrequired"accepted"

One call does two things that always travel together: it marks the inbound message as read and it shows the typing indicator on that conversation. There is no read-only and no typing-only variant. WhatsApp draws the indicator for at most 25 seconds and dismisses it the moment your reply is delivered, so call this immediately before you start composing — not on receipt — and send the reply inside that window. If composing will take longer than 25 seconds, send an interim reply rather than leaving the contact waiting on an indicator that has already lapsed. It is never billed and never appears on your usage.

Take the target from message.received.data.message_id. That is the Tyxter message ID this route accepts. The webhook envelope’s top-level id is an event ID, and data.provider_message_idis Meta’s reference; neither is a valid substitute. In untyped JavaScript, check that data.message_id is a non-empty string before calling the SDK. Reading a missing property can otherwise produce the literal path /v1/messages/undefined/typing, which returns message_not_found and shows no indicator.

The call creates no message: nothing appears in GET /v1/messages, no webhook fires, and there is no delivery status to poll. The 202 is the only acknowledgement, and a provider-side failure after it is not reported back. Everything that can be refused is refused synchronously, so branch on the response instead: the id must name an inbound message you received (typing_indicator_target_not_inbound, 400), on a channel whose provider supports the indicator — today only WhatsApp (typing_indicator_unsupported_channel, 400) — received within the last 30 days (typing_indicator_target_expired, 400). An unknown id, or one belonging to another organization, answers message_not_found (404). A valid target that carries no provider reference to draw against answers typing_indicator_target_unavailable (409); retrying will not clear it, so answer with a normal reply instead.

The endpoint takes no Idempotency-Key — it stores nothing, so a repeated call is already indistinguishable from the first — and needs the same messages:send scope a reply needs. Supply Tyxter-Trace-Id to correlate the acknowledgement with the asynchronous job and worker logs. Sandbox keys are accepted and the indicator is simulated (no real device shows anything), so the flow can be rehearsed end to end before going live.

async function showTypingForInbound(event: {
  type?: unknown;
  data?: { message_id?: unknown };
}) {
  if (event.type !== 'message.received') return;

  const messageId = event.data?.message_id;
  if (typeof messageId !== 'string' || messageId.trim() === '') {
    throw new Error('message.received is missing data.message_id');
  }

  await tyxter.messages.typing(messageId, { traceId: 'trc_reply_123' });
  // Generate and send the reply with tyxter.whatsapp.sendText(...) next.
}

Request an inbound audio transcription

POST/v1/messages/{message_id}/transcription202 Accepted

Request body

FieldTypeNotes
languagestring
Optional ISO-639-1 language hint, for example pt or en.
pattern: ^[a-z]{2}$

Response

FieldTypeNotes
idrequiredstring
objectrequired"message_media_transcript"
message_idrequiredstring
media_asset_idrequiredstring
statusrequired"pending" | "succeeded" | "failed"
providerrequiredstring | null
modelrequiredstring | null
languagerequiredstring | null
textrequiredstring | null
duration_secondsrequirednumber | null
error_coderequiredstring | null
error_messagerequiredstring | null
trace_idrequiredstring
created_atrequiredstring<ISO-8601>
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
completed_atrequiredstring<ISO-8601> | null

This is an explicit per-message opt-in and accepts only inbound audio whose Tyxter media asset is ready for playback. A pending or succeeded receipt replays only when the language hint is omitted or unchanged. A failed receipt answers transcription_retry_required; this create operation never starts another provider attempt. Transcription runs asynchronously and never delays message.received.

To avoid polling, subscribe to both terminal events: message.media_transcribed for success and message.media_transcription_failed for failure. The failed event carries the same error_codeas this route's GET receipt and never carries speech, provider, model, or duration fields.

Retry a failed inbound audio transcription

POST/v1/messages/{message_id}/transcription/retry202 Accepted

Request body

FieldTypeNotes
languagestring
Optional ISO-639-1 language hint, for example pt or en.
pattern: ^[a-z]{2}$

Response

FieldTypeNotes
idrequiredstring
objectrequired"message_media_transcript"
message_idrequiredstring
media_asset_idrequiredstring
statusrequired"pending" | "succeeded" | "failed"
providerrequiredstring | null
modelrequiredstring | null
languagerequiredstring | null
textrequiredstring | null
duration_secondsrequirednumber | null
error_coderequiredstring | null
error_messagerequiredstring | null
trace_idrequiredstring
created_atrequiredstring<ISO-8601>
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
completed_atrequiredstring<ISO-8601> | null

This route reopens only a failed receipt against its exact original inbound audio. It requires a non-blank Idempotency-Key: reuse the same key and body to replay the stored202, rather than creating another generation. The receipt id and original asset stay stable. media_asset_expired means that otherwise-valid original audio is beyond retention; transcription_source_unavailable means the original source is structurally no longer usable. Neither can be repaired by uploading a replacement attachment.

Retrieve an inbound audio transcription

GET/v1/messages/{message_id}/transcription

Response

FieldTypeNotes
idrequiredstring
objectrequired"message_media_transcript"
message_idrequiredstring
media_asset_idrequiredstring
statusrequired"pending" | "succeeded" | "failed"
providerrequiredstring | null
modelrequiredstring | null
languagerequiredstring | null
textrequiredstring | null
duration_secondsrequirednumber | null
error_coderequiredstring | null
error_messagerequiredstring | null
trace_idrequiredstring
created_atrequiredstring<ISO-8601>
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
completed_atrequiredstring<ISO-8601> | null