Skip to main content
Chert can deliver audio as a native iMessage voice-note bubble on supported Apple devices, with a waveform, playback controls, duration, and the normal Keep affordance. The workflow uses two API requests:
1

Stage the audio

Call POST /api/v1/voice-notes with a genuine CAF file or an uncompressed WAV file. Chert returns a temporary voice_note_id and the phone_line_id that staged it.
2

Send from the same line

Call POST /api/v1/send with the returned voice_note_id and the exact same phone_line_id before the staged audio expires.
Voice notes are not ordinary attachments. Do not upload them through the generic attachment API or put a voice_note_id in attachments[]. The /voice-notes plus /send workflow requests native voice-message presentation.
This two-step endpoint flow is for outbound native voice notes. Inbound Apple voice memos arrive through the normal message.received attachment flow—there is no separate inbound voice-note endpoint. See Receiving inbound voice memos.

Prerequisites

You need:
  • A Chert API signing secret.
  • A phone line assigned to the authenticated project.
  • Voice-note support enabled on the selected phone line.
  • A recipient phone number or Apple ID email address.
  • A genuine CAF file or an uncompressed WAV file.
  • One phone_line_id used for both staging and sending.
List the lines assigned to your project with GET /api/v1/phone-numbers:
Select a line where is_active is true and capabilities includes send. The list does not currently expose a separate voice-note capability flag, so use a line Chert has enabled for voice notes. See Phone numbers for the complete response reference.

Authentication

The examples use bearer authentication for readability:
For a default single-tenant account, the bearer token identifies the workspace. For a multi-tenant account, also send x-chert-tenant with the tenant slug. HMAC signing is recommended when you do not want to transmit the secret as a bearer token. See Authentication for both modes and the five-minute signature window. Never place a signing secret in client-side code, source control, logs, or an example payload.

Stage a voice note

Provide exactly one audio source: audio_url or audio_base64.

HTTPS audio URL

audio_url must use HTTPS and must remain reachable while the staging request runs.

Inline base64 WAV

The following example builds the JSON with jq so the base64 payload is not printed or interpolated into shell history:

Request fields

Supported formats

The production allowlist is: Renaming or relabeling M4A, AAC, MP3, WebM, Ogg, compressed WAV, or another format as audio/caf does not turn it into a valid CAF file. Convert an unsupported source to uncompressed WAV before staging it:
This produces a mono, 44.1 kHz, 16-bit PCM WAV suitable for staging.

Duration and staging lifetime

Chert probes the actual audio duration. A caller-provided duration_seconds value is informational and does not override the measured duration. Staged IDs are temporary and line-local. A phone-line service restart invalidates them even if expires_at_ms has not passed. Stage only when your application is ready to send. If an ID expires or the selected line restarts, stage the audio again and use the new voice_note_id.

Staging response

Send the staged voice note

  • Copy the exact phone_line_id from the staging response.
  • Send before expires_at_ms.
  • phone accepts an E.164 number or an Apple ID email handle; Chert lowercases email handles during normalization.
  • Use an idempotency_key for every intended send.
A voice-note send is exclusive. Do not include body, attachments, effect, or rich_link in the same request. Send companion text in a separate /send request with its own idempotency key.

Send response

Complete cURL walkthrough: HTTPS URL

This walkthrough selects an assigned active send line, stages the audio, reads both returned IDs, and sends with one stable idempotency key. curl --fail-with-body stops each command on an HTTP error.

Complete cURL walkthrough: inline base64

JavaScript / TypeScript

Python

The examples log only the final status and message ID. They do not log the signing secret or full base64 payload.

Retry and duplicate safety

  • Use a unique idempotency key for each intended voice note.
  • Reuse the same key only for the same logical send and identical request.
  • If a request times out, do not generate a new key merely to retry it.
  • If the outcome is ambiguous, inspect the message status and your controlled recipient before attempting another send.
  • A new key represents a new send and can create a duplicate voice note.
  • If staging expires or the line restarts, stage again. Then make a deliberate send decision while preserving the original key for the same logical send.
Reusing a key with an identical request returns the recorded result. Reusing it with a different recipient, body, or voice_note_id returns HTTP 422 with the legacy string code idempotency_collision.
Do not blindly retry an ambiguous send. status: "sent" means Chert submitted the message, and a network timeout can occur after submission. Check GET /api/v1/messages/{message_id} when you have a message ID and verify the controlled recipient before creating a new logical send.

Errors and troubleshooting

POST /api/v1/voice-notes uses the standard error envelope:
POST /api/v1/send uses its legacy flat failure shape:
/send failures do not include trace_id. On 5xx responses, the route may replace reason and recipient IDs with a generic message. Always branch on retryable when it is present. See Errors for the complete numeric registry and retry guidance.

Receive an inbound voice memo

Inbound and outbound voice notes use different API shapes: There is no separate endpoint for receiving voice notes. In a verified production event, an Apple voice memo appeared through the ordinary attachment/media flow with an attachment_id, a generic application/octet-stream MIME type, and no filename in the event metadata. The authenticated content response supplied the useful .caf filename.
A generic webhook MIME type does not mean the recording is invalid. Inspect the content response headers and downloaded bytes instead of relying only on the event’s MIME type or filename.

Verified observed format

One controlled Apple voice memo had the following characteristics: This is a verified example, not a universal format guarantee. Apple devices and OS versions may produce different metadata or audio encodings. Inspect the download response, file signature, and media metadata before choosing a decoder. Chert preserves and exposes the inbound bytes. It does not automatically convert recordings to MP3 or WAV, and it does not currently return a transcript or perform speech-to-text. Applications that need transcription must download the attachment and submit it to their chosen speech-to-text system. See Receiving replies for a representative webhook and complete cURL, TypeScript, Python, file, ffprobe, and FFmpeg examples.

Best practices

  • Prefer genuine CAF or uncompressed PCM WAV.
  • Keep audio between 0.5 seconds and five minutes.
  • Stage immediately before sending.
  • Preserve the staging response’s phone_line_id.
  • Use one stable idempotency key per intended send.
  • Keep secrets in environment variables.
  • Avoid logging raw base64.
  • Send companion text or links separately.
  • Do not blindly retry ambiguous sends.
  • Test native rendering on a controlled Apple recipient before a large rollout.

Relationship to attachments

Generic audio attachments and native voice notes are separate workflows: Inbound Apple voice memos are a third case: they arrive as downloadable attachment parts in message.received. The inbound event does not reliably distinguish a native Apple voice memo from every other audio attachment, so do not infer provenance from type, MIME type, or filename alone. Do not put a voice-note ID in attachments[]. See Attachments for the generic file workflow.

See also