SDK
Error Handling
Typed errors, retry strategy and 429 handling in the OptimoCMS TypeScript SDK.
Error Handling
The SDK throws typed errors that you can catch and handle.
OptimoCMSError
All API errors are wrapped in an OptimoCMSError:
import { OptimoCMS, OptimoCMSError } from '@optimocms/sdk';
const cms = new OptimoCMS({ apiKey: process.env.OPTIMOCMS_API_KEY! });
try {
const page = await cms.pages.get('site_abc123', 'invalid-id');
} catch (error) {
if (error instanceof OptimoCMSError) {
console.error('Status:', error.status); // 404
console.error('Code:', error.code); // "NOT_FOUND"
console.error('Message:', error.message); // "Page not found"
console.error('Request ID:', error.requestId); // "req_abc123"
}
}Error properties
| Property | Type | Description |
|---|---|---|
status | number | HTTP status code (400, 401, 403, 404, 409, 429, 500) |
code | string | Machine-readable error code |
message | string | Human-readable error message |
requestId | string | Unique request ID for debugging |
details | object | undefined | Additional details (e.g. validation errors) |
Error codes
| Status | Code | When |
|---|---|---|
400 | BAD_REQUEST | Invalid request body or parameters |
400 | VALIDATION_ERROR | Fields don't pass validation rules |
401 | UNAUTHORIZED | Missing or invalid API key |
403 | FORBIDDEN | Key is missing the required scope |
404 | NOT_FOUND | Resource doesn't exist |
409 | CONFLICT | Slug conflict or duplicate resource |
429 | RATE_LIMIT_EXCEEDED | Too many requests |
500 | INTERNAL_ERROR | Server-side error |
Specific error handling
Validation errors
try {
await cms.pages.create('site_abc123', {
title: '', // too short
slug: 'a b c', // invalid characters
});
} catch (error) {
if (error instanceof OptimoCMSError && error.code === 'VALIDATION_ERROR') {
console.error('Validation errors:', error.details);
// { fields: { title: "must be at least 1 character", slug: "invalid characters" } }
}
}404 Not Found
try {
const page = await cms.pages.get('site_abc123', 'page_nonexistent');
} catch (error) {
if (error instanceof OptimoCMSError && error.status === 404) {
console.log('Page not found, creating a new one');
await cms.pages.create('site_abc123', {
title: 'New Page',
slug: 'new-page',
});
}
}409 Conflict
try {
await cms.pages.create('site_abc123', {
title: 'About Us',
slug: 'about-us', // slug already exists
});
} catch (error) {
if (error instanceof OptimoCMSError && error.status === 409) {
console.log('Slug already exists, use a different one');
}
}Automatic retry
The SDK automatically retries on 429 (rate limit) and 5xx (server errors) with exponential backoff.
Default behavior
| Attempt | Wait time |
|---|---|
| 1st retry | 1 second |
| 2nd retry | 2 seconds |
| 3rd retry | 4 seconds |
After maxRetries attempts the SDK throws the error.
Configure retry
const cms = new OptimoCMS({
apiKey: process.env.OPTIMOCMS_API_KEY!,
// More retries for batch operations
maxRetries: 5,
});Disable retry
const cms = new OptimoCMS({
apiKey: process.env.OPTIMOCMS_API_KEY!,
maxRetries: 0, // no retry
});Which errors are retried?
| Status | Retry? | Reason |
|---|---|---|
429 | Yes | Rate limit — waits for Retry-After header |
500 | Yes | Server error — transient issue |
502 | Yes | Bad gateway |
503 | Yes | Service unavailable |
504 | Yes | Gateway timeout |
400 | No | Client error — retry won't help |
401 | No | Auth error — fix your API key |
403 | No | Scope error — add scopes |
404 | No | Resource doesn't exist |
409 | No | Conflict — change the data |
Rate limit handling
On a 429 response the SDK reads the Retry-After header and waits exactly that long:
// The SDK does this automatically:
// 1. Request → 429 (Retry-After: 45)
// 2. Wait 45 seconds
// 3. Retry request
// 4. 200 OK ✓For manual rate limit handling:
const cms = new OptimoCMS({
apiKey: process.env.OPTIMOCMS_API_KEY!,
maxRetries: 0, // manual control
});
try {
await cms.sites.list();
} catch (error) {
if (error instanceof OptimoCMSError && error.status === 429) {
const retryAfter = error.details?.retryAfter ?? 60;
console.log(`Rate limited. Waiting ${retryAfter} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
await cms.sites.list(); // retry
}
}Network errors
On timeout or network errors the SDK throws a standard Error (not OptimoCMSError):
try {
await cms.sites.list();
} catch (error) {
if (error instanceof OptimoCMSError) {
// API error (4xx/5xx)
} else if (error instanceof Error) {
// Network/timeout error
console.error('Network error:', error.message);
}
}Complete example
import { OptimoCMS, OptimoCMSError } from '@optimocms/sdk';
const cms = new OptimoCMS({
apiKey: process.env.OPTIMOCMS_API_KEY!,
maxRetries: 3,
timeout: 15_000,
});
async function updatePageSafely(siteId: string, pageId: string, title: string) {
try {
const result = await cms.pages.update(siteId, pageId, { title });
console.log(`Page updated: ${result.data.title}`);
return result.data;
} catch (error) {
if (!(error instanceof OptimoCMSError)) {
console.error('Network error:', error);
throw error;
}
switch (error.status) {
case 401:
throw new Error('API key is invalid. Check your configuration.');
case 403:
throw new Error(`Missing scope: ${error.message}`);
case 404:
console.log('Page not found, skipping');
return null;
case 429:
throw new Error('Rate limit reached after all retries');
default:
throw error;
}
}
}Try it — curl equivalent:
# Error responses have the same JSON structure
curl -i https://api.optimocms.com/v1/sites/site_abc123/pages/invalid-id \
-H "X-Api-Key: your_api_key"
# HTTP/1.1 404 Not Found
# {"error":{"code":"NOT_FOUND","message":"Page not found"},"meta":{"requestId":"req_abc123",...}}Try it — MCP:
Get page "invalid-id" from site site_abc123.
→ The MCP server returns a clear error message.Next steps
- Pagination — Fetch large datasets
- Authentication — Scopes and rate limits
- API Reference — All endpoints