Errors
The error object, every error type and code, and how to handle each.
1 min read
The API uses HTTP status codes for the broad outcome and a typed error object for the detail. 2xx means success, 4xx means the request needs to change, 5xx means something failed on our side.
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "Unknown show id: abc. Valid show ids in this workspace: 8sQe1v0x2kTq, k2LmP0aZ9qWe",
"param": "show_id",
"doc_url": "https://developers.listener.com/docs/errors#parameter_invalid",
"request_id": "req_8f2LkQ0aZ1"
}
}| Field | |
|---|---|
type | The broad category. Branch on this first. |
code | A stable, machine-readable reason. Branch on this for specific handling. |
message | A human-readable explanation. Safe to log; wording may change, so do not parse it. |
param | The parameter at fault, when there is one. |
required_scope | Present on scope_missing. |
doc_url | A link to this page, anchored at the code. |
request_id | The same value as the Request-Id header. Include it when you contact support. |
Error types#
| Type | HTTP | Meaning |
|---|---|---|
invalid_request_error | 400, 405, 413 | The request is malformed or a parameter is wrong. Fix the request before retrying. |
authentication_error | 401 | No credential, or one the API cannot accept. |
permission_error | 403 | The credential is valid but may not do this. |
not_found_error | 404 | The resource or route does not exist in this workspace. |
conflict_error | 409 | The write needs explicit confirmation. |
idempotency_error | 400, 409 | The Idempotency-Key conflicts with an earlier or in-flight request. |
rate_limit_error | 429 | Too many requests for this credential. Wait Retry-After seconds. |
api_error | 500, 502, 503 | Something failed on our side or upstream. Safe to retry with backoff. |
Error codes#
A required parameter was not sent. param names it.
Fix: Add the parameter. The reference marks required ones.
A parameter has the wrong type or value, or names something that is not in this workspace (an unknown show id, for example). The message lists valid values when it can.
Fix: Correct the value named in param. For ids, list the resource first.
The request sent a parameter this endpoint does not accept.
Fix: Remove it, or check the spelling against the reference.
The body is not valid JSON, or the Content-Type is not application/json.
Fix: Send a JSON object with Content-Type: application/json.
The Listener-Version header names a version that does not exist.
Fix: Use a dated version from the changelog, or omit the header.
This platform can only be connected by a person signing in (Google or Meta sign-in, or the Listener browser extension). The error carries connect_url, the show’s Integrations page.
Fix: Send the creator to error.connect_url to connect it, then listen for integration.connected.
The path exists but not with this HTTP method.
Fix: Check the method in the reference.
The body is larger than 1 MB.
Fix: Send less per request, or split it into several writes.
No Authorization header was sent.
Fix: Send Authorization: Bearer lsk_…. Keys in the query string are not accepted on /v1.
The key or token is malformed or unknown.
Fix: Copy the key again from Settings → Developers. Check for whitespace.
The key passed its expiry date, or the OAuth access token is older than one hour.
Fix: Create a new key, or refresh the OAuth token.
The key was revoked, or the user removed your app’s access.
Fix: Create a new key, or send the user through OAuth again.
The credential lacks the scope this endpoint needs. The error carries required_scope.
Fix: Create a key with that scope, or request it in your OAuth authorization URL.
The resource belongs to a different workspace than the credential.
Fix: Use a credential for the workspace that owns the resource.
The workspace’s plan does not include this feature.
Fix: Upgrade the plan in the app, or skip this call for that workspace.
Writes are temporarily switched off for maintenance.
Fix: Retry later. Reads keep working.
No resource with that id in this workspace.
Fix: List the resource to find valid ids.
No endpoint at this path.
Fix: Check the path and the /v1 prefix.
A destructive or metered write (deletes, publishing reports, metered refreshes, data requests) was sent without confirm: true. The error carries a preview of exactly what would happen.
Fix: Show the preview (or run with dry_run), then repeat with confirm: true.
Another request with the same Idempotency-Key is still running.
Fix: Wait for it to finish, then retry to receive its stored response.
The Idempotency-Key was used before with a different body.
Fix: Use a new key for a different request.
The credential exceeded its rate limit. Retry-After says how many seconds to wait.
Fix: Back off for Retry-After seconds. See Rate limits.
An unexpected error. Details never leak; the request id lets us find it.
Fix: Retry with backoff. If it persists, contact support with the request_id.
A platform or service we depend on failed.
Fix: Retry with backoff.
Handling errors well#
async function listener(path, init = {}) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(`https://api.listener.com${path}`, {
...init,
headers: { Authorization: `Bearer ${process.env.LISTENER_API_KEY}`, ...init.headers },
});
if (res.ok) return res.status === 204 ? null : res.json();
const { error } = await res.json();
const retryable = res.status === 429 || res.status >= 500;
if (retryable && attempt < 4) {
const wait = Number(res.headers.get('Retry-After')) || 2 ** attempt;
await new Promise((r) => setTimeout(r, wait * 1000));
continue;
}
throw Object.assign(new Error(`${error.code}: ${error.message}`), { error, status: res.status });
}
}- Retry
429and5xxwith backoff, honouringRetry-After. Send anIdempotency-Keyon writes so a retry never creates a duplicate. - Do not retry other
4xxerrors unchanged; they will fail the same way. - Treat
401 credential_revokedon an OAuth token as the user disconnecting your app. - Log
request_idwith every failure.