Errors
Eight stable error codes and what to do about each.
Every non-2xx response from /public/v1/* returns the same envelope:
{
"error": {
"code": "scope_missing",
"message": "Key is missing required scope: meetings:read",
"request_id": "req_70ac81fe9ef7a3c6"
}
}error.code— stable, machine-readable. Branch on this. Codes are versioned with the API; we won't repurpose one without a major version bump.error.message— human-readable English. The wording can change between versions; don't string-match on it.error.request_id— a per-request id. Quote it in support requests so we can grep logs and Sentry quickly.
The eight codes
| HTTP | error.code | When | What to do |
|---|---|---|---|
| 400 | invalid_request | Validation failure on params/query/body — e.g., time_range=bogus, limit=999. | Fix the request. The message describes which field. |
| 400 | invalid_cursor | The cursor query param is malformed or corrupt. | Restart pagination from the first page (omit cursor). |
| 401 | unauthorized | Missing or malformed Authorization header. | Add Authorization: Bearer mts_live_…. |
| 401 | invalid_key | Key not found, revoked, or expired. | Mint a new key in Settings → API Keys. |
| 403 | scope_missing | Key valid but lacks the required scope. | Mint a new key with the right scopes; revoke the old one. |
| 404 | not_found | Resource doesn't exist OR belongs to a different org. We don't distinguish — see note below. | Verify the id; if it should exist, check that the key belongs to the right org. |
| 429 | rate_limited | You've exceeded the burst (10/sec) or sustained (60/min) limit. | Back off and retry. See Rate limits. |
| 500 | internal_error | Unhandled server error. | Quote request_id to support. Retry with backoff. |
Cross-org access returns 404, not 403
If a key from org A requests GET /public/v1/meetings/:id for a
meeting that exists but belongs to org B, you get 404 not_found,
not 403. That's deliberate — distinguishing "doesn't exist" from
"exists in another org" would leak information about other orgs'
data. From a key's perspective, the resource doesn't exist.
Recommended client behavior
- 2xx: success. Process the body.
- 4xx (except 429): client bug. Don't retry — fix the request.
Surface
error.codeto whoever's wired up the integration. - 429: respect the
Retry-Afterheader (when present) and back off. See Rate limits for retry strategy. - 5xx: transient server issue. Retry with exponential backoff
(1s → 2s → 4s, max ~3 attempts). If it persists, file a support
request with
error.request_id.
Don't parse error.message
It's English prose — the wording will drift. Always branch on
error.code. We treat code changes as a major-version event; messages
can be tweaked anytime without notice.