Skip to main content
When something goes wrong, the Topo API always returns a structured JSON error body alongside an appropriate HTTP status code. The error shape is the same regardless of the endpoint or error type, so you can build a single error-handling layer for your entire integration.

Error response shape

Every error response body contains the following fields:
status_code
integer
required
The HTTP status code of the response (e.g. 404, 422, 429). Mirrors the HTTP status on the response itself.
type
string
required
A stable string identifier for the error category (e.g. NotFoundIssue). See the taxonomy below. This value is frozen — it will not be renamed in /v1.
message
string
A human-readable description of what went wrong. Useful for logging and debugging; do not rely on the exact wording in application logic.
data
object
Optional structured detail about the error — for example, which field failed validation or what the applicable rate limit is.
request_id
string
A unique identifier for this specific request, generated by Topo’s API gateway. Always include this value when contacting support — it’s the fastest way to locate your request in our logs.
trace_id
string
Internal trace identifier. Present in non-production environments for debugging.
span_id
string
Internal span identifier. Present in non-production environments for debugging.
Example error response
{
  "status_code": 404,
  "type": "NotFoundIssue",
  "message": "Contact not found",
  "request_id": "req_01j9kx3m7p0000000000000000"
}

Error taxonomy

The type field uses a frozen set of values. Branch first on status_code, then use type as a stable refinement for more specific handling.
The type identifiers listed below are frozen and will not be renamed in /v1. Adding a new error type is an additive change; renaming an existing one is breaking and would require /v2.
typeHTTP statusWhen it occurs
ValidationIssue400The request body or query parameters failed validation — a required field is missing, a value is the wrong type, etc.
UnauthorizedIssue401The Authorization header is missing, malformed, or contains an invalid key
UnauthorizedIssue403The API key is valid but lacks the required scope for this endpoint
NotFoundIssue404The requested resource does not exist or does not belong to your workspace
CommitIssue409The operation conflicted with the current state of the resource — for example, a concurrent modification or uniqueness constraint
BusinessIssue422The operation is not allowed given the current state — for example, enrolling a contact who is already excluded
RateLimitIssue429Your organization or API key has exceeded a rate limit
InternalServerIssue500An unexpected error occurred on Topo’s side

ValidationIssue (400)

Returned when the request itself is malformed. Check the message and data fields for specifics about which field or parameter is invalid.
{
  "status_code": 400,
  "type": "ValidationIssue",
  "message": "Request validation failed",
  "data": {
    "field": "email",
    "issue": "value is not a valid email address"
  },
  "request_id": "req_01j9kx3m7p0000000000000000"
}

UnauthorizedIssue (401 / 403)

A 401 means no valid key was provided; a 403 means the key is valid but does not have the required scope. Check the data.required field to see which scope is needed.
{
  "status_code": 403,
  "type": "UnauthorizedIssue",
  "message": "API key lacks required scope",
  "data": {
    "required": "contacts:write"
  },
  "request_id": "req_01j9kx3m7p0000000000000000"
}

NotFoundIssue (404)

Returned when a resource ID does not exist or belongs to a different workspace. Topo deliberately returns 404 (rather than 403) for cross-workspace resources to avoid leaking the existence of records.

RateLimitIssue (429)

Returned when your organization exceeds the allowed request rate. See the rate limits section below for how to handle this response.

Rate limits

Topo enforces rate limits per organization across three time windows simultaneously: per second, per minute, and per day. All three windows are active on every request. Every API response includes headers that tell you your current rate limit state:
HeaderDescription
X-RateLimit-Limit-secondMaximum requests allowed per second
X-RateLimit-Remaining-secondRequests remaining in the current second
X-RateLimit-Reset-secondUnix timestamp when the second window resets
X-RateLimit-Limit-minuteMaximum requests allowed per minute
X-RateLimit-Remaining-minuteRequests remaining in the current minute
X-RateLimit-Reset-minuteUnix timestamp when the minute window resets
X-RateLimit-Limit-dayMaximum requests allowed per day
X-RateLimit-Remaining-dayRequests remaining in the current day
X-RateLimit-Reset-dayUnix timestamp when the day window resets
Retry-AfterSeconds to wait before retrying (present only on 429 responses)
When a rate limit is exceeded, the API returns 429 with a RateLimitIssue error body:
{
  "status_code": 429,
  "type": "RateLimitIssue",
  "message": "Rate limit exceeded",
  "data": {
    "limit": 60,
    "reset_at": 1737043200,
    "window_seconds": 60
  },
  "request_id": "req_01j9kx3m7p0000000000000000"
}

Handling rate limits

1

Read the Retry-After header

On a 429 response, read the Retry-After header value (in seconds) and wait at least that long before retrying.
2

Apply exponential backoff

If you continue to receive 429 responses, increase your wait time exponentially with jitter to avoid synchronized retries across parallel workers.
3

Monitor remaining capacity proactively

Watch X-RateLimit-Remaining-minute on successful responses. If it drops near zero, slow down your request rate before hitting the limit.
Repeatedly sending requests after receiving a 429 will not speed up the reset — it will only consume capacity from subsequent windows. Always respect the Retry-After value.
Additionally, repeated authentication failures (e.g. using an invalid key in a loop) will trigger a temporary block on that key’s token hash. Ensure your key is correct before automating requests.

Debugging tips

Always log the request_id from every API response — successful or failed. When you contact Topo support, providing the request_id lets the team locate your exact request instantly.
  • Branch on status_code first for broad categories (auth, not found, server error), then use type to distinguish sub-cases like 401 vs 403.
  • Don’t match on message — the human-readable message text is for display only and may change over time. Use type for logic.
  • Log data for validation errors — it contains field-level detail that helps you identify what to fix in your request.
  • 5xx errors are Topo-side — if you receive a 500 InternalServerIssue, the issue is not with your request. Retry with backoff and contact support if it persists, providing the request_id.