Errors and troubleshooting
Every API failure uses one structured envelope. Branch on code, show message where appropriate, log request_id, and attach fields to form controls.
Error envelope
{
"error": {
"code": "validation_failed",
"message": "The request could not be validated.",
"fields": { "recurrence": ["is invalid"] },
"request_id": "F4..."
}
}
| Field | Use |
|---|---|
| code | Stable machine-readable branch key |
| message | Concise diagnostic suitable for logs and most developer UI |
| fields | Optional map of field names to validation messages |
| request_id | Support and server-log correlation; may be null |
Status and code reference
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_json | The framework adapter could not parse the request or webhook JSON body. |
| 401 | unauthorized / context_required / signature_required / signature_invalid | A bearer credential, application user context, or valid webhook signature is missing. |
| 402 | free_tier_limit_exceeded | New billable work would exceed the remaining Developer allowance. |
| 403 | forbidden / context_forbidden | The actor or framework authorize callback denied the operation. |
| 403 | component_not_enabled | The project has not activated the required component. |
| 403 | self_approval_forbidden | The matched policy does not permit the requester to approve. |
| 404 | not_found / route_not_found | The resource or framework proxy route does not exist or is not visible. |
| 405 | method_not_allowed / operation_not_allowed | The webhook method or browser proxy operation is not allowed. |
| 409 | conflict | An idempotency key was reused with different input or another resource conflict occurred. |
| 409 | invalid_state / already_decided | The lifecycle command is no longer valid or the actor already decided. |
| 422 | validation_failed / invalid_decision / action_not_declared | Input fails domain rules or no server handler is declared for a delivered action. |
SDK behavior
The JavaScript SDK throws IncldError
with status, code, fields, and requestId; it has authentication, forbidden, not-found, and validation subclasses. Go returns a typed *APIError. Elixir returns {:error, %Incld.APIError{}}. Network and timeout failures remain native transport errors.
try {
await incld.schedules.create(input, { idempotencyKey })
} catch (error) {
if (error instanceof ValidationError) setErrors(error.fields)
else if (error instanceof IncldError) log(error.requestId, error.code)
else throw error
}
Fast diagnostics
| Symptom | Check |
|---|---|
| Browser receives unauthorized | resolveContext must return trusted user and organization ids; verify the app session before mounting the adapter. |
| context_forbidden | Remove external_user_id, requester_id, approver_id, actor_id, or viewer_id from browser input; the adapter owns them. |
| component_not_enabled | The first project receives Developer automatically; an additional project needs a paid component before its API key can use that route. |
| free_tier_limit_exceeded | Upgrade that component or wait for the UTC monthly reset. Existing data and non-billable actions remain available. |
| Webhook signature_invalid | Read the raw body exactly once, use the webhook secret rather than API key, and confirm clock skew is below five minutes. |
| action_not_declared | Register the stable identifier in defineActions and run syncActions during deploy/startup. |
| Repeated side effects | Persist Incld-Idempotency-Key before doing work and return the stored result on retries. |
| Unexpected empty pages | Pass meta.next_cursor unchanged; do not invent or decode cursors. |