> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getcatalog.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Codes

> Complete reference for Catalog API error codes and how to handle them

The Catalog API uses standard HTTP status codes and returns detailed error information to help you troubleshoot issues quickly.

## Error response format

All error responses follow a consistent structure:

```json theme={null}
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description",
    "request_id": "req_truncated"
  }
}
```

Additional fields may be included at the root level for specific error types. For example, validation errors may include additional context:

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "One or more URLs have invalid format",
    "request_id": "req_abc123"
  },
  "invalid_urls": [
    "Index 0: https://invalid-url - invalid format"
  ]
}
```

All error responses include a `Request-ID` header that matches the `request_id` in the response body. Use this ID when contacting support.

## HTTP status codes

| Status Code | Meaning               | Description                                                   |
| :---------- | :-------------------- | :------------------------------------------------------------ |
| `200`       | Success               | Request completed successfully                                |
| `400`       | Bad Request           | Invalid request parameters or malformed JSON                  |
| `401`       | Unauthorized          | Missing or invalid API key                                    |
| `402`       | Payment Required      | Insufficient credits to complete the request                  |
| `403`       | Forbidden             | API key lacks required permissions                            |
| `404`       | Not Found             | Requested resource does not exist                             |
| `409`       | Conflict              | Resource state conflict (e.g., operation already in progress) |
| `429`       | Too Many Requests     | Rate limit exceeded                                           |
| `500`       | Internal Server Error | Server-side error occurred                                    |

## Error codes reference

### Authentication errors

| Code           | HTTP Status | Description                | Resolution                                                            |
| :------------- | :---------- | :------------------------- | :-------------------------------------------------------------------- |
| `UNAUTHORIZED` | 401         | Missing or invalid API key | Verify your API key is correct and included in the `x-api-key` header |
| `FORBIDDEN`    | 403         | API key access denied      | Contact support to verify your API key permissions                    |

### Request validation errors

| Code               | HTTP Status | Description                                       | Resolution                                                                                   |
| :----------------- | :---------- | :------------------------------------------------ | :------------------------------------------------------------------------------------------- |
| `INVALID_REQUEST`  | 400         | Invalid request format or missing required fields | Check the request format and ensure all required parameters are included                     |
| `VALIDATION_ERROR` | 400         | Request format is valid but values are invalid    | Check parameter values against endpoint requirements (e.g., URL format, array length limits) |

### Resource errors

| Code        | HTTP Status | Description             | Resolution                                                                                                            |
| :---------- | :---------- | :---------------------- | :-------------------------------------------------------------------------------------------------------------------- |
| `NOT_FOUND` | 404         | Resource not found      | Verify the endpoint URL and any resource IDs (e.g., execution IDs)                                                    |
| `CONFLICT`  | 409         | Resource state conflict | The requested operation conflicts with the current state (e.g., trying to cancel an execution that's already stopped) |

### Billing errors

| Code                   | HTTP Status | Description                                | Resolution                                                                                                                |
| :--------------------- | :---------- | :----------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
| `INSUFFICIENT_CREDITS` | 402         | Not enough credits to complete the request | Add credits to your account or enable auto top-up. The response includes `credits_needed` and `credits_remaining` fields. |

### Rate limiting errors

| Code           | HTTP Status | Description                         | Resolution                                                                                                                                                  |
| :------------- | :---------- | :---------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `RATE_LIMITED` | 429         | Too many requests in a short window | Respect `Retry-After` header, pace requests using `RateLimit-Remaining`, and back off (exponential recommended). Contact support if you need higher limits. |

### Server errors

| Code             | HTTP Status | Description             | Resolution                                                              |
| :--------------- | :---------- | :---------------------- | :---------------------------------------------------------------------- |
| `INTERNAL_ERROR` | 500         | Unexpected server error | Retry the request; if persistent, contact support with the `request_id` |

## Handling errors

### Basic error handling

<CodeGroup>
  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.getcatalog.ai/v1/search', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': 'YOUR_API_KEY'
    },
    body: JSON.stringify({ query: 'black dress' })
  });

  if (!response.ok) {
    const error = await response.json();
    const requestId = response.headers.get('Request-ID') || error.error?.request_id;
    
    switch (response.status) {
      case 401:
        console.error('Authentication failed:', error.error.message);
        break;
      case 402:
        console.error('Insufficient credits:', error.error.message);
        if (error.credits_needed && error.credits_remaining) {
          console.error(`Need ${error.credits_needed}, have ${error.credits_remaining}`);
        }
        break;
      case 403:
        console.error('Access forbidden:', error.error.message);
        break;
      case 404:
        console.error('Resource not found:', error.error.message);
        break;
      case 409:
        console.error('Conflict:', error.error.message);
        break;
      case 429:
        const retryAfter = response.headers.get('Retry-After');
        console.error('Rate limited. Retry after:', retryAfter);
        break;
      case 500:
        console.error('Server error:', error.error.message);
        console.error('Request ID:', requestId);
        break;
      default:
        console.error('Request failed:', error);
    }
    return;
  }

  const data = await response.json();
  ```

  ```python Python theme={null}
  import requests
  from requests.exceptions import HTTPError

  try:
      response = requests.post(
          'https://api.getcatalog.ai/v1/search',
          headers={
              'Content-Type': 'application/json',
              'x-api-key': 'YOUR_API_KEY'
          },
          json={'query': 'black dress'}
      )
      response.raise_for_status()
      data = response.json()
      
  except HTTPError as e:
      error = e.response.json()
      request_id = e.response.headers.get('Request-ID') or error.get('error', {}).get('request_id')
      
      if e.response.status_code == 401:
          print('Authentication failed')
      elif e.response.status_code == 402:
          print('Insufficient credits')
          if 'credits_needed' in error and 'credits_remaining' in error:
              print(f"Need {error['credits_needed']}, have {error['credits_remaining']}")
      elif e.response.status_code == 403:
          print('Access forbidden')
      elif e.response.status_code == 404:
          print('Resource not found')
      elif e.response.status_code == 409:
          print('Conflict:', error.get('error', {}).get('message'))
      elif e.response.status_code == 429:
          retry_after = e.response.headers.get('Retry-After')
          print(f'Rate limited. Please wait {retry_after} seconds before retrying.')
      elif e.response.status_code >= 500:
          print('Server error. Please retry.')
          print(f'Request ID: {request_id}')
      else:
          print(f'Request failed: {error}')
  ```
</CodeGroup>

### Retry with exponential backoff

For transient errors (402, 429, 500), implement exponential backoff:

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function requestWithRetry(url, options, maxRetries = 3) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const response = await fetch(url, options);
      
      if (response.ok) {
        return response.json();
      }
      
      const error = await response.json();
      
      // Don't retry client errors (except 402 for credits)
      if (response.status === 402) {
        // Insufficient credits - don't retry, user needs to add credits
        throw new Error(`Insufficient credits: ${error.error.message}`);
      }
      
      if (response.status === 429 || response.status >= 500) {
        const retryAfter = response.headers.get('Retry-After');
        const delay = retryAfter 
          ? parseInt(retryAfter) * 1000 
          : Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
        
        console.log(`Retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      
      // Non-retryable error
      throw new Error(`Request failed: ${response.status} - ${error.error.message}`);
    }
    
    throw new Error('Max retries exceeded');
  }
  ```

  ```python Python theme={null}
  import time
  import requests

  def request_with_retry(url, headers, json_data, max_retries=3):
      for attempt in range(max_retries):
          response = requests.post(url, headers=headers, json=json_data)
          
          if response.ok:
              return response.json()
          
          error = response.json()
          
          # Don't retry client errors (except 402 for credits)
          if response.status_code == 402:
              # Insufficient credits - don't retry, user needs to add credits
              raise Exception(f"Insufficient credits: {error['error']['message']}")
          
          if response.status_code in [429, 500, 502, 503, 504]:
              retry_after = response.headers.get('Retry-After')
              delay = int(retry_after) if retry_after else (2 ** attempt)  # 1s, 2s, 4s
              print(f'Retrying in {delay}s...')
              time.sleep(delay)
              continue
          
          # Non-retryable error
          response.raise_for_status()
      
      raise Exception('Max retries exceeded')
  ```
</CodeGroup>

## Error response examples

### Insufficient Credits (402)

```json theme={null}
{
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "Insufficient credits",
    "request_id": "req_01HZ..."
  },
  "credits_needed": 100,
  "credits_remaining": 50
}
```

### Validation Error with Additional Context (400)

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "One or more URLs have invalid format",
    "request_id": "req_01HZ..."
  },
  "invalid_urls": [
    "Index 0: https://invalid-url - invalid format",
    "Index 2: not-a-url - invalid format"
  ]
}
```

### Not Found (404)

```json theme={null}
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Execution not found",
    "request_id": "req_01HZ..."
  }
}
```

### Conflict (409)

```json theme={null}
{
  "error": {
    "code": "CONFLICT",
    "message": "Crawl is currently stopping for example.com. Please wait.",
    "request_id": "req_01HZ..."
  }
}
```

## Logical errors (200 OK responses)

Some endpoints return `200 OK` even when individual items fail. Always check the response body:

### Single product endpoint

```json theme={null}
{
  "product": null,
  "meta": {
    "found": false,
    "url": "https://example.com/invalid-product",
    "outcome": "VENDOR_NOT_SUPPORTED"
  }
}
```

### Bulk products endpoint

```json theme={null}
{
  "products": [
    {
      "url": "https://valid-store.com/product",
      "success": true,
      "product": { "_truncated": "Product object omitted for display" }
    },
    {
      "url": "https://invalid-store.com/product",
      "success": false,
      "product": null,
      "outcome": "VENDOR_NOT_SUPPORTED"
    }
  ],
  "meta": {
    "total_requested": 2,
    "total_successful": 1,
    "total_failed": 1
  }
}
```

<Tip>
  Always check `meta.found` or `success` fields in responses, even when the HTTP status is 200.
</Tip>

## Need help?

If you encounter persistent errors or need assistance, [contact our support team](mailto:founders@getcatalog.ai) with:

* The full error response (including the `error` object)
* The `request_id` from the response (found in both the response body and `Request-ID` header)
* Your request payload (with sensitive data redacted)
* Timestamp of when the error occurred
