Reference
Error Handling
Response envelope format, HTTP status codes, common errors, and retry strategies.
Every PacSpace API response follows a consistent envelope format. This page covers the response structure, status codes, common errors, and how to implement retries.
Response Envelope
Success
json
{
"statusCode": 200,
"data": {
"...": "response payload"
}
}
Error
json
{
"statusCode": 400,
"error": "Bad Request",
"message": "Validation failed: 'items' must be a non-empty array."
}
All responses include a statusCode field. Successful responses contain a data field. Error responses contain error and message fields.
HTTP Status Codes
| Code | Meaning | When It Happens |
|---|---|---|
200 | OK | Request succeeded |
201 | Created | Resource created successfully |
202 | Accepted | Request accepted for async processing (writes, facts) |
400 | Bad Request | Invalid request body, missing fields, or validation failure |
401 | Unauthorized | Missing, invalid, or expired authentication |
402 | Payment Required | Insufficient credits to complete the operation |
403 | Forbidden | Valid auth but insufficient permissions |
404 | Not Found | Resource doesn't exist |
409 | Conflict | Resource already exists or state conflict |
422 | Unprocessable Entity | Request is well-formed but semantically invalid |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Something went wrong on our end |
502 | Bad Gateway | Upstream service temporarily unavailable |
503 | Service Unavailable | PacSpace is temporarily down for maintenance |
Common Errors
| Error | Status | Cause | Fix |
|---|---|---|---|
Invalid API key | 401 | The X-Api-Key header is missing or malformed | Check your API key format: pk_{env}_{id}.{secret} |
API key disabled | 401 | The key has been toggled off | Re-enable the key in the dashboard |
JWT expired | 401 | The dashboard access token has expired | Log in again to get a new token |
Insufficient credits | 402 | Your credit balance is too low | Top up credits in the dashboard |
Validation failed | 400 | Required fields are missing or invalid | Check the error message for specific field details |
Record not found | 404 | The record key or resource ID doesn't exist | Verify the key/ID is correct |
Environment not provisioned | 403 | You haven't activated the required environment | Provision it via POST /dashboard/contracts/provision |
Rate limit exceeded | 429 | Too many requests in a short period | Implement exponential backoff (see below) |
Internal server error | 500 | An unexpected error occurred | Retry with backoff; contact support if persistent |
Rate Limits
PacSpace applies rate limits per API key to ensure fair usage:
| Tier | Requests per second | Burst |
|---|---|---|
| Sandbox | 10 req/s | 20 |
| Production | 100 req/s | 200 |
| Enterprise | Custom | Custom |
When rate limited, the response includes a Retry-After header indicating how many seconds to wait.
Retry Strategy
For transient errors (429, 500, 502, 503), implement exponential backoff with jitter:
javascript
async function fetchWithRetry(url, options, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
// Don't retry client errors (except 429)
if (response.ok) {
return response.json();
}
if (response.status === 429 || response.status >= 500) {
const retryAfter = response.headers.get('Retry-After');
const baseDelay = retryAfter
? parseInt(retryAfter, 10) * 1000
: Math.min(1000 * Math.pow(2, attempt), 30000);
// Add jitter: ±25% randomization
const jitter = baseDelay * 0.25 * (Math.random() * 2 - 1);
const delay = Math.max(0, baseDelay + jitter);
console.log(`Retry ${attempt + 1}/${maxRetries} in ${Math.round(delay)}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// Non-retryable error
const error = await response.json();
throw new Error(`PacSpace ${response.status}: ${error.message}`);
} catch (err) {
if (attempt === maxRetries - 1) throw err;
// Network errors are retryable
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
Retry Rules
| Status | Retry? | Strategy |
|---|---|---|
400 | No | Fix the request |
401 | No | Fix authentication |
402 | No | Top up credits |
403 | No | Check permissions |
404 | No | Check the resource ID |
429 | Yes | Use Retry-After header |
500 | Yes | Exponential backoff |
502 | Yes | Exponential backoff |
503 | Yes | Exponential backoff |
Best Practices
- Always check
statusCode— Don't assume success based on HTTP status alone - Log error responses — The
messagefield contains actionable details - Implement retries for 5xx — Transient errors resolve quickly
- Respect rate limits — Use the
Retry-Afterheader when present - Set reasonable timeouts — 30 seconds for writes, 10 seconds for reads
- Monitor 402 errors — Set up balance alerts to avoid credit exhaustion
Was this page helpful?
Last updated February 11, 2026