Skip to main content

Errors

Every non-2xx response from the TrakRF API returns a JSON body in a consistent error envelope. This page catalogs the error types you'll see and recommends retry behavior for each.

Envelope shape

The envelope follows RFC 7807 (Problem Details for HTTP APIs) with TrakRF-specific additions:

{
"error": {
"type": "validation_error",
"title": "Invalid request",
"status": 400,
"detail": "identifier must be 1-255 characters",
"instance": "/api/v1/assets",
"request_id": "01JXXXXXXXXXXXXXXXXXXXXXXX"
}
}
FieldPurpose
typeA machine-readable identifier — your code should branch on this. Extensible enum.
titleA short human-readable summary safe to log.
statusThe HTTP status code. Always matches the response's status line.
detailA longer human-readable explanation. Safe to log; may name the offending field or value.
instanceThe request path that produced the error. Useful when the same error appears across multiple logs.
request_idA ULID matching the X-Request-ID response header. Include this when filing support tickets.

Error type catalog

typeHTTP statusWhen you'll see itRetry?
validation_error400Request body failed schema validation (see validation errors below)No — fix the request
bad_request400Malformed request — bad JSON, unknown query param, invalid sort fieldNo — fix the request
unauthorized401Missing, malformed, revoked, or expired API keyNo — re-auth
forbidden403Valid key but insufficient scope for this endpointNo — needs a key with the right scope
not_found404Natural-key lookup failedNo — check the identifier
conflict409Unique-constraint violation (typically a duplicate identifier)No — reconcile with GET then PUT
rate_limited429You've hit the rate limit — see Rate limitsYes, after Retry-After seconds
internal_error500Unhandled server failureYes, with exponential backoff

The type enum is extensible — TrakRF may add new error types in any v1 release. Clients should handle unknown type values gracefully (fall through to a generic error handler based on HTTP status code, which is a closed enum).

Validation errors

When type is validation_error, the envelope carries an additional fields array with one entry per invalid field:

{
"error": {
"type": "validation_error",
"title": "Validation failed",
"status": 400,
"detail": "Request did not pass validation",
"instance": "/api/v1/assets",
"request_id": "01JXXXXXXXXXXXXXXXXXXXXXXX",
"fields": [
{
"field": "identifier",
"code": "too_long",
"message": "identifier must be at most 255 characters"
},
{
"field": "type",
"code": "invalid_value",
"message": "type is not a valid value"
}
]
}
}

Field entries:

FieldPurpose
fieldThe JSON field name of the offending request attribute (e.g. identifier, org_name). Values are the snake_case JSON keys defined by the endpoint's request schema, not Go struct names or JSON-pointer paths.
codeA machine-readable code — your validation UI can branch on this. Extensible enum.
messageA human-readable message safe to show the end user.

Current code values (extensible):

  • required — the field is missing and mandatory
  • invalid_value — the value is not one of the allowed values, fails a format check (email, URL, UUID), or fails a validation TrakRF has not mapped to a more specific code
  • too_short — string or collection length below the minimum
  • too_long — string or collection length above the maximum
  • too_small — numeric value below the minimum
  • too_large — numeric value above the maximum

The code enum is extensible — TrakRF may add new validation codes in any v1 release. Treat unknown codes as generic invalid-value errors and surface the message field.

Query-parameter validation errors

List endpoints validate their query string the same way. The field value in the fields array is the query-parameter name, and detail summarizes the first problem. A few you'll see in practice:

{
"error": {
"type": "validation_error",
"title": "Invalid request",
"status": 400,
"detail": "limit must be ≤ 200",
"instance": "/api/v1/assets",
"request_id": "01JXXXXXXXXXXXXXXXXXXXXXXX",
"fields": [
{
"field": "limit",
"code": "too_large",
"message": "limit must be ≤ 200"
}
]
}
}
{
"error": {
"type": "validation_error",
"title": "Invalid request",
"status": 400,
"detail": "unknown sort field: bogus",
"instance": "/api/v1/assets",
"request_id": "01JXXXXXXXXXXXXXXXXXXXXXXX",
"fields": [
{
"field": "sort",
"code": "invalid_value",
"message": "unknown sort field: bogus"
}
]
}
}
{
"error": {
"type": "validation_error",
"title": "Invalid request",
"status": 400,
"detail": "Invalid 'from' timestamp; RFC3339 required",
"instance": "/api/v1/assets/ASSET-0001/history",
"request_id": "01JXXXXXXXXXXXXXXXXXXXXXXX",
"fields": [
{
"field": "from",
"code": "invalid_value",
"message": "Invalid 'from' timestamp; RFC3339 required"
}
]
}
}

The detail and message strings are stable enough to surface to an end user, but for programmatic handling branch on type and fields[].code — those are the contract.

Retry guidance

Use the retry column in the catalog above as the default, but these patterns apply more broadly:

  • 4xx other than 429: Never retry blindly. These indicate a problem with the request the server cannot fix by seeing it again. Fix the request and retry once.
  • 429: Wait Retry-After seconds, then retry. Exponential backoff with jitter is appropriate if you're hitting the limit repeatedly. See Rate limits.
  • 500: Retry with exponential backoff, starting at 1 second, doubling to ~30s, with jitter. Surface the failure after 3-5 attempts.
  • Network timeouts (no response): Retry with backoff. Treat the first attempt as "unknown state" — for idempotent methods (GET, PUT, DELETE), retry is safe. For POST, retry may create duplicates if you didn't supply an identifier; see Idempotency.

Idempotency

The TrakRF v1 API does not support the Idempotency-Key header. Retry safety comes from HTTP semantics and natural-key constraints:

  • POST /assets, POST /locations — retrying with the same identifier hits the UNIQUE(org_id, identifier) constraint and returns 409 conflict. Detect the 409, then use GET or PUT to reconcile. If you omit identifier on a POST retry, you may create duplicates — for retry-critical workflows, always supply an identifier.
  • PUT — HTTP-semantically idempotent. Safe to retry.
  • DELETE — idempotent. A second delete returns 404 not_found (not 204) so you can detect state drift; both outcomes are fine to treat as "deleted."

Explicit Idempotency-Key header support is on the v1.x roadmap if customer pain materializes.

Request IDs

Every response includes an X-Request-ID header with a ULID. The same ULID appears in the request_id field of any error envelope. When filing a support ticket, include this ID — it lets TrakRF staff find the exact request in logs without grepping.

If your client supplies an inbound X-Request-ID header, it is echoed back unchanged — TrakRF does not validate its format. Clients that supply their own IDs are encouraged to use ULIDs so log tooling remains consistent. When no inbound header is supplied, TrakRF generates a ULID server-side.

Deprecation notices

When TrakRF retires an endpoint (typically at a v2 cutover), responses from the deprecated endpoint carry two extra headers per RFC 8594:

Deprecation: true
Sunset: Wed, 11 Nov 2026 23:59:59 GMT

The minimum gap between Deprecation and Sunset is six months. After the sunset date, the endpoint returns 410 Gone. No endpoints are currently deprecated at v1 launch; this mechanism is in place so future use doesn't surprise integrators.