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

# Errors

> Understanding API error responses

## Error Response Format

All errors follow a consistent JSON structure:

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

| Field     | Type   | Description                       |
| --------- | ------ | --------------------------------- |
| `code`    | string | Machine-readable error identifier |
| `message` | string | Human-readable explanation        |
| `details` | object | Additional context (optional)     |

## HTTP Status Codes

The API uses standard HTTP status codes:

| Code    | Meaning               | Description                                     |
| ------- | --------------------- | ----------------------------------------------- |
| **200** | OK                    | Request succeeded                               |
| **201** | Created               | Resource created successfully                   |
| **400** | Bad Request           | Invalid request data or parameters              |
| **401** | Unauthorized          | Missing or invalid API key                      |
| **403** | Forbidden             | Insufficient permissions (scope error)          |
| **404** | Not Found             | Resource doesn't exist                          |
| **409** | Conflict              | Resource already exists (e.g., duplicate email) |
| **429** | Too Many Requests     | Rate limit exceeded                             |
| **500** | Internal Server Error | Server-side error (contact support)             |

## Common Errors

### Authentication Errors (401)

<AccordionGroup>
  <Accordion title="MISSING_API_KEY">
    No API key provided in request headers.

    ```json theme={null}
    {
      "error": {
        "code": "MISSING_API_KEY",
        "message": "API key required. Provide via X-API-Key header or Authorization: Bearer <key>"
      }
    }
    ```

    **Solution:** Add your API key to the `X-API-Key` header.
  </Accordion>

  <Accordion title="INVALID_API_KEY_FORMAT">
    API key doesn't match the expected format.

    ```json theme={null}
    {
      "error": {
        "code": "INVALID_API_KEY_FORMAT",
        "message": "Invalid API key format. Key must start with 'dem_'"
      }
    }
    ```

    **Solution:** Ensure your API key starts with `dem_` and is 47 characters total.
  </Accordion>

  <Accordion title="INVALID_API_KEY">
    API key not found in database or has been deactivated.

    ```json theme={null}
    {
      "error": {
        "code": "INVALID_API_KEY",
        "message": "Invalid or inactive API key"
      }
    }
    ```

    **Solution:** Generate a new API key in your dashboard settings.
  </Accordion>

  <Accordion title="EXPIRED_API_KEY">
    API key has passed its expiration date.

    ```json theme={null}
    {
      "error": {
        "code": "EXPIRED_API_KEY",
        "message": "API key has expired"
      }
    }
    ```

    **Solution:** Create a new API key and update your integration.
  </Accordion>
</AccordionGroup>

### Permission Errors (403)

<Accordion title="INSUFFICIENT_PERMISSIONS">
  API key doesn't have the required scope.

  ```json theme={null}
  {
    "error": {
      "code": "INSUFFICIENT_PERMISSIONS",
      "message": "This API key does not have the required scope: contacts:write",
      "requiredScope": "contacts:write",
      "availableScopes": ["contacts:read", "surveys:read"]
    }
  }
  ```

  **Solution:** Create a new API key with the required scope, or add the scope to your existing key in the dashboard.
</Accordion>

### Validation Errors (400)

<Accordion title="VALIDATION_ERROR">
  Request data failed validation.

  ```json theme={null}
  {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "Invalid request data",
      "details": {
        "email": ["Invalid email address"],
        "firstName": ["First name is required"]
      }
    }
  }
  ```

  **Solution:** Fix the validation errors listed in `details` and retry.
</Accordion>

### Resource Errors

<AccordionGroup>
  <Accordion title="NOT_FOUND (404)">
    Requested resource doesn't exist.

    ```json theme={null}
    {
      "error": {
        "code": "NOT_FOUND",
        "message": "Contact not found"
      }
    }
    ```

    **Solution:** Verify the resource ID is correct and belongs to your organization.
  </Accordion>

  <Accordion title="CONTACT_EXISTS (409)">
    Contact with email already exists.

    ```json theme={null}
    {
      "error": {
        "code": "CONTACT_EXISTS",
        "message": "A contact with this email already exists",
        "details": {
          "contactId": "uuid"
        }
      }
    }
    ```

    **Solution:** Use `PATCH /api/v1/contacts/:id` to update the existing contact instead.
  </Accordion>

  <Accordion title="SEQUENCE_INACTIVE (400)">
    Trying to execute an inactive sequence.

    ```json theme={null}
    {
      "error": {
        "code": "SEQUENCE_INACTIVE",
        "message": "Sequence is not active"
      }
    }
    ```

    **Solution:** Activate the sequence in your dashboard before executing it via API.
  </Accordion>
</AccordionGroup>

### Rate Limiting (429)

<Accordion title="RATE_LIMIT_EXCEEDED">
  Too many requests in a short period.

  ```json theme={null}
  {
    "error": {
      "code": "RATE_LIMIT_EXCEEDED",
      "message": "Rate limit exceeded. Please try again later.",
      "details": {
        "retryAfter": 60
      }
    }
  }
  ```

  **Solution:** Wait for the time specified in `retryAfter` (seconds) before retrying. Implement exponential backoff in your integration.

  **Rate Limits:**

  * 1,000 requests per hour per organization
  * 100 requests per minute per organization
</Accordion>

### Server Errors (500)

<Accordion title="INTERNAL_ERROR">
  Unexpected server-side error.

  ```json theme={null}
  {
    "error": {
      "code": "INTERNAL_ERROR",
      "message": "An internal error occurred. Please try again later."
    }
  }
  ```

  **Solution:** This is a temporary error. Retry your request with exponential backoff. If the error persists, contact [support@demeterrr.com](mailto:support@demeterrr.com).
</Accordion>

## Error Handling Best Practices

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function makeApiRequest(url, options) {
    try {
      const response = await fetch(url, options);
      const data = await response.json();

      if (!response.ok) {
        // Handle API error
        console.error(`API Error: ${data.error.code}`, data.error.message);

        if (response.status === 401) {
          // Handle authentication error
          throw new Error('Authentication failed. Check your API key.');
        } else if (response.status === 403) {
          // Handle permission error
          throw new Error(`Missing scope: ${data.error.requiredScope}`);
        } else if (response.status === 429) {
          // Handle rate limit with retry
          const retryAfter = data.error.details?.retryAfter || 60;
          await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
          return makeApiRequest(url, options); // Retry
        }

        throw new Error(data.error.message);
      }

      return data;
    } catch (error) {
      console.error('Request failed:', error);
      throw error;
    }
  }
  ```

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

  def make_api_request(url, headers, max_retries=3):
      for attempt in range(max_retries):
          try:
              response = requests.get(url, headers=headers)
              data = response.json()

              if not response.ok:
                  error = data.get('error', {})
                  print(f"API Error: {error.get('code')} - {error.get('message')}")

                  if response.status_code == 401:
                      raise Exception('Authentication failed')
                  elif response.status_code == 403:
                      required = error.get('requiredScope')
                      raise Exception(f'Missing scope: {required}')
                  elif response.status_code == 429:
                      retry_after = error.get('details', {}).get('retryAfter', 60)
                      time.sleep(retry_after)
                      continue  # Retry

                  raise Exception(error.get('message'))

              return data

          except requests.exceptions.RequestException as e:
              if attempt == max_retries - 1:
                  raise
              time.sleep(2 ** attempt)  # Exponential backoff

      raise Exception('Max retries exceeded')
  ```
</CodeGroup>

## Need Help?

If you encounter persistent errors or need assistance:

<CardGroup cols={2}>
  <Card title="Support" icon="envelope" href="mailto:support@demeterrr.com">
    Email our support team
  </Card>

  <Card title="Status Page" icon="signal" href="https://status.demeterrr.com">
    Check API status
  </Card>
</CardGroup>
