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

# Errors

> Every error code, what it means, and how to recover

All errors share a consistent shape:

```json theme={null}
{
  "error": { "code": "not_found", "message": "Video not found" },
  "meta": { "request_id": "req_xxxxx" }
}
```

The `error.code` is a stable machine-readable string. The `error.message` is human-readable and may change between versions. Parse `code`, log `message`.

## Error codes

| Code                   | HTTP | When it fires                                                               | Recommended client action                                                                       |
| ---------------------- | ---- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `unauthorized`         | 401  | Missing, malformed, or invalid `Authorization` header                       | Check the header is `Bearer lymo_...`; re-issue the key if it's been revoked                    |
| `invalid_key_metadata` | 401  | Key signature is valid but Lymo-specific metadata is missing                | Re-issue the key from the web app — it was created outside the normal flow                      |
| `forbidden`            | 403  | Key lacks the required scope, or the organization is inactive               | Check your key's scopes; contact support if the org should be active                            |
| `not_found`            | 404  | Resource doesn't exist, or it exists in a different organization            | See [cross-tenant 404](#cross-tenant-404) below                                                 |
| `bad_request`          | 400  | Malformed header — e.g. `Idempotency-Key` longer than 256 characters        | Fix the header and retry                                                                        |
| `validation_failed`    | 422  | Query parameters or request body failed schema validation                   | Read `message` for the failing field path; fix and retry                                        |
| `invalid_webhook_url`  | 422  | `POST /v1/webhooks` destination URL isn't on your org's hostname allowlist  | See [Webhooks → Hostname allowlist](/en/guides/webhooks#before-you-register-hostname-allowlist) |
| `rate_limited`         | 429  | Too many requests for this key (when enforcement is configured — see below) | Honor the `Retry-After` header (seconds); back off exponentially thereafter                     |
| `service_unavailable`  | 503  | Key-verification service is temporarily unavailable                         | Retry with backoff; escalate if it persists more than a minute                                  |
| `internal_error`       | 500  | Unexpected server error                                                     | Retry once; if it persists, report the `request_id`                                             |

## Cross-tenant 404

Resources that belong to a different organization return `404 not_found`, **not** `403 forbidden`. This is intentional — `403` would leak the fact that an ID exists; `404` is indistinguishable from "never existed." This applies to every resource path, not just videos.

Concretely: if your key is for org A, calling `GET /v1/videos/{id}` with a video ID from org B returns:

```json theme={null}
{
  "error": { "code": "not_found", "message": "Video not found" },
  "meta": { "request_id": "req_xxxxx" }
}
```

If a resource you *know* exists returns `404`, the most common cause is that the API key is scoped to a different organization than you expected. Check it with:

```bash theme={null}
curl "$LYMO_BASE/v1/members/me" -H "Authorization: Bearer $LYMO_KEY"
```

The returned member's organization is the only one this key can see.

## Key self-revocation

If a key is compromised, the holder can revoke it without web UI access:

```bash theme={null}
curl -X DELETE "$LYMO_BASE/v1/keys/me" -H "Authorization: Bearer $LYMO_KEY"
# → { "data": { "revoked": true }, "meta": {...} }
```

This endpoint requires no permission scope — any key can always revoke itself. After revocation, every subsequent request with the same key returns `401 unauthorized`.

## Using `request_id`

Every response — success or error — includes `meta.request_id`. When reporting issues, include it: we can trace the exact request server-side, including timing, inputs, and which backend service handled it.

```json theme={null}
{
  "error": { "code": "internal_error", "message": "An unexpected error occurred" },
  "meta": { "request_id": "req_fa29e58a3cfb4c0fbab0a75f82c4c7a3" }
}
```

Format is `req_` followed by 32 lowercase hex characters (a UUID with dashes removed). The same ID is echoed on the `X-Request-Id` response header so you can grep logs end-to-end.

## Rate limits

Rate-limit enforcement is per-API-key. When a limit is exceeded the response is `429 rate_limited` with a `Retry-After` header specifying seconds to wait:

```
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json

{ "error": { "code": "rate_limited", "message": "Rate limit exceeded" }, "meta": {...} }
```

<Note>
  Rate limits are not yet enforced on all issued keys. Future versions will apply sensible defaults per scope. Design clients to always honor `Retry-After` rather than hard-coding intervals, so the switch to enforcement is a no-op for you.
</Note>

## Validation errors

`validation_failed` (422) is the most common error during development. The `message` field names the offending field path:

```json theme={null}
{
  "error": {
    "code": "validation_failed",
    "message": "limit: Number must be less than or equal to 100"
  },
  "meta": { "request_id": "req_..." }
}
```

Common causes:

* `limit` outside `1–100` (default is 25)
* `created_after` / `created_before` not a valid ISO 8601 timestamp
* `status` filter not in the allowed enum — see the **API Reference** tab for per-resource status values
* Missing required body field on `POST /v1/webhooks`
