Skip to main content

Responses and Errors

How every v1 REST response is shaped — success envelope, pagination cursors, and machine-readable errors.

Success envelope

Every successful JSON response on /v1/... includes "ok": true as the first field of the top-level object:

{
"ok": true,
"chatId": "757dfe66-37b4-4772-baa5-8c86ec68c176",
"timestamp": 1765602474760032
}

You can branch on body.ok before reading the rest of the payload. Non-JSON bodies (for example meeting.transcript when requested as WebVTT) and 204 responses are not wrapped.

Request correlation uses the Request-Id response header (not a body field).

Error envelope

Error responses use the inverse envelope. The error field is a machine-readable catalog code (not a free-text sentence):

{
"ok": false,
"error": "invalid_token"
}

HTTP status codes still apply (400, 401, 403, 404, 429, 500, …). Branch on error rather than parsing human language.

missing_scope details

When the token lacks a required scope, the body includes structured fields so you can drive a re-auth / re-consent flow without parsing prose:

{
"ok": false,
"error": "missing_scope",
"needed": ["chat:send_message", "chat:write"],
"provided": ["chat:read", "chat:history", "group:read"]
}
FieldMeaning
neededScopes that would satisfy the check. Any-of (OR): holding any one element is enough.
providedThe token's granted scopes after alias normalization (e.g. legacy groups:read appears as group:read). For personal tokens this is the expanded OAuth set, not the pat:* group names.

When an endpoint requires several scopes together (AND), the API still uses any-of needed and reports one missing scope at a time. Grant that scope, retry, and the next response names the next missing scope. Do not assume needed is a complete all-of checklist.

Slack porting note: Slack returns needed / provided as comma-separated strings on missing_scope. Roam returns string arrays, and needed is explicitly any-of (matching endpoints that accept alternate scopes such as chat:send_message or chat:write). Capability parity — not wire compatibility.

Auth failures

CodeMeaningClient action
not_authedNo bearer tokenAttach a token
invalid_tokenUnknown, malformed, or expiredObtain a new token
token_revokedPermanently unusable (e.g. owner archived)Discard token; re-authenticate — do not retry

invalid_token and token_revoked also send:

WWW-Authenticate: Bearer error="invalid_token"

Common codes

CodeTypical statusMeaning
invalid_arguments / invalid_parameter / missing_parameter / invalid_json400Bad request
invalid_cursor400Pagination cursor invalid or expired — restart without a cursor
invalid_expand400Unrecognized expand= field
missing_scope403Token lacks a required scope — see needed / provided
access_mode_not_supported403Personal vs organization access cannot use this endpoint
not_in_chat / not_in_group403Caller is not a member
*_not_found (e.g. chat_not_found, user_not_found)404Resource missing or invisible
msg_too_long413Message text too long
ratelimited429Rate limit; honor Retry-After
transcript_pending404Transcript not ready; retry later
transcript_unavailable404Meeting was not transcribed; stop retrying
upstream_timeout504Upstream timeout; retry
internal_error500Unexpected server failure

Rate-limit headers

Every response carries burst-bucket headers. Retry-After is sent only on 429. See Rate Limiting for the full table.

HTTP/1.1 200 OK
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1776556801
RateLimit-Policy: "burst";q=10;w=1
RateLimit: "burst";r=7
HTTP/1.1 429 Too Many Requests
Retry-After: 10
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1776556810
RateLimit-Policy: "burst";q=10;w=1
RateLimit: "burst";r=0;t=10

{"ok":false,"error":"ratelimited"}
  • X-RateLimit-Limit — burst size (10)
  • X-RateLimit-Remaining — tokens left (010)
  • X-RateLimit-Reset — Unix epoch seconds (UTC) when another burst token is available
  • Retry-After — seconds to wait after a 429; honor this over Reset
  • RateLimit-Policy / RateLimit — IETF draft equivalents (q/w and r; t only when r is 0)

PATs add a "day" policy on RateLimit-Policy / RateLimit for the 1000/day quota. X-RateLimit-* is always the burst bucket.

Remaining is a per-process hint (appserver replicas do not share counters).

Pagination cursors

List endpoints that support cursor pagination expose:

FieldWhereMeaning
cursorrequest query/bodyResume token from a prior page
nextCursorresponsePass as cursor on the next request; omitted on the last page

Cursors are opaque. Do not parse, construct, or reuse them across endpoints or API versions. An invalid or expired cursor returns 400 with error: "invalid_cursor" — restart pagination from the beginning (no cursor).

Date-range list endpoints may still use after / before in addition to or instead of cursors; see each endpoint.

Client checklist

  1. Check HTTP status, then ok / error.
  2. Treat token_revoked as terminal for that credential.
  3. On missing_scope, use needed / provided for re-consent — do not parse prose.
  4. Echo nextCursor values unchanged; never invent cursors.
  5. Tolerate new response fields (additive changes within a dated API version).