# Error Codes (/api-reference/errors)

## Overview

Bitelio API errors follow one consistent shape, designed so you can pinpoint the problem fast. Each error carries:

* A **machine-readable error code** your code can branch on
* A **human-readable message** describing the failure
* A **suggestion** pointing at the likely fix
* A **request ID** for debugging and support conversations
* **Field-level validation details** where they apply

## HTTP Status Codes

### 200 OK

The request went through.

### 201 Created

The resource was created.

### 400 Bad Request

Something is wrong with the request format or its parameters — the error details say what.

### 401 Unauthorized

Authentication is missing or didn't succeed. Double-check your API key.

### 402 Payment Required

A billing cap was hit, or the operation needs a higher plan. Accompanies `BILLING_LIMIT_EXCEEDED` and `UPGRADE_REQUIRED`.

### 403 Forbidden

You're not allowed to touch this resource — review permissions and project status. Also used when the account's email still needs verifying (`EMAIL_VERIFICATION_REQUIRED`) or the project was disabled (`PROJECT_DISABLED`).

### 404 Not Found

There's no resource with that ID. Confirm the ID is right.

### 409 Conflict

The request clashes with existing state — e.g. creating a contact whose email is already registered, or renaming to an email that's taken. Accompanies `CONFLICT`.

### 422 Unprocessable Entity

Validation rejected the request. The `errors` array lists the offending fields.

### 429 Too Many Requests

You've gone over a rate limit. Back off before retrying, or move to a higher plan.

### 500 Internal Server Error

Something broke on our end. Reach out to support with the request ID.

## Error Response Format

Every error uses this one structure:

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "statusCode": 422,
    "requestId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "errors": [
      {
        "field": "email",
        "message": "Invalid email",
        "code": "invalid_string"
      }
    ],
    "suggestion": "One or more fields have incorrect types. Check that strings are quoted, numbers are unquoted, and booleans are true/false."
  },
  "timestamp": "2025-11-30T10:30:00.000Z"
}
```

### Response Fields

| Field              | Type    | Description                                    |
| ------------------ | ------- | ---------------------------------------------- |
| `success`          | boolean | On errors this is always `false`               |
| `error.code`       | string  | Machine-readable identifier (catalogued below) |
| `error.message`    | string  | Plain-language account of what failed          |
| `error.statusCode` | number  | The HTTP status                                |
| `error.requestId`  | string  | Per-request identifier for debugging           |
| `error.errors`     | array   | Per-field breakdown (validation failures only) |
| `error.details`    | object  | Extra context, when available (optional)       |
| `error.suggestion` | string  | A pointer toward the fix (optional)            |
| `timestamp`        | string  | When the error happened, as ISO 8601           |

## Error Codes Reference

The `code` field is present on every error so you can handle them programmatically. The complete set:

### Authentication & Authorization

| Code                          | Status | Description                                               |
| ----------------------------- | ------ | --------------------------------------------------------- |
| `UNAUTHORIZED`                | 401    | Authentication failed for an unspecified reason           |
| `INVALID_CREDENTIALS`         | 401    | The login details don't match                             |
| `MISSING_AUTH`                | 401    | No usable Authorization header on the request             |
| `INVALID_API_KEY`             | 401    | That API key doesn't exist or isn't valid                 |
| `REVOKED_API_KEY`             | 401    | That key was valid and has been revoked — issue a new one |
| `EXPIRED_API_KEY`             | 401    | That key passed its expiry date — issue a new one         |
| `FORBIDDEN`                   | 403    | This action isn't permitted                               |
| `PROJECT_ACCESS_DENIED`       | 403    | You don't have access to this project                     |
| `PROJECT_DISABLED`            | 403    | The project is currently disabled                         |
| `EMAIL_VERIFICATION_REQUIRED` | 403    | Verify the account's email address before doing this      |

### Validation & Input Errors

| Code                     | Status | Description                                             |
| ------------------------ | ------ | ------------------------------------------------------- |
| `BAD_REQUEST`            | 400    | Generic problem with the request                        |
| `VALIDATION_ERROR`       | 422    | Validation rejected the request (field errors attached) |
| `INVALID_EMAIL`          | 422    | The email address isn't well-formed                     |
| `INVALID_REQUEST_BODY`   | 400    | The body couldn't be parsed                             |
| `MISSING_REQUIRED_FIELD` | 422    | A required field wasn't supplied                        |

### Resource Errors

| Code                 | Status | Description                                    |
| -------------------- | ------ | ---------------------------------------------- |
| `RESOURCE_NOT_FOUND` | 404    | No resource matches (generic)                  |
| `CONTACT_NOT_FOUND`  | 404    | No such contact                                |
| `TEMPLATE_NOT_FOUND` | 404    | No such template                               |
| `CAMPAIGN_NOT_FOUND` | 404    | No such campaign                               |
| `WORKFLOW_NOT_FOUND` | 404    | No such workflow                               |
| `CONFLICT`           | 409    | Clashes with existing state (e.g. a duplicate) |

### Rate Limiting & Billing

| Code                     | Status | Description                     |
| ------------------------ | ------ | ------------------------------- |
| `RATE_LIMIT_EXCEEDED`    | 429    | Request rate is too high        |
| `BILLING_LIMIT_EXCEEDED` | 402    | A billing cap was reached       |
| `UPGRADE_REQUIRED`       | 402    | The feature needs a higher plan |

### Server Errors

| Code                     | Status | Description                          |
| ------------------------ | ------ | ------------------------------------ |
| `INTERNAL_SERVER_ERROR`  | 500    | Unexpected failure on our side       |
| `DATABASE_ERROR`         | 500    | A database operation didn't complete |
| `EXTERNAL_SERVICE_ERROR` | 500    | A downstream service was unreachable |

## Common Error Examples

### Authentication Errors

#### Invalid API Key

```json
{
  "success": false,
  "error": {
    "code": "INVALID_API_KEY",
    "message": "Invalid secret API key. This endpoint requires a secret key (sk_*), not a public key.",
    "statusCode": 401,
    "requestId": "abc-123",
    "suggestion": "Verify your API key is correct and starts with \"sk_\" for secret keys or \"pk_\" for public keys."
  },
  "timestamp": "2025-11-30T10:30:00.000Z"
}
```

**Solution**: Confirm the key itself and its type — endpoints that need a secret key expect a `sk_` prefix, while event tracking uses `pk_` keys.

#### Missing Authorization Header

```json
{
  "success": false,
  "error": {
    "code": "MISSING_AUTH",
    "message": "Authorization header is required",
    "statusCode": 401,
    "requestId": "abc-123",
    "suggestion": "Include an Authorization header with format: \"Authorization: Bearer YOUR_API_KEY\""
  },
  "timestamp": "2025-11-30T10:30:00.000Z"
}
```

**Solution**: Send your key as a Bearer token in the `Authorization` header.

### Validation Errors

#### Invalid Email Format

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "statusCode": 422,
    "requestId": "abc-123",
    "errors": [
      {
        "field": "email",
        "message": "Invalid email",
        "code": "invalid_string"
      }
    ],
    "suggestion": "One or more fields have incorrect types. Check that strings are quoted, numbers are unquoted, and booleans are true/false."
  },
  "timestamp": "2025-11-30T10:30:00.000Z"
}
```

**Solution**: Send a well-formed email address. Look at the `errors` array to see exactly which fields were rejected.

#### Missing Required Fields

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "statusCode": 422,
    "requestId": "abc-123",
    "errors": [
      {
        "field": "event",
        "message": "Required",
        "code": "invalid_type"
      },
      {
        "field": "email",
        "message": "Required",
        "code": "invalid_type"
      }
    ],
    "suggestion": "Required fields are missing. Ensure all required fields are included in your request."
  },
  "timestamp": "2025-11-30T10:30:00.000Z"
}
```

**Solution**: Add every required field to the request body.

### Resource Errors

#### Template Not Found

```json
{
  "success": false,
  "error": {
    "code": "TEMPLATE_NOT_FOUND",
    "message": "Template with ID \"tpl_abc123\" was not found",
    "statusCode": 404,
    "requestId": "abc-123",
    "details": {
      "resource": "Template",
      "id": "tpl_abc123"
    },
    "suggestion": "Ensure the template ID is correct and belongs to your project. You can list available templates via the API."
  },
  "timestamp": "2025-11-30T10:30:00.000Z"
}
```

**Solution**: Make sure the template ID exists and lives in your project.

### Rate Limiting

#### Rate Limit Exceeded

```json
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Please try again later.",
    "statusCode": 429,
    "requestId": "abc-123",
    "suggestion": "You have exceeded the rate limit. Wait a moment before retrying, or upgrade your plan."
  },
  "timestamp": "2025-11-30T10:30:00.000Z"
}
```

**Solution**: Add retry logic with exponential backoff. If you routinely hit the ceiling, a plan upgrade raises it.

## Success Response Format

When a request succeeds, the response comes back as `success: true` with a `data` object:

```json
{
  "success": true,
  "data": {
    "contact": "cnt_abc123",
    "event": "evt_xyz789",
    "timestamp": "2025-11-30T10:30:00.000Z"
  }
}
```

### Response Fields

| Field     | Type    | Description                      |
| --------- | ------- | -------------------------------- |
| `success` | boolean | On success this is always `true` |
| `data`    | object  | Endpoint-specific payload        |

## Handling Errors in Your Code

### JavaScript/TypeScript Example

```typescript
try {
  const response = await fetch('https://api.bitelio.com/v1/track', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${publicKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      event: 'purchase',
      email: 'user@example.com'
    })
  });

  const data = await response.json();

  if (!data.success) {
    // Something went wrong
    console.error(`Error [${data.error.code}]:`, data.error.message);

    // Surface the API's suggested fix
    if (data.error.suggestion) {
      console.log('Suggestion:', data.error.suggestion);
    }

    // Keep the request ID around for support
    console.log('Request ID:', data.error.requestId);

    // Branch on the specific code
    switch (data.error.code) {
      case 'VALIDATION_ERROR':
        // Print each field that failed
        data.error.errors?.forEach(err => {
          console.log(`${err.field}: ${err.message}`);
        });
        break;
      case 'INVALID_API_KEY':
        // Ask the user to re-check their key
        break;
      case 'REVOKED_API_KEY':
      case 'EXPIRED_API_KEY':
        // The key WAS valid: rotate it rather than asking anyone to re-check a typo
        break;
      case 'RATE_LIMIT_EXCEEDED':
        // Retry later with backoff
        break;
    }

    return;
  }

  // Success path
  console.log('Event tracked:', data.data);
} catch (error) {
  console.error('Network error:', error);
}
```

### Python Example

```python
import requests

response = requests.post(
    'https://api.bitelio.com/v1/track',
    headers={
        'Authorization': f'Bearer {public_key}',
        'Content-Type': 'application/json'
    },
    json={
        'event': 'purchase',
        'email': 'user@example.com'
    }
)

data = response.json()

if not data.get('success'):
    error = data['error']
    print(f"Error [{error['code']}]: {error['message']}")

    # Surface the suggested fix
    if 'suggestion' in error:
        print(f"Suggestion: {error['suggestion']}")

    # Keep the request ID around
    print(f"Request ID: {error['requestId']}")

    # Print field-level failures
    if error['code'] == 'VALIDATION_ERROR':
        for err in error.get('errors', []):
            print(f"{err['field']}: {err['message']}")
else:
    print(f"Event tracked: {data['data']}")
```

## Troubleshooting Guide

### Using Request IDs

Each error ships with a `requestId` that follows the request across our whole stack. That makes it valuable in two directions:

**For Developers:**

* The ID is stamped on every log line for that request
* Grep your logs by ID and every related entry surfaces at once
* Follow one request across API → Database → Queue → Worker

**For Support:**

1. Paste the request ID into your message
2. Explain what you were attempting
3. Attach the complete error response when you can

**Example:** Given the request ID `f47ac10b-58cc-4372-a567-0e02b2c3d479`, a log search on our side reveals:

* The request body exactly as it arrived
* The database queries that ran
* Any background jobs it spawned
* The full stack trace, if one exists

That means we can usually diagnose the problem straight away, without a back-and-forth asking you for more detail.

#### How to Find Request IDs

The request ID shows up in three places:

* **Error responses**: the `error.requestId` field
* **Response headers**: the `X-Request-ID` header (present on successes too)
* **Your application logs**: write the header into your own logs so you can correlate

```javascript
// Capture the request ID in your own logging
const response = await fetch('https://api.bitelio.com/v1/send', {
  // ... your request
});

const requestId = response.headers.get('X-Request-ID');
console.log('Request ID:', requestId);  // Store it for correlation

const data = await response.json();
if (!data.success) {
  console.error('Error:', data.error.message);
  console.error('Request ID:', data.error.requestId);  // Matches the header
}
```

### Common Issues and Solutions

import {Accordion, Accordions} from 'fumadocs-ui/components/accordion';

<Accordions type="single">
  <Accordion title="Authentication Issues (401) — INVALID_API_KEY or MISSING_AUTH">
    * Make sure the key was pasted exactly, without stray whitespace.
    * Confirm the key type matches the endpoint (`sk_` secret vs. `pk_` public).
    * Check the `Authorization` header uses the Bearer token format.
    * Make sure the key wasn't rotated or revoked in the meantime.
  </Accordion>

  <Accordion title="Validation Issues (422) — VALIDATION_ERROR">
    * Read the `errors` array — it names each failing field.
    * Confirm nothing required was left out.
    * Match every field's type (quote strings, leave numbers unquoted).
    * Compare your payload against the API reference.
  </Accordion>

  <Accordion title="Not Found Issues (404) — TEMPLATE_NOT_FOUND, CONTACT_NOT_FOUND, etc.">
    * Double-check the resource ID.
    * Confirm it belongs to your project, not another one.
    * Make sure it wasn't deleted.
    * List the resources over the API to verify the IDs you hold.
  </Accordion>

  <Accordion title="Rate Limit Issues (429) — RATE_LIMIT_EXCEEDED">
    * Retry with exponential backoff (1s, 2s, 4s, 8s between attempts).
    * Slow down your request rate.
    * A higher plan raises the ceiling if you need more throughput.
    * Prefer batch endpoints where they exist.
  </Accordion>

  <Accordion title="Server Errors (500) — INTERNAL_SERVER_ERROR">
    * Write down the request ID from the response.
    * Give it a moment, then retry.
    * Look at [status.bitelio.com](https://status.bitelio.com) for ongoing incidents.
    * If it keeps happening, contact support and include the request ID.
  </Accordion>
</Accordions>

### Best Practices

1. **Inspect `success` first**, before touching the rest of the response
2. **Record request IDs** so debugging and support requests go faster
3. **Fail gracefully**, translating errors into messages your users understand
4. **Retry transient failures** using exponential backoff
5. **Watch your error rates** so problems surface early
6. **Branch on error codes**, not just HTTP statuses, in your handling logic

## Getting Help

Still stuck? Work through this list:

1. Start with the `suggestion` field in the error itself
2. Compare your request against the [API Reference](/api-reference/overview)
3. Look up your exact error code in these docs
4. Write to support and include your request ID
5. Ask the community — other developers have often hit the same thing
