Errors
Vision errors are designed for safe diagnosis without revealing whether a credential, private record, grant, key, or policy object exists. Read the target App's declared schema; the preview contracts do not yet share one identical envelope.
Envelope
{
"error": {
"code": "invalid_token",
"message": "A valid access token is required.",
"requestId": "req_...",
"retryable": false
}
}
The Vision Core resource envelope carries a stable code, safe message, request ID, and server retry decision. OAuth token exchange is a protocol exception and returns OAuth-compatible error and optional error_description fields instead.
Status codes
- Name
400- Description
- Malformed or unsupported request.
- Name
401- Description
Missing, invalid, expired, or revoked authentication.
- Name
403- Description
Valid identity without the required scope or entitlement.
- Name
404- Description
Resource unavailable or intentionally non-disclosed.
- Name
429- Description
A shared rate policy denied the request. Respect
Retry-After.
- Name
503- Description
A required dependency is unavailable. Retry only when
retryableis true.
409 and 422 are also used by Apps that expose stateful commands, optimistic versions, idempotency conflicts, or domain validation. The operation page is authoritative for the statuses it can return.
App differences
While the platform is in preview, Apps return one of three envelopes. Each carries a stable code, a safe message, a retry decision and a correlation ID.
| App | Shape | Codes | Correlation |
|---|---|---|---|
| Vision Core | { "error": { code, message, requestId, retryable } } | lowercase, e.g. invalid_token, access_denied, rate_limited | x-request-id, format req_... |
| Token endpoint | { "error", "error_description" } (OAuth 2.0) | invalid_request, invalid_client, invalid_scope, invalid_target, unsupported_grant_type, temporarily_unavailable | x-request-id |
| CRM, To-Do, Calendar | flat { schemaVersion, status: "failed", code, message, retryable, correlationId, fieldIssues[] }; CRM and Calendar add conflict | UPPERCASE, e.g. INVALID_REQUEST, PERMISSION_DENIED, STALE_VERSION, IDEMPOTENCY_CONFLICT | X-Correlation-ID (send a UUID; it is echoed) |
| Accounting, Directory | wrapped { "error": { schemaVersion, code, message, requestId, retryable, fieldIssues[] } } | lowercase, e.g. invalid_request, permission_denied, stale_version, idempotency_conflict | X-Request-ID (send a UUID; it is echoed) |
A robust client reads body.error ?? body, then code, retryable and whichever of requestId or correlationId is present:
Normalise any Vision error
export function visionError(status: number, body: any, headers: Headers) {
const e =
body?.error && typeof body.error === 'object' ? body.error : (body ?? {})
return {
status,
code: String(e.code ?? e.error ?? 'unknown').toLowerCase(),
message: e.message ?? e.error_description ?? '',
retryable: e.retryable === true || status === 429,
fieldIssues: e.fieldIssues ?? [],
requestId:
e.requestId ??
e.correlationId ??
headers.get('x-request-id') ??
headers.get('x-correlation-id'),
}
}
fieldIssues entries are { path, code, message? }; path names the offending field, for example cursor with code invalid_cursor.
Retry decisions
| Failure | Default action |
|---|---|
| Invalid request or field issue | Correct the request; do not retry unchanged |
| Invalid or expired token | Obtain a valid token once; stop if the replacement is denied |
| Missing scope, entitlement, role, or record permission | Change the approved grant or workflow; do not retry |
| Not found or non-disclosed | Stop unless the business workflow explicitly permits later reconciliation |
| Idempotency or stale-version conflict | Read current state and reconcile; do not blind-retry |
429 with Retry-After | Wait for the supplied interval, add jitter, remain inside a bounded retry budget |
Dependency unavailable and retryable: true | Retry with exponential backoff and a total deadline |
| Client timeout on a mutation | Treat outcome as unknown; reconcile by idempotency key or receipt |
Handling example
Structured Vision API error
type VisionErrorBody = {
error?: {
code?: string
message?: string
requestId?: string
retryable?: boolean
details?: unknown
}
}
export class VisionApiError extends Error {
constructor(
readonly status: number,
readonly code: string,
readonly requestId: string | null,
readonly retryable: boolean,
message: string,
) {
super(message)
}
}
async function parseVisionResponse(response: Response) {
const requestId = response.headers.get('x-request-id')
const body = (await response.json().catch(() => ({}))) as VisionErrorBody
if (response.ok) return body
throw new VisionApiError(
response.status,
body.error?.code ?? 'unparseable_error',
body.error?.requestId ?? requestId,
body.error?.retryable ?? false,
body.error?.message ?? 'The Vision API request failed.',
)
}
Do not mark every 5xx retryable in client code. The resource server's decision and the operation's idempotency semantics matter.
Troubleshooting
Capture the environment, App, contract version, operation, status, stable error code, safe request/correlation ID, and timestamp. Confirm the exact origin, token audience, App entitlement, workspace, scope, and identity class. For validation errors, compare field casing, media type, enum values, nullability, timestamp offsets, and unknown properties against the operation schema.
Never send a client secret, bearer token, webhook secret, private key, cookie, complete private payload, or production database identifier in support material.
Request IDs
Vision Core returns x-request-id; CRM commonly exposes a UUID correlation ID in its error model. Preserve the identifier exactly as returned. A client-generated identifier is useful only when the server accepts and echoes it.