Requests & responses
Hosts, formats, ids, timestamps, headers, caching and CORS. The conventions every endpoint shares.
3 min read
Base URL#
All REST endpoints live under https://api.listener.com/v1. Every request must use HTTPS.
Format#
- Request and response bodies are JSON, UTF-8, with
snake_casefield names. - Requests with a body send
Content-Type: application/json. Bodies are limited to 1 MB (413 body_too_large). GETparameters go in the query string. Lists of ids are comma-separated:show_ids=8sQe1v0x2kTq,k2LmP0aZ9qWe.
Objects and ids#
Every resource carries an object field naming its type and an id:
{ "object": "campaign", "id": "cmpA7Hq2LmX9vRt4BzKe", "title": "Acme Coffee Q4 host reads", "status": "active" }Workspace resources (shows, campaigns, clusters, reports) keep the ids the apps use. Objects that only exist in the API use prefixed ids so you can tell them apart at a glance:
| Prefix | Object |
|---|---|
we_ | Webhook endpoint |
evt_ | Event |
wd_ | Webhook delivery |
req_ | Request |
whsec_ | Webhook signing secret |
lsk_ | API key |
lcid_ / lcsk_ | OAuth client id / client secret |
lat_ / lrt_ | OAuth access token / refresh token |
Dates and times#
Timestamps are ISO 8601 strings in UTC: 2026-10-14T09:30:00Z. Calendar dates are YYYY-MM-DD. Analytics days are UTC calendar days (see Time windows).
Response headers#
| Header | |
|---|---|
Request-Id | req_…, unique per request. Log it; support will ask for it. Also in every error body as request_id. |
Listener-Version | The API version that served the request. See Versioning. |
RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset | Your rate-limit budget. X-RateLimit-* aliases are sent too. See Rate limits. |
ETag | On GET responses, for conditional requests. Weak (W/"…"). |
Cache-Control | private, max-age=0 on GET, no-store otherwise. |
Idempotent-Replayed | true when a response was replayed for a repeated Idempotency-Key. |
Conditional requests#
Send the ETag you received as If-None-Match on the next GET. If nothing changed, the API answers 304 Not Modified with no body, which saves bandwidth when you poll. The ETag ignores computed_at and cache_age_seconds, so a recomputed but identical result still answers 304.
curl -i https://api.listener.com/v1/shows \
-H "Authorization: Bearer $LISTENER_API_KEY" \
-H 'If-None-Match: "…etag from the last response…"'Computed results and caching#
Analytics are computed from the same documents the apps read. Reads are cached for up to two minutes, and analytics responses say so with computed_at and cache_age_seconds. Writes made through the API clear the cache for what they touch, so a read right after your own write sees it.
Writes#
| Method | Use |
|---|---|
POST | Create something, or perform an action (/status, /refresh, /roll-secret). |
PATCH | Partial update: send only the fields you change. |
PUT | Replace a settable value (default EMV, episode goals, a slot’s cluster link). |
DELETE | Delete. Deletes that cannot be undone need confirm=true. |
Every write supports dry_run and an Idempotency-Key. Successful writes emit events.
Most writes answer with a write result that says exactly what was stored:
{
"object": "write_result",
"resource": "campaign",
"ok": true,
"id": "cmpA7Hq2LmX9vRt4BzKe",
"method": "add",
"written": { "title": "Acme Coffee Q4 host reads", "status": "upcoming" },
"warnings": [],
"urls": { "app": "https://network.listener.com/app/campaigns/cmpA7Hq2LmX9vRt4BzKe", "public": "https://network.listener.com/northwind-audio/campaigns/cmpA7Hq2LmX9vRt4BzKe" },
"data": {}
}written is the exact payload; server-side timestamps appear as "<serverTimestamp>". warnings lists side effects worth knowing about (for example other campaigns that share a brand). Webhook endpoint writes are the exception: they return the webhook_endpoint or webhook_delivery object itself.
Unknown routes and methods#
An unknown path answers 404 route_not_found. A known path with the wrong method answers 405 method_not_allowed with an Allow header listing the methods it supports.
CORS#
/v1, /oauth/token and /oauth/revoke answer cross-origin requests from any origin (Access-Control-Allow-Origin: *) and expose the headers above. Authentication is by bearer token only; no cookies are involved. This exists for OAuth apps running in a browser. Do not put API keys in browser code (see Authentication).