Error Codes
Every error the TrustRails API can return — status codes, error codes, response formats, and patterns for handling them in production.
Overview
The TrustRails API uses standard HTTP status codes to indicate request outcomes. Every error response includes a machine-readable code field and a human-readable message to help you diagnose and resolve issues quickly.
code field for programmatic error handling rather than parsing the message string, which may change.Error Response Format
All error responses follow a consistent JSON structure:
{ "error": "Error Type", "message": "A human-readable description of what went wrong", "code": "ERROR_CODE"}Validation errors include an additional details array with field-level information:
{ "error": "Validation Error", "message": "Invalid request body", "details": [ { "code": "invalid_type", "path": ["participant", "email"], "message": "Expected string, received number" } ]}Rate limit errors include additional fields to help you implement backoff logic:
{ "error": "Too Many Requests", "code": "RATE_LIMIT_EXCEEDED", "limit": 1000, "remaining": 0, "retryAfter": 42}HTTP Status Codes
The API uses these HTTP status codes across all endpoints:
| Code | Status | When It Occurs |
|---|---|---|
200 | OK | Request succeeded (reads, updates, deletes) |
201 | Created | Resource successfully created (POST) |
400 | Bad Request | Malformed request or invalid parameters |
401 | Unauthorized | Missing, invalid, or revoked API key |
403 | Forbidden | IP not allowed, permission denied, or inactive custodian |
404 | Not Found | Resource does not exist or is not accessible |
409 | Conflict | State conflict, duplicate, or reversal already in progress |
422 | Unprocessable Entity | Request body fails schema validation |
429 | Too Many Requests | Rate limit exceeded for your tier |
500 | Internal Server Error | Unexpected server-side error |
503 | Service Unavailable | Maintenance mode or dependent service unavailable |
Authentication Errors
These errors occur during API key validation before your request reaches the endpoint.
401 Unauthorized
| Code | Cause | Resolution |
|---|---|---|
MISSING_API_KEY | No Authorization or X-API-Key header | Add your API key to the request header |
INVALID_FORMAT | API key doesn't match expected format | Verify key starts with tr_test_ or tr_live_ |
NOT_FOUND | API key not recognized | Check for typos or generate a new key |
INACTIVE | API key has been deactivated | Contact your account administrator |
REVOKED | API key was revoked (e.g., after rotation) | Use your current active key |
403 Forbidden
| Code | Cause | Resolution |
|---|---|---|
IP_NOT_ALLOWED | Request IP not in your allowlist | Add the server IP to your allowlist in the portal |
CUSTODIAN_INACTIVE | Your custodian account is inactive | Contact TrustRails support |
PERMISSION_DENIED | Key lacks required permission for this action | Request elevated permissions or use a different key |
// Example: Missing API key// HTTP 401{ "error": "Unauthorized", "message": "API key is required. Include it in the Authorization header as 'Bearer <key>' or in the X-API-Key header.", "code": "MISSING_API_KEY"}Rate Limit Errors
Rate limits are applied per API key based on your integration tier. Every response includes rate limit headers so you can track your usage proactively.
Rate Limits by Tier
| Tier | Limit | Typical Use |
|---|---|---|
| Tier 1 | 1,000 req/min | Financial institutions |
| Tier 2 | 500 req/min | HR platforms |
| Tier 3 | 100 req/min | Basic / trial integrations |
Rate limit headers are included on every response, not just errors:
X-RateLimit-Limit: 1000 # Your tier's per-minute limitX-RateLimit-Remaining: 997 # Requests remaining in current windowX-RateLimit-Reset: 1714300800 # Unix timestamp when the window resetsRetry-After: 42 # Seconds to wait (only on 429 responses)// HTTP 429{ "error": "Too Many Requests", "code": "RATE_LIMIT_EXCEEDED", "limit": 1000, "remaining": 0, "retryAfter": 42}Validation Errors
Request body and query parameter validation errors return a details array with field-level information. These errors use HTTP 400 or 422 depending on the endpoint.
// HTTP 422 - Invalid request body{ "error": "Validation Error", "message": "Invalid request body", "details": [ { "code": "invalid_type", "path": ["participant", "email"], "message": "Expected string, received number" }, { "code": "too_small", "path": ["participant", "firstName"], "message": "String must contain at least 1 character(s)" } ]}path array maps directly to the nested field in your request body. Use it to highlight specific form fields in your UI.Resource Errors
These errors occur when looking up specific resources like rollovers, partners, or plans.
404 Not Found
| Code | Context |
|---|---|
ROLLOVER_NOT_FOUND | Rollover ID doesn't exist or belongs to another custodian |
CUSTODIAN_NOT_FOUND | Custodian ID not registered in the system |
PLAN_NOT_FOUND | No plan found for the given EIN and plan number |
409 Conflict
| Code | Context |
|---|---|
ALREADY_REVERSED | This rollover has already been reversed |
REVERSAL_IN_PROGRESS | A reversal is already pending for this rollover |
Rollover Errors
Errors specific to rollover creation, state transitions, and reversals.
Creation Errors (400/503)
| Code | Cause |
|---|---|
CUSTODIAN_NOT_FOUND | Source or destination custodian not registered (returns 400 during creation, 404 on direct lookup) |
VAULT_UNAVAILABLE | Encryption service unavailable (retry later) |
ROLLOVER_CREATE_FAILED | Internal error during rollover creation |
Reversal Errors (400/403/409)
| Code | Cause |
|---|---|
ROLLOVER_NOT_COMPLETED | Rollover must be in completed state to reverse |
IS_REVERSAL | Cannot reverse a rollover that is itself a reversal |
OUTSIDE_REVERSAL_WINDOW | Reversal window (60 days) has passed |
NOT_INVOLVED | Your custodian is not the source or destination |
IS_INITIATOR | Cannot approve your own reversal request |
Webhook Errors
Errors that can occur when configuring or testing webhooks.
| Status | Cause | Resolution |
|---|---|---|
400 | URL must use HTTPS | Provide an HTTPS endpoint URL |
400 | Invalid event type in subscription | Use valid event types (e.g., rollover.started, settlement.funds_sent) |
400 | Test delivery failed | Verify your endpoint is reachable and returns 2xx |
secret for HMAC signature verification is returned only once in the response. Store it securely immediately.Service Errors
Server-side errors that may require retrying your request.
500 / 503 Errors
| Code | Cause | Action |
|---|---|---|
VAULT_UNAVAILABLE | Encryption service temporarily down | Retry with exponential backoff |
MAINTENANCE_MODE | Scheduled maintenance in progress | Wait for Retry-After header value |
SERVICE_UNAVAILABLE | Dependent service unreachable | Retry with exponential backoff |
// HTTP 503 - Maintenance{ "error": "Service Unavailable", "code": "MAINTENANCE_MODE", "requestId": "req_a1b2c3d4", "retryAfter": 3600}Handling Errors
Best practices for building resilient integrations that handle errors gracefully.
Use Exponential Backoff for Retries
For 429 and 5xx errors, implement exponential backoff with jitter to avoid thundering herd problems.
async function apiRequest(url, options, maxRetries = 3) { for (let attempt = 0; attempt <= maxRetries; attempt++) { const response = await fetch(url, options); if (response.ok) return response.json(); // Don't retry client errors (except 429) if (response.status < 500 && response.status !== 429) { const error = await response.json(); throw new Error(`${error.code}: ${error.message}`); } if (attempt < maxRetries) { const retryAfter = response.headers.get('Retry-After'); const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.min(1000 * 2 ** attempt, 30000) + Math.random() * 1000; await new Promise(r => setTimeout(r, delay)); } } throw new Error('Max retries exceeded');}Monitor Rate Limit Headers Proactively
Check X-RateLimit-Remaining on every response. If it drops below 10% of your limit, slow down requests before hitting a 429.
Handle Validation Errors with the Details Array
Map the path field to your form inputs for inline error display. The code field in each detail uses standard validation codes (e.g., invalid_type, too_small, invalid_string).
Log the Error Code, Not Just the Message
Always log the code field for debugging. Error messages may be updated for clarity, but codes remain stable across API versions.
Ready to Get Started?
Explore more guides or request sandbox access to start building on TrustRails