Conventions & Errors
Pagination, tenancy headers and error shapes.
Every endpoint follows the same conventions, which keeps client code small and predictable.
Base URL and tenancy
https://api.introzen.com/api/Workspace-scoped endpoints require the X-Tenant-ID header. The value only selects a workspace — membership is verified server-side before any query runs.
-H "X-Tenant-ID: 42"Pagination
List endpoints use DRF limit/offset pagination with a default page size of 20.
| Field | Type | Description |
|---|---|---|
| limit | integer | Maximum records to return. |
| offset | integer | Records to skip. |
{
"count": 137,
"next": "https://api.introzen.com/api/pipeline/pipelines/?limit=20&offset=20",
"previous": null,
"results": []
}Timestamps
All timestamps are ISO-8601 in UTC, with the timezone in USE_TZ enabled. Convert to local time for display.
{ "started_at": "2026-10-04T09:12:44.512Z" }Error envelope
Every failure - validation, authentication, permission, throttling, a missing record, or an unexpected server exception - is rendered into one stable envelope. Branch on error.code; never parse the prose in error.message.
{
"error": {
"code": "validation_error",
"message": "Enter a valid email address.",
"fields": { "email": ["Enter a valid email address."] },
"request_id": "b1f0c4d5e6a7483192c3d4e5f6071829"
}
}Envelope fields
| Field | Type | Description |
|---|---|---|
| code | string | Stable machine-readable identifier. Always present. This is the only value you should branch on. |
| message | string | Human copy, safe to show verbatim in a toast or an inline hint. |
| fields | object | Field-keyed arrays keyed by serializer field name. Present only when the failure is field-scoped, so forms can highlight the offending input. |
| request_id | string | Correlates this response with the server log line. See Request correlation. |
| retry_after | integer | Seconds to wait before retrying. Present on 429 and 503 responses only. |
Error codes
Codes are grouped by intent. Request codes describe a failure in the call itself; domain codes describe a business rule the call violated and each one has a known recovery path.
Request codes
| Field | Type | Description |
|---|---|---|
| validation_error | 400 | A serializer rejected the payload. Inspect fields. |
| bad_request | 400 | The request was malformed in a way no field describes. |
| parse_error | 400 | The body could not be parsed as JSON or multipart. |
| not_authenticated | 401 | No usable session. Refresh once, then sign in. |
| authentication_failed | 401 | Credentials were present but rejected. |
| permission_denied | 403 | Authenticated, but not permitted for this resource. |
| not_found | 404 | No such record, or no dataset has been produced yet. |
| method_not_allowed | 405 | The route exists but not for this HTTP method. |
| conflict | 409 | The request collided with the current state. |
| payment_required | 402 | The active plan does not entitle this capability. |
| throttled | 429 | Rate limited. Honour retry_after. |
| internal_error | 500 | Unexpected server fault. Quote request_id to support. |
| service_unavailable | 503 | A dependency (broker, cache or database) is down. Honour retry_after. |
| database_unavailable | 503 | The database is unreachable or unhealthy. Transient: retry after retry_after seconds. |
| dependency_unavailable | 503 | A transport-level dependency failed (cache, broker, object store, upstream HTTP). Retry after retry_after seconds. |
Domain codes
| Field | Type | Description |
|---|---|---|
| invalid_workspace | 400 | X-Tenant-ID was missing or not a number. |
| workspace_unresolved | 400 | No workspace could be resolved for the session. |
| workspace_access_denied | 403 | The session is not a member of that workspace. |
| workspace_not_found | 404 | The workspace does not exist or is not visible to you. |
| member_not_found | 404 | No such workspace member. |
| member_already_owner | 400 | The requested member already owns the workspace. |
| plan_not_found | 404 | The plan identifier does not exist. |
| paid_plan_provisioned | 409 | A paid plan is already active; change it instead of re-subscribing. |
| unsupported_fields | 400 | Read-only fields were sent on a write. |
| pipeline_already_running | 409 | A run for this pipeline is already in flight. |
| execution_not_cancellable | 409 | The run already reached a terminal state. |
| no_successful_execution | 404 | Charts and reports need at least one successful run. |
| dataset_unavailable | 410 | The stored result was pruned. Re-run the pipeline. |
| invalid_pagination | 400 | limit or offset was not a positive integer. |
| file_required | 400 | A multipart upload arrived with no file part. |
| unsupported_file_type | 400 | Only .csv, .xlsx and .xls are accepted. |
| file_unreadable | 400 | The file was corrupt, encrypted or password protected. |
| account_not_found | 404 | Staff-only: no such account. |
| self_deactivation | 400 | Staff-only: you cannot deactivate your own account. |
| refresh_token_missing | 401 | The refresh cookie was absent. |
| refresh_token_invalid | 401 | The refresh token was expired, reused or revoked. |
| invalid_reset_token | 400 | The password-reset link is invalid or has expired. |
Request correlation
Every response carries an X-Request-ID header, and the same value is echoed as error.request_id. A sane X-Request-ID you send is reused, so a correlation id can span the browser, the API and your own gateway.
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
X-Request-ID: b1f0c4d5e6a7483192c3d4e5f6071829Status codes
| Field | Type | Description |
|---|---|---|
| 200 | string | Success. |
| 201 | string | Created. |
| 400 | string | Validation failed. Inspect the body. |
| 401 | string | Missing or expired session. Refresh and retry once. |
| 403 | string | Authenticated but not permitted for this workspace. |
| 404 | string | Not found, or no dataset exists yet. |
| 429 | string | Rate limited. Back off and retry. |
| 503 | string | Upstream dependency (database or broker) unavailable. |
Last reviewed October 2026.