OptimoCMSDocs
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

PropertyTypeDescription
statusnumberHTTP status code (400, 401, 403, 404, 409, 429, 500)
codestringMachine-readable error code
messagestringHuman-readable error message
requestIdstringUnique request ID for debugging
detailsobject | undefinedAdditional details (e.g. validation errors)

Error codes

StatusCodeWhen
400BAD_REQUESTInvalid request body or parameters
400VALIDATION_ERRORFields don't pass validation rules
401UNAUTHORIZEDMissing or invalid API key
403FORBIDDENKey is missing the required scope
404NOT_FOUNDResource doesn't exist
409CONFLICTSlug conflict or duplicate resource
429RATE_LIMIT_EXCEEDEDToo many requests
500INTERNAL_ERRORServer-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

AttemptWait time
1st retry1 second
2nd retry2 seconds
3rd retry4 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?

StatusRetry?Reason
429YesRate limit — waits for Retry-After header
500YesServer error — transient issue
502YesBad gateway
503YesService unavailable
504YesGateway timeout
400NoClient error — retry won't help
401NoAuth error — fix your API key
403NoScope error — add scopes
404NoResource doesn't exist
409NoConflict — 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

On this page