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

# Error Codes

> Complete reference of error codes returned by the Unosend API and how to handle them.

## Error Response Format

When an error occurs, the API returns a JSON response with the following structure:

```json error-response.json theme={null}
{
  "error": {
    "code": "invalid_api_key",
    "message": "The API key provided is invalid or has been revoked.",
    "status": 401,
    "details": {
      "api_key_prefix": "un_xxx..."
    }
  }
}
```

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

## HTTP Status Codes

<CardGroup cols={3}>
  <Card title="2xx Success" icon="circle-check" color="#22c55e">
    Request was successful
  </Card>

  <Card title="4xx Client Error" icon="triangle-exclamation" color="#f59e0b">
    Something was wrong with the request
  </Card>

  <Card title="5xx Server Error" icon="circle-xmark" color="#ef4444">
    Something went wrong on our end
  </Card>
</CardGroup>

## Authentication Errors

| Code                       | Status | Description                        |
| -------------------------- | ------ | ---------------------------------- |
| `missing_api_key`          | 401    | No API key provided in request     |
| `invalid_api_key`          | 401    | API key is invalid or revoked      |
| `api_key_expired`          | 401    | API key has expired                |
| `insufficient_permissions` | 403    | API key lacks required permissions |

### How to Fix

```bash Terminal theme={null}
# Correct format: Include API key in Authorization header
curl -X POST "https://api.unosend.co/emails" \
  -H "Authorization: Bearer un_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"from": "hello@yourdomain.com", "to": "user@example.com", "subject": "Test", "html": "<p>Hello</p>"}'
```

<Warning>
  **Common mistake:** Make sure you're using `Bearer` prefix before your API key.
  The format should be `Authorization: Bearer un_your_api_key`.
</Warning>

## Validation Errors

| Code                         | Status | Description                           |
| ---------------------------- | ------ | ------------------------------------- |
| `validation_error`           | 400    | Request body failed validation        |
| `missing_required_field`     | 400    | Required field is missing             |
| `invalid_email_address`      | 400    | Email address format is invalid       |
| `invalid_from_address`       | 400    | From address domain not verified      |
| `invalid_template_variables` | 400    | Template variables don't match schema |
| `attachment_too_large`       | 400    | Attachment exceeds size limit (10MB)  |
| `invalid_json`               | 400    | Request body is not valid JSON        |

### Example Validation Error

```json validation-error.json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "status": 400,
    "details": {
      "errors": [
        {
          "field": "to",
          "message": "must be a valid email address",
          "value": "not-an-email"
        },
        {
          "field": "subject",
          "message": "is required",
          "value": null
        }
      ]
    }
  }
}
```

### Handling Validation Errors

```typescript handle-validation.ts theme={null}
const response = await fetch('https://api.unosend.co/emails', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(payload)
});

if (response.status === 400) {
  const error = await response.json();
  
  if (error.error.code === 'validation_error') {
    // Log each field error
    error.error.details.errors.forEach((e: any) => {
      console.error(`Field '${e.field}': ${e.message}`);
    });
  }
}
```

## Resource Errors

| Code                 | Status | Description            |
| -------------------- | ------ | ---------------------- |
| `not_found`          | 404    | Resource doesn't exist |
| `email_not_found`    | 404    | Email ID not found     |
| `domain_not_found`   | 404    | Domain not found       |
| `template_not_found` | 404    | Template ID not found  |
| `webhook_not_found`  | 404    | Webhook not found      |

### Example 404 Response

```json not-found.json theme={null}
{
  "error": {
    "code": "email_not_found",
    "message": "Email with ID 'em_abc123' not found",
    "status": 404,
    "details": {
      "email_id": "em_abc123"
    }
  }
}
```

## Rate Limit Errors

| Code                   | Status | Description                  |
| ---------------------- | ------ | ---------------------------- |
| `rate_limit_exceeded`  | 429    | Too many requests per second |
| `daily_limit_exceeded` | 429    | Daily email limit reached    |
| `quota_exceeded`       | 429    | Monthly quota exceeded       |

### Rate Limit Response

```json rate-limit.json theme={null}
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Retry after 60 seconds.",
    "status": 429,
    "details": {
      "retry_after": 60,
      "limit": 100,
      "remaining": 0,
      "reset_at": "2024-01-15T10:30:00Z"
    }
  }
}
```

<Note>
  See the [Rate Limits](/resources/rate-limits) documentation for handling strategies and retry logic examples.
</Note>

## Domain Errors

| Code                         | Status | Description                                    |
| ---------------------------- | ------ | ---------------------------------------------- |
| `domain_not_verified`        | 400    | Domain has not been verified yet               |
| `domain_verification_failed` | 400    | DNS records not found or incorrect             |
| `domain_already_exists`      | 409    | Domain already registered in another workspace |
| `dkim_verification_failed`   | 400    | DKIM record not found or invalid               |
| `spf_verification_failed`    | 400    | SPF record not found or invalid                |

<Tip>
  See [Domain Verification](/guides/domain-verification) guide for setting up DNS records correctly.
</Tip>

## Server Errors

| Code                    | Status | Description                            |
| ----------------------- | ------ | -------------------------------------- |
| `internal_server_error` | 500    | Unexpected server error                |
| `service_unavailable`   | 503    | Service temporarily unavailable        |
| `gateway_timeout`       | 504    | Request timed out                      |
| `email_delivery_failed` | 500    | Failed to deliver email to mail server |

<Warning>
  **5xx errors** are rare and usually temporary. Implement retry logic with exponential backoff for these errors.
</Warning>

## Complete Error Handling Example

<CodeGroup>
  ```typescript JavaScript/TypeScript theme={null}
  interface UnosendError {
    error: {
      code: string;
      message: string;
      status: number;
      details?: Record<string, any>;
    };
  }

  async function sendEmail(payload: EmailPayload): Promise<EmailResponse> {
    const response = await fetch('https://api.unosend.co/emails', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(payload)
    });
    
    if (!response.ok) {
      const error: UnosendError = await response.json();
      
      switch (error.error.code) {
        case 'invalid_api_key':
        case 'missing_api_key':
          throw new Error('Authentication failed. Check your API key.');
          
        case 'validation_error':
          const fields = error.error.details?.errors
            ?.map((e: any) => `${e.field}: ${e.message}`)
            .join(', ');
          throw new Error(`Validation failed: ${fields}`);
          
        case 'rate_limit_exceeded':
          const retryAfter = error.error.details?.retry_after || 60;
          // Implement retry logic
          await sleep(retryAfter * 1000);
          return sendEmail(payload); // Retry once
          
        case 'domain_not_verified':
          throw new Error('Verify your domain before sending emails.');
          
        case 'quota_exceeded':
          throw new Error('Monthly email quota exceeded. Upgrade your plan.');
          
        case 'internal_server_error':
        case 'service_unavailable':
          // Retry with exponential backoff
          throw new Error('Service temporarily unavailable. Please retry.');
          
        default:
          throw new Error(error.error.message);
      }
    }
    
    return response.json();
  }
  ```

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

  class UnosendError(Exception):
      def __init__(self, code, message, status, details=None):
          self.code = code
          self.message = message
          self.status = status
          self.details = details or {}
          super().__init__(message)

  def send_email(payload):
      api_key = "un_your_api_key"
      
      response = requests.post(
          "https://api.unosend.co/emails",
          headers={
              "Authorization": f"Bearer {api_key}",
              "Content-Type": "application/json"
          },
          json=payload
      )
      
      if not response.ok:
          error = response.json()["error"]
          
          if error["code"] == "rate_limit_exceeded":
              retry_after = error.get("details", {}).get("retry_after", 60)
              time.sleep(retry_after)
              return send_email(payload)  # Retry
          
          if error["code"] == "validation_error":
              errors = error.get("details", {}).get("errors", [])
              fields = ", ".join([f"{e['field']}: {e['message']}" for e in errors])
              raise UnosendError(
                  error["code"],
                  f"Validation failed: {fields}",
                  error["status"],
                  error.get("details")
              )
          
          raise UnosendError(
              error["code"],
              error["message"],
              error["status"],
              error.get("details")
          )
      
      return response.json()
  ```

  ```bash cURL Error Checking theme={null}
  # Check HTTP status code and response
  response=$(curl -s -w "\n%{http_code}" -X POST "https://api.unosend.co/emails" \
    -H "Authorization: Bearer un_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{"from": "hello@yourdomain.com", "to": "user@example.com", "subject": "Test", "html": "<p>Hello</p>"}')

  status=$(echo "$response" | tail -n1)
  body=$(echo "$response" | sed '$d')

  if [ "$status" -ge 400 ]; then
    echo "Error ($status): $body"
  else
    echo "Success: $body"
  fi
  ```
</CodeGroup>

## Quick Reference

<CardGroup cols={2}>
  <Card title="Retryable Errors" icon="rotate">
    * `429` Rate limit exceeded
    * `500` Internal server error
    * `503` Service unavailable
    * `504` Gateway timeout
  </Card>

  <Card title="Non-Retryable Errors" icon="ban">
    * `400` Validation error
    * `401` Authentication error
    * `403` Permission denied
    * `404` Resource not found
  </Card>
</CardGroup>
