Every error response has the same shape:
JSON
{
"error": {
"code": "invalid_request",
"message": "Request validation failed.",
"request_id": "req_8Hf2kLq9TzW1xYbN",
"details": {
"issues": [{ "path": "body.periods.0.end", "message": "period end must be after start", "code": "custom" }]
}
}
}codeis stable and machine-readable. Branch on it.messageexplains the problem in plain English. It may change, so don't parse it.request_idis also sent as theX-Request-Idheader on every response, including successful ones. Quote it when you contact support.detailsis only present on some errors.
Error codes
| Status | Code | Meaning | Retry? |
|---|---|---|---|
| 400 | invalid_request | The request is malformed or fails validation. details.issues lists each problem, with path like body.periods.0.end. | No: fix the request. |
| 400 | wrong_region | The API key belongs to another region. See Regions and base URLs. | No |
| 401 | unauthorized | The API key is missing, malformed, unknown or revoked. | No |
| 403 | forbidden | The application is disabled, or Google or Microsoft refused the operation (for example writing to a read-only calendar). | No |
| 403 | company_suspended | The company account is suspended. | No |
| 403 | connection_limit_reached | A new connection would pass the development limit. See Development and production. | No |
| 404 | not_found | The user, connection, session, calendar or event doesn't exist, or belongs to another application. | No |
| 409 | relink_required | The connection needs relinking. details.connection_id says which. See Connecting calendars. | After the user relinks |
| 409 | conflict | The request conflicts with current state, for example the connection was revoked or the user has no default connection. | No |
| 429 | rate_limited | Too many requests. See Limits and rate limits. | Yes, after Retry-After seconds |
| 500 | internal_error | Something went wrong on BentoCal's side. | Yes, with backoff |
| 502 | provider_unavailable | Google or Microsoft failed or timed out. | Yes, after Retry-After seconds |
New codes may be added. Treat an unknown code by its HTTP status.
Errors inside successful responses
Free/busy and availability read many calendars at once, so one failing user doesn't fail the request. They return 200 and list failures in errors, with a code of relink_required, provider_unavailable, not_found or no_connections.
Retrying safely
After a timeout or a retryable error, you can usually repeat the request:
| Call | On retry |
|---|---|
PUT /users/{user_id}/events/{event_id} | Your event id identifies the event, so a retry updates it instead of creating a second one. |
DELETE /users/{user_id}/events/{event_id} | Deleting an event that's already gone returns 204. |
DELETE /connections/{connection_id} | Revoking an already revoked connection returns 204. |
DELETE /users/{user_id} | Returns 404 not_found if the first attempt already deleted the user. Treat that as done. |
GET, POST /free_busy, POST /availability | They only read. |
PATCH /users/{user_id} | Setting the same default twice has the same result. |
POST /connect_sessions | A retry creates another session. Use the one from the response you received; unused sessions expire after 30 minutes. |
Use exponential backoff with jitter, and honour Retry-After when it's present.