Error Handling
The TrustGate API uses a consistent error response format across all endpoints. Every error response follows the same JSON envelope structure, making it straightforward to build reliable error handling in your integration.
Error Response Format
All API errors return a JSON object with an error key containing a structured error detail:
{
"error": {
"code": "MACHINE_READABLE_CODE",
"message": "Human-readable description of what went wrong",
"status": 400,
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}
| Field | Type | Description |
|---|---|---|
code | string | A machine-readable error code (e.g., UNAUTHORIZED, VALIDATION_ERROR). Use this for programmatic error handling. |
message | string | A human-readable description of the error. Suitable for logging but not guaranteed to be stable across versions. |
status | integer | The HTTP status code, mirrored inside the error body for convenience. |
request_id | string or null | A unique identifier for the request. Include this when contacting support for faster debugging. |
The request_id is assigned by TrustGate middleware on every incoming request and is included in all error responses. It is also available in the X-Request-ID response header.
Error Codes
TrustGate uses the following machine-readable error codes:
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid authentication credentials. |
INVALID_API_KEY | 401 | The provided API key does not match any active key. |
EXPIRED_API_KEY | 401 | The API key has passed its expiration date. |
PERMISSION_DENIED | 403 | The authenticated user or API key lacks the required permission. |
NOT_FOUND | 404 | The requested resource does not exist or is not accessible to your tenant. |
CONFLICT | 409 | The request conflicts with existing state (e.g., duplicate resource). |
IDEMPOTENCY_MISMATCH | 409 | An idempotency key was reused with different request parameters. |
VALIDATION_ERROR | 422 | The request body failed validation. The message field contains field-level details. |
RATE_LIMITED | 429 | Too many requests. Back off and retry after the duration indicated in the Retry-After header. |
INTERNAL_ERROR | 500 | An unexpected server error occurred. |
CIRCUIT_BREAKER_OPEN | 503 | A downstream provider is temporarily unavailable. The circuit breaker has been tripped. |
PRIMITIVE_NOT_FOUND | 404 | The requested primitive does not exist. |
PAYMENT_REQUIRED | 402 | Usage limit reached or premium feature requires a plan upgrade. |
HTTP Status Codes
400 Bad Request
The request is malformed or contains invalid parameters that do not fall under validation (422). Examples include invalid permission names when creating an API key or invalid webhook event types.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid permissions: ['nonexistent:permission']",
"status": 400,
"request_id": "a1b2c3d4-..."
}
}
401 Unauthorized
Authentication failed. The request either has no credentials or the credentials are invalid.
Missing authentication:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Missing authentication. Provide Bearer token or X-API-Key header.",
"status": 401,
"request_id": "a1b2c3d4-..."
}
}
Invalid API key:
{
"error": {
"code": "INVALID_API_KEY",
"message": "Invalid API key",
"status": 401,
"request_id": "a1b2c3d4-..."
}
}
Expired API key:
{
"error": {
"code": "EXPIRED_API_KEY",
"message": "API key has expired",
"status": 401,
"request_id": "a1b2c3d4-..."
}
}
Invalid JWT token:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid token: Signature has expired.",
"status": 401,
"request_id": "a1b2c3d4-..."
}
}
402 Payment Required
The request was authenticated but the tenant has exceeded their usage limits or is attempting to use a feature that requires a plan upgrade.
{
"error": {
"code": "PAYMENT_REQUIRED",
"message": "Monthly usage limit reached. Upgrade your plan to continue.",
"status": 402,
"request_id": "a1b2c3d4-..."
}
}
403 Forbidden
The request was authenticated, but the user or API key does not have the required permission or role.
Missing permission:
{
"error": {
"code": "PERMISSION_DENIED",
"message": "API key lacks permission to invoke primitive: screening.individual",
"status": 403,
"request_id": "a1b2c3d4-..."
}
}
Insufficient role:
{
"error": {
"code": "PERMISSION_DENIED",
"message": "Requires one of roles: admin, manager",
"status": 403,
"request_id": "a1b2c3d4-..."
}
}
Plan-gated feature:
{
"error": {
"code": "PERMISSION_DENIED",
"message": "'workflow_builder' requires business plan or higher. Current plan: developer",
"status": 403,
"request_id": "a1b2c3d4-..."
}
}
404 Not Found
The requested resource does not exist, or it belongs to a different tenant and is not accessible.
{
"error": {
"code": "NOT_FOUND",
"message": "API key not found",
"status": 404,
"request_id": "a1b2c3d4-..."
}
}
Due to tenant isolation, a 404 is returned even if the resource exists under a different tenant. This prevents information leakage about other tenants' data.
409 Conflict
The request conflicts with the current state of a resource.
Idempotency conflict:
{
"error": {
"code": "IDEMPOTENCY_MISMATCH",
"message": "Idempotency key reused with different request parameters",
"status": 409,
"request_id": "a1b2c3d4-..."
}
}
State conflict:
{
"error": {
"code": "CONFLICT",
"message": "API key already revoked",
"status": 409,
"request_id": "a1b2c3d4-..."
}
}
422 Unprocessable Entity
The request body failed schema validation. The message field contains field-level error details joined by semicolons.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "body.email: value is not a valid email address; body.first_name: Field required",
"status": 422,
"request_id": "a1b2c3d4-..."
}
}
The message format is {location}.{field}: {error_description} for each invalid field. Multiple field errors are separated by semicolons.
429 Too Many Requests
The request was rate-limited. Wait for the duration specified in the Retry-After response header before making another request.
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded",
"status": 429,
"request_id": "a1b2c3d4-..."
}
}
Rate limit headers are included in the response:
| Header | Description |
|---|---|
Retry-After | Number of seconds to wait before retrying |
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining before the limit is hit |
X-RateLimit-Reset | Unix timestamp when the rate limit window resets |
500 Internal Server Error
An unexpected error occurred on the server. In production, the message is generic to prevent information leakage. The request_id can be provided to TrustGate support for investigation.
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal server error",
"status": 500,
"request_id": "a1b2c3d4-..."
}
}
503 Service Unavailable
A downstream provider (e.g., sanctions screening service, document verification provider) is temporarily unavailable. The circuit breaker has been tripped to prevent cascading failures.
{
"error": {
"code": "CIRCUIT_BREAKER_OPEN",
"message": "Provider temporarily unavailable. Please retry shortly.",
"status": 503,
"request_id": "a1b2c3d4-..."
}
}
Error Handling Best Practices
Retry Strategy
Not all errors should be retried. Use the following guide:
| Status Code | Retry? | Strategy |
|---|---|---|
| 400 | No | Fix the request. The parameters are invalid. |
| 401 | No | Check your API key or token. Re-authenticate if using JWTs. |
| 402 | No | Upgrade your plan or wait for the billing cycle to reset. |
| 403 | No | The API key or user lacks the required permission. Update permissions. |
| 404 | No | The resource does not exist. Verify the ID and your tenant scope. |
| 409 | No | Resolve the conflict. For idempotency mismatches, use a new idempotency key. |
| 422 | No | Fix the request body. Review the field-level errors in the message. |
| 429 | Yes | Wait for the Retry-After duration, then retry. |
| 500 | Yes | Retry with exponential backoff. If persistent, contact support with the request_id. |
| 503 | Yes | Retry with exponential backoff. The downstream provider should recover shortly. |
Exponential Backoff
For retryable errors (429, 500, 503), use exponential backoff with jitter:
Python:
import time
import random
import requests
def make_request_with_retry(url, headers, max_retries=3):
for attempt in range(max_retries + 1):
response = requests.get(url, headers=headers)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
time.sleep(retry_after)
continue
if response.status_code in (500, 503):
if attempt < max_retries:
delay = (2 ** attempt) + random.uniform(0, 1)
time.sleep(delay)
continue
return response
return response # Return last response after exhausting retries
JavaScript:
async function makeRequestWithRetry(url, headers, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, { headers });
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "5", 10);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
continue;
}
if ([500, 503].includes(response.status) && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
await new Promise((r) => setTimeout(r, delay));
continue;
}
return response;
}
}
Parsing Error Responses
Always check for the error envelope in non-2xx responses:
response = requests.post(url, headers=headers, json=payload)
if not response.ok:
error_data = response.json().get("error", {})
error_code = error_data.get("code")
error_message = error_data.get("message")
request_id = error_data.get("request_id")
if error_code == "INVALID_API_KEY":
# Handle invalid key: check configuration
pass
elif error_code == "PERMISSION_DENIED":
# Handle missing permission: check API key scopes
pass
elif error_code == "RATE_LIMITED":
# Handle rate limit: implement backoff
pass
else:
# Log for debugging
print(f"API error [{error_code}]: {error_message} (request_id: {request_id})")
Using the Request ID
Every API response includes a request_id (in both the error body and the X-Request-ID response header). When contacting TrustGate support about an error, always include the request_id -- it allows the team to trace the exact request through server logs.
Idempotency Keys
For operations that should not be repeated (such as invoking a primitive), TrustGate supports idempotency keys via the Idempotency-Key header. If you replay a request with the same idempotency key and the same parameters, TrustGate returns the cached result. If you reuse the key with different parameters, you receive a 409 IDEMPOTENCY_MISMATCH error.
curl -X POST https://api.bytrustgate.com/api/v1/primitives/screening.individual/invoke \
-H "X-API-Key: sk_live_your_api_key_here" \
-H "Idempotency-Key: unique-request-id-12345" \
-H "Content-Type: application/json" \
-d '{"name": "John Doe", "date_of_birth": "1990-01-15"}'
Next Steps
- Authentication -- API authentication methods
- API Keys -- Key management reference
- Webhooks -- Real-time event notifications