Error Handling
SignalStack returns consistent JSON error responses and appropriate HTTP status codes. This guide covers every error you might encounter and how to handle them in your application.
Error response format
Code
{
"error": {
"code": "INVALID_REQUEST",
"message": "The 'claim' field is required and must be a non-empty string.",
"docs": "https://signal-stack-ten.vercel.app/docs/api-reference/verify-claim",
"request_id": "vrf_a1b2c3d4e5f6"
}
}| Field | Type | Description |
|---|---|---|
error.code | string | Machine-readable error code |
error.message | string | Human-readable description of the issue |
error.docs | string (optional) | Link to relevant documentation |
error.request_id | string | Unique request identifier for support/debugging |
HTTP status codes
| Status | Code | Meaning | Action |
|---|---|---|---|
| 200 | OK | Request succeeded | — |
| 400 | INVALID_REQUEST | Malformed request body or missing required fields | Fix request payload |
| 401 | UNAUTHORIZED | Missing or invalid API key | Check Authorization header |
| 403 | FORBIDDEN | API key lacks permission for this endpoint | Upgrade plan or use correct key |
| 404 | NOT_FOUND | Endpoint or resource not found | Check URL |
| 409 | CONFLICT | Request conflicts with current state | Retry with updated state |
| 422 | VALIDATION_ERROR | Request body fails validation | Check field constraints |
| 429 | RATE_LIMITED | Rate limit exceeded | Retry after Retry-After |
| 500 | INTERNAL_ERROR | Unexpected server error | Retry with exponential backoff |
| 502 | UPSTREAM_ERROR | Upstream data source unavailable | Retry after a delay |
| 503 | SERVICE_UNAVAILABLE | Temporary service disruption | Retry with backoff |
Common error codes
| Code | HTTP Status | Typical Cause |
|---|---|---|
INVALID_REQUEST | 400 | Missing required field, invalid JSON, wrong type |
UNAUTHORIZED | 401 | Missing/wrong Authorization header, revoked key |
FORBIDDEN | 403 | Key lacks endpoint access (e.g. media on Starter plan) |
VALIDATION_ERROR | 422 | URL format invalid, claim too long, source unsupported |
INSUFFICIENT_CREDITS | 402 | Account has insufficient credits for the request |
RATE_LIMITED | 429 | Request rate exceeds plan limit |
SOURCE_UNAVAILABLE | 502 | Requested data source is temporarily unreachable |
CONTENT_TOO_LARGE | 413 | Document/media content exceeds size limit (50 MB) |
Rate limiting headers
When you hit a rate limit (429), the response includes these headers:
Code
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700000000
Retry-After: 3600| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per window |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
Retry-After | Seconds to wait before retrying |
Retry strategies
Use exponential backoff with jitter for retries:
Code
# Python — exponential backoff with jitter
import time
import random
def verify_with_retry(client, max_retries=5):
for attempt in range(max_retries):
try:
return client.verify.claim(claim="...")
except signalstack.RateLimitError as e:
wait = (2 ** attempt) + random.uniform(0, 1)
time.sleep(min(wait, 60))
except signalstack.ServerError as e:
if attempt == max_retries - 1:
raise
wait = (2 ** attempt) + random.uniform(0, 1)
time.sleep(min(wait, 120))
raise signalstack.MaxRetriesExceeded()Code
// TypeScript — retry with backoff
async function verifyWithRetry(
client: SignalStack,
options: VerifyClaimOptions,
maxRetries = 5,
) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await client.verify.claim(options)
} catch (err) {
if (err instanceof RateLimitError) {
const wait = Math.min(2 ** attempt + Math.random(), 60)
await sleep(wait * 1000)
} else if (err instanceof ServerError) {
if (attempt === maxRetries - 1) throw err
const wait = Math.min(2 ** attempt + Math.random(), 120)
await sleep(wait * 1000)
} else {
throw err
}
}
}
throw new MaxRetriesExceeded()
}Which errors are safe to retry?
| Status | Safe to retry? | Notes |
|---|---|---|
| 429 | Yes | Use Retry-After header or exponential backoff |
| 500 | Yes | Internal error, retry with backoff (max 3) |
| 502 | Yes | Upstream source unavailable, retry with backoff |
| 503 | Yes | Service unavailable, retry with backoff |
| 400 | No | Fix request payload before retrying |
| 401 | No | Fix API key before retrying |
| 403 | No | Upgrade plan or use correct key |
| 422 | No | Fix validation errors in request |
Next steps
- Review Rate Limits by plan tier
- Explore the Error Code Reference
- Set up Webhooks for async processing