Error Handling Guide¶
This guide explains how to handle errors when working with the Centrali API and SDK.
Error Response Format¶
All Centrali API errors follow a consistent JSON format:
{
"error": "Error Type",
"message": "Human-readable error description",
"details": [
{
"field": "fieldName",
"message": "Field-specific error message"
}
],
"code": "ERROR_CODE",
"requestId": "req_abc123"
}
Fields: - error: Short error type (e.g., "Validation Error", "Unauthorized") - message: Detailed error description - details: Array of field-level errors (for validation failures) - code: Machine-readable error code (optional) - requestId: Unique request identifier for debugging
HTTP Status Codes¶
2xx - Success¶
200 OK¶
Request succeeded.
201 Created¶
Resource created successfully.
204 No Content¶
Request succeeded with no response body (e.g., delete operations).
4xx - Client Errors¶
400 Bad Request - Validation Failed¶
Cause: Request data doesn't match schema requirements.
Response:
{
"error": "Validation Failed",
"message": "The request contains invalid data",
"details": [
{
"field": "email",
"message": "Invalid email format"
},
{
"field": "price",
"message": "Must be greater than or equal to 0"
}
]
}
Solution: - Check field types match structure definition - Verify required fields are present - Validate format constraints (email, URL, etc.) - Check min/max constraints for numbers and strings
Example:
try {
await centrali.createRecord('User', {
email: 'invalid-email', // ❌ Invalid format
age: -5 // ❌ Negative number
});
} catch (error) {
if (error.response?.status === 400) {
console.error('Validation errors:', error.response.data.details);
// Output: [{ field: 'email', message: 'Invalid email format' }, ...]
}
}
401 Unauthorized - Invalid or Missing Token¶
Cause: Missing, invalid, or expired authentication token.
Response:
Common Causes: 1. Missing Authorization header
# ❌ Wrong
curl https://api.centrali.io/data/workspace/my-workspace/api/v1/structures
# ✅ Correct
curl https://api.centrali.io/data/workspace/my-workspace/api/v1/structures \
-H "Authorization: Bearer YOUR_TOKEN"
- Token expired (lifetime: 7 hours)
- Fetch a new token from
/oidc/token -
SDK handles this automatically
-
Invalid credentials
- Verify
client_idandclient_secret - Check for extra spaces or newlines
Solution:
// With SDK (handles automatically)
const centrali = new CentraliSDK({
clientId: process.env.CENTRALI_CLIENT_ID,
clientSecret: process.env.CENTRALI_CLIENT_SECRET,
// ... other config
});
// Manual handling
async function fetchWithAuth(url) {
try {
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.status === 401) {
// Token expired - fetch new one
token = await getNewToken();
// Retry request
return fetch(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
}
return response;
} catch (error) {
console.error('Auth error:', error);
}
}
403 Forbidden - Insufficient Permissions¶
Cause: Token is valid but lacks required permissions.
Response:
Solution: - Verify service account has required permissions - Check workspace access - Ensure you're using the correct workspace credentials
404 Not Found - Resource Doesn't Exist¶
Cause: Requested resource doesn't exist or you don't have access.
Response:
Common Scenarios: - Structure ID doesn't exist - Record ID doesn't exist - Wrong workspace - Resource was deleted
Solution:
try {
const record = await centrali.getRecord('Product', 'rec_nonexistent');
} catch (error) {
if (error.response?.status === 404) {
console.error('Record not found');
// Handle gracefully (show error message, redirect, etc.)
}
}
409 Conflict - Resource Already Exists¶
Cause: Attempting to create a resource that violates a uniqueness constraint.
Response:
{
"error": "Conflict",
"message": "A record with slug 'my-product' already exists",
"details": [
{
"field": "slug",
"message": "Value must be unique"
}
]
}
Solution: - Use a different value for unique fields - Check if resource exists before creating - Use update instead of create
422 Unprocessable Entity - Business Logic Error¶
Cause: Request is valid but cannot be processed due to business logic.
Response:
Example Scenarios: - Deleting a structure that has records - Circular reference in relationships - Violating custom validation rules
429 Too Many Requests - Rate Limit Exceeded¶
Cause: Exceeded API rate limits.
Response:
Headers:
Solution:
async function makeRequestWithRetry(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.response?.status === 429) {
const retryAfter = error.response.headers['retry-after'] || 60;
console.log(`Rate limited. Retrying in ${retryAfter}s...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
See Limits & Quotas for rate limit details.
5xx - Server Errors¶
500 Internal Server Error¶
Cause: Unexpected server error.
Response:
{
"error": "Internal Server Error",
"message": "An unexpected error occurred",
"requestId": "req_abc123"
}
Solution: - Retry the request (may be transient) - Check Centrali status page - Contact support with requestId if persistent
503 Service Unavailable¶
Cause: Service is temporarily unavailable (maintenance, high load).
Response:
Solution: - Implement exponential backoff retry logic - Check status page for scheduled maintenance
async function exponentialBackoff(fn, maxRetries = 5) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.response?.status === 503 && i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s, 8s, 16s
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}
SDK Error Handling¶
The Centrali SDK provides typed error objects:
import { CentraliSDK } from '@centrali-io/centrali-sdk';
const centrali = new CentraliSDK({ /* config */ });
try {
const product = await centrali.createRecord('Product', {
name: 'Widget',
price: 'invalid' // ❌ Wrong type
});
} catch (error) {
// HTTP status code
console.log('Status:', error.response?.status);
// Error details
console.log('Error:', error.response?.data);
// Handle specific errors
if (error.response?.status === 400) {
console.error('Validation errors:', error.response.data.details);
} else if (error.response?.status === 401) {
console.error('Authentication failed');
} else if (error.response?.status === 404) {
console.error('Resource not found');
} else {
console.error('Unexpected error:', error.message);
}
}
Best Practices¶
1. Always Handle Errors¶
// ❌ Bad - Silent failure
const record = await centrali.createRecord('Product', data);
// ✅ Good - Explicit error handling
try {
const record = await centrali.createRecord('Product', data);
console.log('Created:', record.id);
} catch (error) {
console.error('Failed to create record:', error.message);
// Show user-friendly error message
// Log error for debugging
// Retry if appropriate
}
2. Provide User-Friendly Messages¶
try {
await centrali.createRecord('Product', formData);
showSuccess('Product created successfully!');
} catch (error) {
if (error.response?.status === 400) {
// Show specific validation errors
showErrors(error.response.data.details);
} else if (error.response?.status === 409) {
showError('A product with this SKU already exists');
} else {
showError('Failed to create product. Please try again.');
}
}
3. Log Errors for Debugging¶
try {
await centrali.createRecord('Product', data);
} catch (error) {
// Log full error details
console.error('Create product failed:', {
status: error.response?.status,
error: error.response?.data,
requestId: error.response?.data?.requestId,
data: data
});
// Or use error tracking service
Sentry.captureException(error, {
extra: {
requestId: error.response?.data?.requestId,
workspace: 'my-workspace'
}
});
}
4. Implement Retry Logic for Transient Errors¶
async function createRecordWithRetry(structure, data, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await centrali.createRecord(structure, data);
} catch (error) {
const status = error.response?.status;
// Retry on 5xx errors or rate limits
if ((status >= 500 || status === 429) && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
console.log(`Attempt ${attempt} failed. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// Don't retry client errors (4xx except 429)
throw error;
}
}
}
5. Validate Before Sending¶
// Validate on client before API call
function validateProduct(data) {
const errors = [];
if (!data.name || data.name.trim().length === 0) {
errors.push({ field: 'name', message: 'Name is required' });
}
if (typeof data.price !== 'number' || data.price < 0) {
errors.push({ field: 'price', message: 'Price must be a positive number' });
}
if (!/^[a-z0-9-]+$/.test(data.slug)) {
errors.push({ field: 'slug', message: 'Slug must be lowercase alphanumeric' });
}
return errors;
}
// Use before API call
const errors = validateProduct(formData);
if (errors.length > 0) {
showErrors(errors);
return;
}
// Now safe to send to API
await centrali.createRecord('Product', formData);
Common Error Scenarios¶
Scenario 1: Creating a Record with Invalid Data¶
try {
await centrali.createRecord('Product', {
name: '', // ❌ Required field empty
price: -10, // ❌ Negative price
email: 'not-an-email' // ❌ Invalid format
});
} catch (error) {
// Status: 400
// Error: Validation Failed
// Details: [
// { field: 'name', message: 'Required field' },
// { field: 'price', message: 'Must be >= 0' },
// { field: 'email', message: 'Invalid email format' }
// ]
}
Scenario 2: Token Expired Mid-Session¶
// SDK handles this automatically!
const centrali = new CentraliSDK({ clientId, clientSecret, ...});
// Even after 7 hours, SDK refetches token
const record = await centrali.getRecord('Product', 'rec_123'); // ✅ Works
Scenario 3: Network Timeout¶
try {
const record = await centrali.createRecord('Product', data);
} catch (error) {
if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
console.error('Request timed out. Please check your connection.');
// Retry or show network error message
}
}
Related Documentation¶
- Troubleshooting Guide - Common issues and solutions
- Limits & Quotas - Rate limits and constraints
- SDK Guide - Using the official SDK
- API Reference - Complete API documentation
Summary¶
Key Takeaways: 1. Always wrap API calls in try/catch 2. Handle specific error codes (400, 401, 404, etc.) 3. Provide user-friendly error messages 4. Log errors with requestId for debugging 5. Implement retry logic for transient errors (5xx, 429) 6. Use the SDK - it handles auth and retry automatically!