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

# Errors & Rate Limits

> HTTP status codes, error response format, rate limits, and error handling guidance for Scanova Conversion Tracking APIs.

## HTTP status codes

| Code                  | Meaning                                                            | Action                                                  |
| --------------------- | ------------------------------------------------------------------ | ------------------------------------------------------- |
| `200`                 | Event accepted                                                     | No action needed                                        |
| `400`                 | Bad request — missing field, invalid format, or inactive `site_id` | Fix the request payload                                 |
| `401`                 | Missing `X-API-Key` header                                         | Add the `X-API-Key` header                              |
| `403`                 | Invalid key, revoked key, or `site_id`/key mismatch                | Check your API key and `site_id`                        |
| `413`                 | Payload too large                                                  | Reduce payload size or split into smaller batches       |
| `422`                 | Validation error — see response body for details                   | Fix the specific field error                            |
| `429`                 | Rate limit exceeded                                                | Back off and retry after the `Retry-After` header value |
| `500` / `502` / `503` | Server error                                                       | Retry with exponential backoff                          |

## Error response format

All error responses return a JSON body with details:

```json theme={null}
{
  "detail": [
    {
      "loc": ["body", "scan_session_id"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}
```

For `400` errors with a single message:

```json theme={null}
{
  "detail": "site_id not found or inactive"
}
```

## Rate limits

| Endpoint                    | Limit                | Scope          |
| --------------------------- | -------------------- | -------------- |
| `POST /ct` (browser events) | 100 requests / min   | Per IP address |
| `POST /collect/batch`       | 100 requests / min   | Per IP address |
| `POST /server-events`       | 1,000 requests / min | Per API key    |
| `POST /server-events/batch` | 1,000 requests / min | Per API key    |

<Note>
  Short bursts above the limit are handled with a small grace window. Design your clients to back off on `429` rather than relying on burst tolerance.
</Note>

## Error handling pattern

### For server events

```javascript theme={null}
async function sendEvent(payload) {
  const response = await fetch('https://track.scanova.io/server-events', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': process.env.SCANOVA_API_KEY,
    },
    body: JSON.stringify(payload),
  });

  if (response.ok) return await response.json();

  const error = await response.json().catch(() => null);

  if (response.status === 429) {
    const retryAfter = parseInt(response.headers.get('Retry-After') ?? '60', 10);
    throw new RetryableError(`Rate limited — retry after ${retryAfter}s`, retryAfter);
  }

  if (response.status >= 500) {
    throw new RetryableError(`Server error ${response.status}`);
  }

  // 400, 401, 403, 422 — do not retry, fix the request
  throw new PermanentError(`Request failed ${response.status}: ${JSON.stringify(error)}`);
}
```

### Retry logic

Only retry on transient errors. Stop immediately on permanent errors.

| Retry?                   | Status codes                                       |
| ------------------------ | -------------------------------------------------- |
| Yes — retry with backoff | `429`, `500`, `502`, `503`, `504`, network timeout |
| No — fix the request     | `400`, `401`, `403`, `413`, `422`                  |

See [Idempotency & Retries](/conversion-tracking/server/idempotency-retries) for a full retry implementation with exponential backoff.

## Common `422` causes

| Error                               | Fix                                                                |
| ----------------------------------- | ------------------------------------------------------------------ |
| `scan_session_id` invalid format    | Must be a valid UUID (e.g. `7ad26d4f-3181-4ef8-b6ca-b8f59499dd43`) |
| `event_name` missing                | Required for server events                                         |
| `conversion_value.currency` invalid | Must be a 3-letter ISO 4217 code (e.g. `USD`, `EUR`, `GBP`)        |
| `properties` too large              | Keep under 10 KB                                                   |
| Raw email in `properties`           | Use `user_identifiers.email_hash` with a SHA-256 hash instead      |
