Intempt Docs
Developer DocsAPI Reference

HTTP Error Codes Reference

The HTTP status codes, error response shapes, and per-service error codes you'll see calling the Intempt API, plus how they differ by service.

Overview

Every Intempt platform service sits behind a single gateway at api.intempt.com/v1, but the services behind it don't all return errors in the same shape. This reference covers the status codes you'll see, the two error response formats in use across the platform, and the named error codes each service documents, so you can write one error handler that branches correctly instead of assuming a single format everywhere.

📘 Good to know

See Authentication for how requests are authenticated before they reach a service, and Project Limits for the requests-per-minute cap tied to your organization's tier.

Standard HTTP status codes

StatusMeaningWhere you'll see it
200SuccessAll services
201Resource createdauth, single-metadata
202Accepted, processing asyncaudit (exports)
204Success, no contentauth, payments, single-metadata (typically DELETE)
303See Other (redirect)auth (OAuth/SSO callback flows)
307Temporary redirectauth (OAuth/SSO callback flows)
400Invalid request body or parametersAll services
401Missing or invalid authenticationAll services
402Payment required (insufficient balance, spend cap exceeded)payments only
403Insufficient permissions, or a policy blocks the actionauth, audit, single-metadata, metric, payments, audience-service
404Resource not foundauth, payments, audit, single-metadata, metric, audience-service
409Conflict (duplicate operation, blocked state)auth, payments
422Validation error (field constraints)payments
429Rate limitedGateway (all services), and payments at the application level
500Internal server errorpayments, single-metadata, metric (unhandled exceptions)
501Not implemented (intentional stub)auth (two SCIM group endpoints only, to avoid IdP error loops)
503Dependent service unavailablepayments (Stripe or metric service down)

Error response formats

Two different error body shapes exist across the platform. Which one you get depends on which service handled the request, not on the status code.

Format A: { error, message, details }

Used by auth, audit, and payments. All three define the same ErrorResponse schema in their OpenAPI specs: a machine-readable error string, a human-readable message, and an optional details object.

{
  "error": "invalid_cursor",
  "message": "The cursor value is malformed or expired.",
  "details": null
}

A payments example, showing details populated:

{
  "error": "insufficient_balance",
  "message": "Wallet balance is too low for this operation",
  "details": {
    "required": 500,
    "available": 120
  }
}

The auth service's OAuth endpoints (/oauth/token, /oauth/revoke) are the one exception inside this group: on a 400, they return a narrower OAuthErrorResponse instead, { error, error_description }, matching the OAuth 2.0 spec's own error shape rather than the platform's general one.

Format B: { errors: [{ message }] }

Used by single-metadata, metric, and audience-service. All three throw a shared IntemptException type that carries a list of messages, not a single machine-readable code:

{
  "errors": [
    { "message": "Brand abc-123 does not exist" }
  ]
}

errors is always an array, since a single request can fail more than one validation check at once:

{
  "errors": [
    { "message": "Name is required" },
    { "message": "Color must be a hex value" }
  ]
}

There's no error code or details field in this format. Don't parse or switch on the message string; it's meant for display, not for branching logic.

single-metadata has one more variant for DELETE requests blocked by existing relations:

{
  "description": "Deletion cannot be performed because the entity has relations",
  "relations": [
    { "type": "JOURNEY", "id": "j_123", "name": "Welcome flow" }
  ]
}

Empty-body errors

Some 401 and 403 responses carry no body at all. In single-metadata and metric, a 401 (from Spring Security's JWT validation) and a 403 (from a legacy ForbiddenException, kept for backward compatibility rather than used in new code) both return an empty body, not the errors array shown above. Check the status code first; don't assume a body is always present.

Rate limiting (429)

Rate limiting happens at two different layers, with two different response shapes.

At the gateway, requests to routes marked rate-limited (a majority of route groups, including auth, payments, metric, single-metadata, integration, and several others) are checked against a Redis-backed token bucket before they ever reach the upstream service. If the bucket is empty, the gateway itself returns:

HTTP/1.1 429 Too Many Requests

(empty body)

with these response headers:

X-RateLimit-Replenish-Rate: 30
X-RateLimit-Burst-Capacity: 50
X-RateLimit-Requested-Tokens: 1

The upstream service never sees the request in this case, so there's no error/message body to parse, only the status and headers.

Inside payments, a 429 can also come from the service itself (not the gateway), and in that case it uses payments' own Format A error body like any other payments error.

Your organization's plan tier sets the requests-per-minute cap the gateway enforces; see Project Limits for the exact numbers per tier.

Per-service error codes

Auth

CodeStatusContext
authorization_pending, access_denied, expired_token, invalid_grant, invalid_request, unsupported_grant_type400OAuth device-flow and token-exchange errors, returned as OAuthErrorResponse

Other auth errors (missing auth, forbidden, conflict) use Format A but the OpenAPI spec documents them by status and description rather than a fixed code list, since error is service-generated per failure.

Audit

CodeStatusDescription
invalid_parameter400A query parameter is out of range, e.g. limit above 200
unauthorized401Missing or invalid Bearer token
forbidden403Missing the required permission on the audit_log resource
event_not_found404No audit event exists with the given ID

Payments

Wallet errors

CodeStatusDescription
insufficient_balance402Wallet balance is too low for the requested operation
subscription_paused409Cannot deduct while the subscription is paused
SPEND_CAP_EXCEEDED402Org-level or member-level monthly spend cap would be exceeded
attribute_budget_exceeded402Monthly budget for this AI attribute is exhausted
attribute_budget_paused402This AI attribute has been paused (budget exceeded)
already_refunded409The transaction has already been refunded
transaction_not_found404No transaction found with the given ID
operation_not_found404The operationKey does not exist in the catalog

Subscription errors

CodeStatusDescription
no_active_subscription404The org does not have an active subscription
subscription_not_found404No subscription cache found for the org
invalid_seat_type400The seat type is not valid for the current tier
minimum_seats400Cannot remove the last full seat
feature_disabled404The requested feature flag is not enabled

single-metadata, metric, audience-service

These three services don't define a fixed error code list. They return the errors[].message array shown under Format B above, with the message text generated per failure rather than drawn from an enum.

Retry guidance

  • 4xx errors aren't retryable. Fix the request instead.
  • 429, gateway-level: back off and retry, using the X-RateLimit-* headers to pace your next request.
  • 503 (payments): the circuit breaker for a dependent service (Stripe or metric) is open. Retry after 30 seconds.
  • 500: retry with exponential backoff, up to 3 attempts.

Use cases

  1. Writing one API client wrapper that checks the response shape (error/message/details vs. errors[]) before deciding how to surface an error to a user, since the field names differ by service.
  2. Detecting gateway-level rate limiting by checking for a 429 with an empty body and the X-RateLimit-* headers, versus an application-level 429 from payments that has a JSON body.
  3. Handling 402 insufficient_balance from payments by prompting the org to top up credits, using the details.required and details.available fields to show exactly how much is missing.
  4. Handling a 400 deletion-blocked response from single-metadata by listing the blocking relations to the user instead of just showing the generic description string.
  5. Retrying a payments call after a 503, waiting the documented 30 seconds before the next attempt instead of retrying immediately.
  6. Parsing an OAuth 400 from the auth service's token or revoke endpoints using error/error_description, not the platform's general error/message/details shape.
  7. Distinguishing a payments 422 validation error (field constraints) from a 400 (malformed request) when deciding whether to show a form-field error or a generic failure message.
  8. Treating a bodyless 401/403 from single-metadata or metric as a pure status-code check, since there's no message field to log or display in that case.

Where to go next

  • Authentication for how API keys and JWTs are validated before a request reaches any service.
  • Project Limits for the per-tier requests-per-minute cap enforced at the gateway.
  • Track Data for a worked example of the 200/400 responses on a specific endpoint.

On this page