---
title: Error Codes
path: reference/error-codes
status: published
---

# Error Codes

Complete reference of error status codes and common error shapes. For error handling patterns, see [Errors](../concepts/errors.md).

## Response shape

All errors return this shape:

```json
{"detail": "Human-readable message"}
```

Or for structured errors:

```json
{
  "detail": {
    "message": "...",
    "field": "...",
    "conflicts": [...]
  }
}
```

Validation errors (from Pydantic) return an array:

```json
{
  "detail": [
    {"loc": ["body", "name"], "msg": "field required", "type": "value_error.missing"}
  ]
}
```

## HTTP status codes

### 2xx — Success

| Code | Used when |
|------|-----------|
| `200` | GET, PATCH, POST returning a body |
| `201` | POST creating a resource (rare; usually `200`) |
| `204` | DELETE, or writes with no response body |

### 4xx — Caller error

| Code | Use | Example |
|------|-----|---------|
| `400 Bad Request` | Malformed input the server can parse but not process | Invalid record type, malformed CIDR, unknown algorithm |
| `401 Unauthorized` | Missing or invalid credentials | No `X-API-Key`, expired JWT, invalid webhook signature |
| `403 Forbidden` | Authenticated but not allowed | Missing permission, IP whitelist mismatch, cross-tenant attempt on non-admin |
| `404 Not Found` | Resource doesn't exist (or you can't see it) | Wrong UUID, soft-deleted resource, tenant scoping |
| `409 Conflict` | Write conflicts with current state | Duplicate domain name, record coexistence rule, role already assigned |
| `413 Payload Too Large` | Request body exceeds limit | Bulk operation too large, file upload too large |
| `422 Unprocessable Entity` | Schema validation failed | Pydantic errors, missing required field |
| `429 Too Many Requests` | Rate limit hit | Exceeded API key rate limit; check `Retry-After` |

### 5xx — Server error

| Code | Use | What to do |
|------|-----|------------|
| `500 Internal Server Error` | Unhandled exception | Retry with backoff; if persistent, check health |
| `502 Bad Gateway` | Upstream dependency unreachable | Usually transient — retry |
| `503 Service Unavailable` | Maintenance or overload | Retry with backoff |
| `504 Gateway Timeout` | Upstream slow | Retry; check dashboards |

## Domain-specific errors

| Message | Cause |
|---------|-------|
| `Domain already exists` | 409 — same name already in this tenant |
| `Invalid domain name` | 400 — name fails RFC validation |
| `Unsupported domain type` | 400 — bad `domain_type` value |
| `Domain not validated` | 403 — trying to edit records on `pending_validation` zone |
| `Domain is deleted` | 404 — zone is soft-deleted |

## Record-specific errors

| Message | Cause |
|---------|-------|
| `Invalid record content` | 400 — content doesn't match type format |
| `Record type not supported` | 400 — unknown type |
| `Cannot create A record: CNAME exists at <name>` | 409 — CNAME + A conflict |
| `DNAME cannot coexist with other records` | 409 — DNAME rule violation |
| `Record already exists` | 409 — duplicate `(name, type, content)` |
| `Invalid TTL` | 400 — TTL must be positive integer |

## Validation errors

| Message | Cause |
|---------|-------|
| `No DNS record found for <name>` | TXT record not published |
| `TXT record found but value doesn't match. Expected: ...` | Wrong TXT value |
| `Domain <name> does not exist in DNS` | NXDOMAIN at the TLD level |
| `No nameservers available for query` | Can't resolve authoritative NS |
| `DNS query timed out` | Authoritative NS unreachable |
| `Challenge expired` | Validation window passed |

## DNSSEC errors

| Message | Cause |
|---------|-------|
| `DNSSEC already enabled` | 409 — trying to enable on a signed zone |
| `DNSSEC not enabled` | 409 — trying to rotate a zone that isn't signed |
| `Unsupported algorithm` | 400 — algorithm number not supported |
| `DS record does not match any KSK` | 400 — confirm-ds-published with wrong DS |
| `KSK rotation in progress` | 409 — can't start another rotation mid-rollover |

## Permission errors

| Message | Cause |
|---------|-------|
| `Platform admin access required` | Caller isn't platform admin |
| `Admin access required` | Caller isn't tenant or platform admin |
| `<permission> permission required on this domain` | Missing specific permission |
| `IP not whitelisted` | API key IP whitelist blocks caller |
| `Access grant not found or expired` | Domain-level delegation missing |

## Auth errors

| Message | Cause |
|---------|-------|
| `Missing X-API-Key header` | API-key-only endpoint called without header |
| `Invalid API key` | Key doesn't match or has been deleted |
| `API key revoked` | Key is `revoked` status |
| `API key expired` | `expires_at` passed |
| `Invalid JWT` | Token signature check failed |
| `Token expired` | JWT `exp` claim in the past |
| `Missing X-ScaiKey-Signature header` | Webhook call without signature |
| `Invalid webhook signature` | HMAC mismatch |
| `Webhook timestamp too old` | Timestamp outside 5-minute window |

## Rate limiting

When rate limited (`429`), responses include:

```
Retry-After: 60
```

Value is seconds, or an HTTP date.

Healthy traffic also gets rate-limit headers:

```
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1714567890
```

## Related

- [Errors concept](../concepts/errors.md) — error handling patterns.
- [Authentication](./authentication.md) — auth error details.
