Error Handling
The Turris Public API uses conventional HTTP response codes and returns detailed error information to help you handle issues gracefully.HTTP Status Codes
Error Response Format
All error responses follow a consistent format:Response Fields
Error Types
The most common values. The full enum is on every endpoint’s error schema in the API reference.unauthorized
Authentication failed or was not provided.
- Missing
Authorizationheader - Expired JWT token
- Invalid token format
forbidden
Authentication succeeded but the request is not allowed.
- IP not in allowlist (for Restricted Access Tokens)
- Insufficient permissions for the requested resource
forbidden is not the only 403. Calling the other persona’s surface returns invalid_organization_category, a missing entitlement returns product_feature_subscription_required, and a carrier at its associated-agency ceiling returns invite_limit_reached. Each has its own section below, because the fix for each is different. Branch on errorType.invalid_organization_category
A 403 carrying this errorType means the credential is valid and the surface is wrong. A credential belongs to exactly one persona and none reaches both: a carrier credential reaches /v1/* and /v2/upstream/*, an agency credential reaches /v2/downstream/*.
- An agency credential on a persona-scoped
/v1/*route, or on/v2/upstream/*(the four/auth/*and/heartbeatroutes carry no persona and serve either surface) - A carrier credential on
/v2/downstream/* - One base URL reused for both surfaces
GET /v2/auth/test-oauth: the response carries orgCategory, upstreamEntity or downstreamEntity (the enum also holds two enterprise values for surfaces that are not routable yet, so match rather than negate). Which API is mine? has the routing rules and the rest of the 403 breakdown.
product_feature_subscription_required
A 403 carrying this errorType means your credentials are fine and the entitlement is not. Entitlement is re-checked on every request, not only when the credential is created, so this can start appearing for an integration that worked yesterday.
Three distinct causes, and the errorMessage names which one applies:
The third case is the one worth reading carefully: it is a single endpoint being unavailable, not your integration being broken.
GET /v2/upstream/webhooks needs the Webhook Settings feature, for example, while the rest of the carrier API does not.
This applies to carriers and agencies alike, on every version. Each area of the API needs the same product feature as the matching screen in the Turris application, so the mapping is the one you already know from the UI: if you can see a section, its endpoints are available to your credential. Getting Started and Getting Started for Agencies list it per endpoint group.
Entitlement is cached briefly, so a feature that has just been switched on can take up to a minute to take effect. Requesting a new token does not shorten that: the cache is keyed on your organization, not on the token.
invite_limit_reached
A 403 carrying this errorType is a plan limit, not a permission problem. It is raised on the carrier association-create endpoints, POST /v2/upstream/downstream-entity-associations/add and POST /v2/upstream/downstream-entity-associations/invite (POST /v1/downstream-entity-associations/add and /invite on v1), when the organization already holds as many live associated agencies as its plan allows.
validation_error
Request validation failed.
- Missing required fields
- Invalid field formats
- Mutually exclusive parameters provided together
not_found
The requested resource doesn’t exist.
A 404 also covers “exists, but is not yours.” On the agency surface, an id belonging to another organization returns 404 rather than 403 — a 403 would confirm the record exists, which is the same disclosure in a politer envelope. So a 404 on an id you believe is yours is worth checking against List Licensed Entities: the usual cause is a credential minted lower in your organization tree than you expected.
internal_server_error
Something went wrong on our servers.
If you receive a 500 error, please contact support with the
requestId for investigation.Handling Errors
Example: JavaScript/TypeScript
Example: Python
Best Practices
Always check the status code
Always check the status code
Don’t assume requests succeed. Check the HTTP status code and handle errors appropriately.
Log the requestId
Log the requestId
The
requestId is essential for debugging. Log it when errors occur so you can reference it when contacting support.Implement retry logic
Implement retry logic
For transient errors (429, 500, 503), implement exponential backoff retry logic.
Handle token expiration gracefully
Handle token expiration gracefully
OAuth tokens expire after 60 minutes. Implement proactive token refresh or handle 401 errors by refreshing and retrying.