Skip to content

Errors & pagination

Reference

Greenlight’s REST API, MCP tools, and webhooks all use the same conventions for errors and pagination. Two pages worth of reading; both surfaces work the same way.

Error envelope

Every error response — REST, MCP, webhook delivery failure — uses this shape:

{
"error": {
"code": "FORBIDDEN",
"message": "User does not have role 'admin'.",
"request_id": "req_01HQX5MWGRTYBJ7C7C7C7C7C",
"details": { /* optional, error-specific */ }
}
}
  • code is stable across versions. Clients should switch on code, not on message.
  • message is human-readable. Greenlight reserves the right to improve wording.
  • request_id is unique per request. Including it in a support ticket gets to the root cause fastest.
  • details is present on errors that carry structured context (which field failed validation, which check failed in the policy check, etc.).

HTTP status mapping

HTTPWhen
400 BAD_REQUESTInput failed validation. details.field names the offending field.
401 UNAUTHENTICATEDNo valid token.
403 FORBIDDENAuthenticated, but the caller’s role doesn’t permit this action.
404 NOT_FOUNDThe target resource doesn’t exist or the caller can’t see it.
409 CONFLICTThe action conflicts with current state (e.g., a stale Knowledge proposal).
412 PRECONDITION_FAILEDAn If-Match or base_version check rejected the request.
429 RATE_LIMITEDThe caller’s token has hit its budget. Retry-After indicates when to retry.
500 INTERNALA platform error. The audit log captures these; report with the request_id.
503 UNAVAILABLETransient platform unavailability. Safe to retry with backoff.

Error code catalog

The most common stable codes. Not exhaustive; the full set is in the OpenAPI spec.

CodeMeaning
UNAUTHENTICATEDMissing or invalid token.
FORBIDDENAuthenticated but not authorized.
NOT_FOUNDResource missing or invisible.
INVALID_INPUTValidation failed. See details.field.
CONFLICTState conflict; retry with current state.
STALE_PROPOSALKnowledge proposal’s base_version is out of date.
POLICY_CHECK_FAILEDThe policy check rejected a change. See details.checks.
INTEGRATION_NOT_GRANTEDThe app doesn’t have access to the requested integration.
INTEGRATION_REVOKEDThe integration was revoked since the request started.
BUDGET_EXCEEDEDThe org or app exceeded its configured budget.
RATE_LIMITEDToo many requests.
INTERNALPlatform error.

Idempotency

Mutating endpoints accept an Idempotency-Key header (REST) or idempotency_key field (MCP). Requests with the same key and the same payload return the original response. Idempotency keys are scoped per token and stored for 24 hours.

Use a UUID v4. Generate a new one for each logical operation; reuse the same one for retries of the same operation.

Pagination

List endpoints accept limit (default 50, max 200) and cursor. The response includes next_cursor if more results exist.

{
"items": [ /* … */ ],
"next_cursor": "eyJsYXN0X2lkIjoiYXBwX2s5eDJtM3AifQ=="
}

To paginate forward, pass next_cursor back as cursor. To start from the beginning, omit cursor. There is no offset parameter; cursor pagination is the only mode supported.

Cursors are opaque. They encode the position needed to resume the query, but the encoding is not part of the API contract. Do not parse a cursor; do not generate one.

Stable sort

Results within a paginated list are sorted by id (which is monotonic), descending — newest first. The sort is stable across pages; an item that exists at request time will appear exactly once during a sustained pagination, even if other items are added concurrently.

Next