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

# Errors

> Error envelope, HTTP status codes, and the business rules that can block a request.

Errors return a non-`2xx` status code and a JSON body with a machine-readable `error` code, a human-readable `message`, and the numeric `status`:

```json theme={null}
{
  "error": "INSUFFICIENT_SCOPE",
  "message": "Partner does not have the required scope: tours.read",
  "status": 403
}
```

<Note>
  Authentication failures raised before your request reaches the application (at the gateway authorizer) return a bare body without an `error` field — `{ "message": "Unauthorized" }` for `401` (no API key) or `{ "message": "Forbidden" }` for `403` (a present-but-invalid key, or a bad/missing bearer token alongside a key). Always branch on the HTTP status code, not only on the body shape.
</Note>

## Status codes

| Status | Code                            | When                                                                                                                                                                                                                  |
| ------ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `INVALID_JSON`                  | Request body is not valid JSON                                                                                                                                                                                        |
| `400`  | `VALIDATION_ERROR`              | A field failed validation (e.g. empty tour name, bad date range, invalid time zone)                                                                                                                                   |
| `400`  | `BAD_REQUEST` / `GRAPHQL_ERROR` | The underlying service rejected the input (missing required field, invalid value)                                                                                                                                     |
| `400`  | `"400"`                         | Tour creation rejected because the organization has reached its **free-tour limit** (message: *"Organization has reached the free tour limit"*). Note the non-semantic `error` code of `"400"`.                       |
| `401`  | —                               | **Missing** API key/secret, or missing/invalid/expired bearer token. Returned as a bare `{ "message": "Unauthorized" }`.                                                                                              |
| `402`  | `SUBSCRIPTION_REQUIRED`         | Write operation on an organization without an active subscription                                                                                                                                                     |
| `403`  | `Forbidden`                     | An API key is **present but the request fails the gateway authorizer** — the key is unknown/invalid/revoked, or the bearer token is missing/invalid alongside a key. Returned as a bare `{ "message": "Forbidden" }`. |
| `403`  | `INSUFFICIENT_SCOPE`            | Your partner credential lacks the endpoint's required scope                                                                                                                                                           |
| `403`  | `Unauthorized`                  | The authenticated user is not allowed to access this resource                                                                                                                                                         |
| `404`  | `NOT_FOUND`                     | No endpoint matches the method + path                                                                                                                                                                                 |
| `404`  | `TOUR_NOT_FOUND`                | The referenced tour has been deleted                                                                                                                                                                                  |
| `405`  | `METHOD_NOT_ALLOWED`            | Path exists but not for this HTTP method (see the `Allow` response header)                                                                                                                                            |
| `409`  | `GRAPHQL_ERROR`                 | The request conflicts with existing data                                                                                                                                                                              |
| `413`  | `PAYLOAD_TOO_LARGE`             | Request body exceeds 512 KB                                                                                                                                                                                           |
| `500`  | `INTERNAL_ERROR`                | Unexpected server error                                                                                                                                                                                               |
| `502`  | `EMPTY_RESPONSE`                | The underlying service returned no data                                                                                                                                                                               |

## Response body examples

Concrete examples of each error response body:

```json theme={null}
// 400 — Validation error
{
  "error": "VALIDATION_ERROR",
  "message": "Tour name must not be empty",
  "status": 400
}
```

```json theme={null}
// 401 — Unauthorized (raised before reaching the application; no `error` field)
{
  "message": "Unauthorized"
}
```

```json theme={null}
// 402 — Subscription required
{
  "error": "SUBSCRIPTION_REQUIRED",
  "message": "This organization does not have an active subscription.",
  "status": 402
}
```

```json theme={null}
// 403 — Insufficient scope
{
  "error": "INSUFFICIENT_SCOPE",
  "message": "Partner does not have the required scope: tours.write",
  "status": 403
}
```

```json theme={null}
// 404 — Tour not found (soft-deleted)
{
  "error": "TOUR_NOT_FOUND",
  "message": "Tour has been deleted",
  "status": 404
}
```

```json theme={null}
// 405 — Method not allowed
{
  "error": "METHOD_NOT_ALLOWED",
  "message": "PATCH is not supported for /v1/tours. Allowed: GET, POST, PUT, DELETE",
  "status": 405
}
```

```json theme={null}
// 413 — Payload too large
{
  "error": "PAYLOAD_TOO_LARGE",
  "message": "Request body exceeds 512KB limit",
  "status": 413
}
```

## Business rules that can block a request

Beyond authentication and scopes, several rules are enforced before a write reaches Daysync's services:

### Subscription requirement (`402`)

Write operations against an organization that has **no active subscription** are rejected with `402 SUBSCRIPTION_REQUIRED`. Organizations flagged as **unlimited-free**, **trial**, or **beta** are exempt. This applies to writes where the organization is identified by the request — in practice, tour creation (`POST /v1/tours`).

### Deleted tours (`404`)

Any request that references a tour which has been soft-deleted returns `404 TOUR_NOT_FOUND` — for both reads of its children and writes against it.

### Input validation (`400`)

Tour create/edit requests are validated up front:

* `name` must not be empty and must be ≤ 500 characters,
* `end_date` must not be before `start_date`,
* `timeZone` must be a valid IANA time zone (e.g. `Australia/Sydney`).

Other endpoints are validated by the underlying service, which surfaces a `400` with a descriptive message.

<Warning>
  **Known issue:** some schedule-item validation failures are currently returned with HTTP `500` and `error: "VALIDATION_ERROR"` rather than `400`. For example, sending `startTime` as an ISO 8601 datetime instead of `HH:mm` returns `500` with *"Start Time must be in HH:mm format (24-hour format)"*. Treat a `500` whose body carries `error: "VALIDATION_ERROR"` as an input error (fix the request) rather than a transient server fault to retry.
</Warning>

## Error messages are sanitized

Internal errors (database constraints, SQL details, internal paths) are never returned verbatim. Such cases are collapsed to a generic message and typically a `409`. Build error handling around the `status` and `error` code rather than parsing message text.

## Handling tips

* Treat `401` as "fix your credentials/token" and `403` as "you are authenticated but not permitted" — they require different responses (re-auth vs. request more scopes).
* On `402`, surface a subscription/billing prompt to the user; retrying will not help.
* On `5xx`, retry with backoff; these are transient or upstream.
