Errors¶
The full error code catalog, with HTTP status, when each fires, and what to do.
Error envelope¶
Every error response has the same shape:
{
"error": "string_code",
"message": "Human-readable explanation",
"request_id": "UUID matching X-Revento-Request-Id"
}
Some codes carry additional fields, called out per code below.
The error string is the stable, machine-readable code. Branch on this, not on the message text. Messages may evolve; codes won't.
Always include X-Revento-Request-Id (a UUID you generate) on outgoing requests. We echo it back, log it on our side. When you contact support, hand them this UUID — investigation drops from minutes to seconds.
Authentication errors (4xx)¶
invalid_token (401)¶
Token is malformed, unknown, or expired in a non-recoverable way.
When: typo in your Authorization header, you sent a sandbox token to production (or vice versa), the token never existed.
Action: check the token bytes. If they're correct, the connection is genuinely broken — escalate.
token_expired (401)¶
Token was valid but its lifetime has passed.
{
"error": "token_expired",
"message": "Access token expired. Refresh and retry.",
"request_id": "..."
}
When: more than 1 hour since the token was issued and you didn't refresh.
Action: refresh the access token using your refresh token, retry the original request once with the new access token. See refresh tokens. Don't loop refreshes — if the refresh itself fails, that's invalid_grant (below) and means re-consent is required.
token_revoked (401)¶
Token was deliberately invalidated.
When: organizer revoked the integration, event was archived, organization lost formal status, your integration was unpublished, Revento suspended your integration, or (for user tokens) the participant revoked access from their settings.
Action: this is permanent for the affected token. Mark the installation/session revoked in your storage, stop background work for it. Don't auto-retry, don't try to refresh — refresh will also fail. If you subscribed to webhooks, you'll have already received data.deletion_required or user.revoked_access; honor the 30-day deletion deadline. Surface to your operators.
invalid_grant (400)¶
Token-exchange or refresh request failed because the grant is no longer valid.
{
"error": "invalid_grant",
"message": "Authorization code is invalid, expired, or already used.",
"request_id": "..."
}
When:
- Authorization code: unknown, already used, expired (codes live ~10 min), or doesn't match the original redirect_uri / code_verifier.
- Refresh token: family was revoked (because of reuse, deliberate revocation, or hard-cap exceeded).
Action — depends on which grant type failed:
- For authorization codes (recoverable at user level): don't retry the same code, but you may have the user start the OAuth flow over from /connect. The failure is per-attempt, not per-installation.
- For refresh tokens (permanent at integration level): don't retry — the family is dead. Mark the installation as requires_reconsent and surface a "Reconnect" CTA to the organizer/user.
These are very different failure modes; branch your error handler on which grant type was being exchanged.
invalid_client (400)¶
client_id / client_secret mismatch or unknown.
When: typo, you rotated client_secret and code still uses the old one, your sandbox/production credentials got crossed.
Action: check credentials. If they're right, escalate.
unauthorized_client (400 or 302)¶
client_id is known but the client is not allowed to use this grant type or your integration is suspended.
Returned as 400 from /oauth/token when the credentials check fails before redirect logic runs. Returned as a 302 redirect to your redirect_uri with ?error=unauthorized_client&... when the failure happens during /oauth/authorize after redirect_uri validation passes.
When: typically a suspended integration, or attempting an OAuth flow your integration isn't configured for.
Action: check your integration status in the developer console. If suspended, contact your account owner.
Authorization errors (4xx)¶
insufficient_scope (403)¶
The endpoint requires a scope your token doesn't have.
{
"error": "insufficient_scope",
"message": "This endpoint requires the participants.read scope.",
"required": ["participants.read"],
"request_id": "..."
}
Action: required lists the missing scope(s). Either request a re-consent with the additional scope, or stop calling this endpoint with this token. Don't retry — the scope set won't change without re-consent.
user_token_required / installation_token_required (403)¶
You used an installation token on a user-scoped endpoint (user_token_required), or a user token on an event-scoped endpoint (installation_token_required).
{
"error": "user_token_required",
"message": "This endpoint requires a user token.",
"request_id": "..."
}
Action: switch tokens. See endpoints reference for which token type each endpoint requires.
event_not_authorized (403)¶
The token is for a different event than the one in the URL.
When: typo in event_id path segment, or you mixed up which token belongs to which event in your storage.
Action: check your storage — installation tokens are bound to one (event, organization) tuple. Use the right token for the right event.
Authorization request errors (4xx)¶
These happen during the OAuth flow itself, not on API calls.
invalid_request (400)¶
{
"error": "invalid_request",
"message": "PKCE required: code_challenge and code_challenge_method are mandatory.",
"request_id": "..."
}
When: missing required parameter in /oauth/authorize or /oauth/token, malformed body, missing PKCE, etc.
Action: read the message field — it's specific. Fix your request shape.
invalid_scope (400)¶
{
"error": "invalid_scope",
"message": "Scope 'program.read' is not declared in your integration manifest.",
"request_id": "..."
}
When: requesting a scope your manifest doesn't declare, or that doesn't exist in the catalog.
Action: either add the scope to your manifest (and ship a re-consent flow per scopes versioning) or remove it from your authorization request.
access_denied (302 redirect with error param)¶
The user clicked "Cancel" on the consent screen.
Action: not really an error — it's the user choosing not to proceed. Show them a "We can't continue without your authorization. [Try again]" UX.
server_error (500 or redirect with error param)¶
An internal failure occurred while processing your authorization request.
Action: surface "try again later" to the user. If it persists, escalate with the request_id.
Rate limit errors (429)¶
rate_limit_exceeded¶
You hit the hourly or per-minute burst limit.
{
"error": "rate_limit_exceeded",
"message": "Rate limit hit. Retry after 42 seconds.",
"request_id": "..."
}
Response includes a Retry-After header (seconds).
Action: back off for at least Retry-After seconds, then retry. Use exponential backoff with jitter for repeated 429s. See rate limits.
concurrent_limit_exceeded¶
You have more than 5 in-flight requests for this token.
Action: wait for an in-flight request to complete (typically <1s), then retry. No Retry-After — this is a queue, not a window.
Resource errors (4xx)¶
resource_not_found (404)¶
The resource referenced in the URL doesn't exist or your token doesn't have visibility into it.
Note: "doesn't exist" and "not visible to you" return the same response. If you're sure the resource exists, your token might lack the right scope or be for a different event.
validation_error (400)¶
Query parameter or body validation failed.
{
"error": "validation_error",
"message": "Invalid value for parameter 'limit': max is 50.",
"field": "limit",
"request_id": "..."
}
The field value names the offending parameter.
Action: fix the input.
Server errors (5xx)¶
internal_error (500)¶
A server-side failure occurred.
Action: retry with backoff. If it persists, escalate with the request_id.
service_unavailable (503)¶
Maintenance or partial outage.
Action: retry after the duration in Retry-After (if present), otherwise back off.
Quick reference¶
| Code | HTTP | Meaning | Recoverable? |
|---|---|---|---|
invalid_token |
401 | Malformed/unknown | No (check bytes) |
token_expired |
401 | Expired access token | Yes (refresh) |
token_revoked |
401 | Permanently revoked | No (re-consent) |
invalid_grant (auth code) |
400 | Code is dead | Yes (user re-starts OAuth) |
invalid_grant (refresh) |
400 | Family revoked | No (re-consent) |
invalid_client |
400 | Bad credentials | No (check credentials) |
unauthorized_client |
400 | Suspended or wrong grant type | No (check status) |
insufficient_scope |
403 | Missing scope | No without re-consent |
user_token_required / installation_token_required |
403 | Mixed up token types | Yes (use correct token) |
event_not_authorized |
403 | Wrong event for token | Yes (use correct token) |
invalid_request |
400 | Malformed OAuth request | Yes (fix request) |
invalid_scope |
400 | Requested scope undeclared | No (update manifest) |
access_denied |
302 | User canceled | UX-level retry |
validation_error |
400 | Bad parameter | Yes (fix input) |
rate_limit_exceeded |
429 | Hit hourly/minute limit | Yes (back off) |
concurrent_limit_exceeded |
429 | >5 in-flight | Yes (wait briefly) |
resource_not_found |
404 | Missing or invisible | No |
internal_error |
500 | Server-side failure | Yes (retry/escalate) |
service_unavailable |
503 | Maintenance | Yes (retry after) |