Conversations
Every voice call from a customer install becomes a conversation row, kept 365 days unless deleted earlier. Use these endpoints to pull transcripts and recordings into your tools.
Auth: API Key. Scoped to the key’s site.
List conversations
GET /v1/public/conversations?limit=25&since=2026-05-01T00:00:00Z&has_recording=1Authorization: Bearer sk_live_...Query parameters
| Param | Type | Default | Notes |
|---|---|---|---|
limit | int | 25 | 1–100 |
cursor | string | — | opaque from a previous next_cursor |
since | ISO 8601 | — | started_at >= since |
has_recording | bool | — | only conversations with an audio recording |
Response
{ "data": [ { "id": "conv_abc", "site_id": "0aa06d96-...", "started_at": "2026-05-07T11:00:00.000Z", "ended_at": "2026-05-07T11:03:42.000Z", "duration_seconds": 222, "turn_count": 12, "page_url": "https://example.com/contact", "first_user_text": "Hi, I'd like to book a consultation", "transcript": [ { "role": "assistant", "text": "Hi! How can I help?", "ts_ms": 0 }, { "role": "user", "text": "I'd like to book a consultation", "ts_ms": 1800 } ], "tool_calls": [ { "name": "submit_lead", "args": { "data": { "name": "Sarah", "email": "..." } } } ], "recording_path": "0aa06d.../conv_abc.webm", "recording_mime": "audio/webm", "recording_bytes": 184320, "created_at": "2026-05-07T11:03:42.000Z" } ], "next_cursor": null, "has_more": false}Fetch one conversation
GET /v1/public/conversations/{id}Authorization: Bearer sk_live_...Same shape as a list item, but always includes the full transcript and
tool_calls. Returns 404 if the conversation does not exist or is on a
different site than the API key.
Get a recording URL
Recordings live in private Storage. To play one back, mint a signed URL:
GET /v1/public/conversations/{id}/recordingAuthorization: Bearer sk_live_...{ "data": { "url": "https://...supabase.co/storage/v1/object/sign/conversation-recordings/...", "expires_at": "2026-05-07T11:08:42.000Z" }}The signed URL is valid for 5 minutes. Fetch it just before you need it — do not cache it.
# 1. Get the signed URLSIGNED=$(curl -s -H "Authorization: Bearer $KEY" \ https://api.spelo.ai/v1/public/conversations/$ID/recording | jq -r '.data.url')
# 2. Downloadcurl -o conversation.webm "$SIGNED"Recording availability
A recording exists when recording_path is non-null on the conversation row.
This depends on:
site_configs.recording_enabled = true(Settings → Lead Capture)- the call lasted long enough for upload (calls under ~3s often skip recording)
- upload succeeded (rare failures are logged but not retried)
The conversation.recording_ready webhook fires the moment a recording
becomes available — subscribe to it instead of polling.
Retention
Conversations expire 365 days after started_at. The expires_at column on
each row tells you when. A nightly cleanup job deletes expired rows AND the
underlying Storage object — there is no recovery after expiry.
See also
- Leads endpoints — leads captured during a conversation
- Webhooks —
conversation.endedandconversation.recording_ready