# Error Reference

All errors share a common envelope (set by `AllExceptionsFilter`):

```json
{
  "statusCode": 403,
  "error": "Forbidden",
  "message": "ไม่สามารถแก้ไข preset ที่นำเข้าจาก Community ได้",
  "path": "/v0/images/presets/<id>",
  "timestamp": "2026-04-26T..."
}
```

## HTTP Status Codes

| Code | Meaning | Common Causes |
|---|---|---|
| `400` | Bad Request | Missing required field, invalid `aspectRatio`, `resolution` not supported by model, required `variables` missing |
| `401` | Unauthorized | Missing / wrong / revoked API key. Check `Authorization: Bearer sk-img-...` |
| `403` | Forbidden | Trying to edit a `COMMUNITY`-imported preset, or wrong access scope |
| `404` | Not Found | Preset not in this team, job/log not owned by this team, slug-traversal blocked |
| `429` | Too Many Requests | Rate limit (if configured) |
| `500` | Internal Server Error | Unexpected server failure — please retry |
| `502` / `503` | Upstream provider error | Vertex / OpenAI / xAI returned a transient failure |
| `504` | Gateway Timeout | Sync generation exceeded 180s — poll `GET /v0/images/generations/:jobId` |

## Concrete Examples

### `403` — editing a community-imported preset

```
PATCH /v0/images/presets/<community-imported-id>
→ 403 { "error": "ไม่สามารถแก้ไข preset ที่นำเข้าจาก Community ได้" }
```

Fix: only edit presets your team authored (`accessLevel: "FULL"`). Fork the ชุมชนพรีเช็ต via the web UI to create a team copy.

### `404` — preset not in this team

```
PATCH /v0/images/presets/<other-team-preset-id>
→ 404 { "error": "ไม่พบ preset ของทีม" }
```

API keys are team-scoped — you can only mutate presets the key's team owns.

### `400` — missing required variable

```
POST /v0/images/generations
{ "presetId": "...", "variables": {} }
→ 400 { "error": "ค่า {{product_name}} จำเป็นต้องระบุ" }
```

Always send every `requirement.key` where `required: true`.

### `400` — invalid aspect ratio

```
POST /v0/images/generations
{ "prompt": "...", "aspectRatio": "5:3" }
→ 400 { "error": "Invalid aspectRatio \"5:3\". Allowed: 1:1, 1:2, ..." }
```

See `images.md` for the full whitelist.

### `401` — bad API key

```
GET /v0/images/presets
(no Authorization header)
→ 401 { "error": "Missing API key" }

(wrong format)
→ 401 { "error": "Invalid API key format" }

(revoked or unknown)
→ 401 { "error": "Invalid or revoked API key" }
```

### `504` — sync generation timeout

```
POST /v0/images/generations
{ ..., "modelVersion": "gpt-image-2" }   // slow model
→ 504 {
  "error": "Generation timed out after 180s",
  "jobId": "abc-123",
  "hint": "GET /v0/images/generations/abc-123 to check status"
}
```

Either:
1. Use `?async=true` for slow models — gets `{ id, status: 'queued' }` immediately, poll the job.
2. Catch the 504 and call `GET /v0/images/generations/:jobId` with the returned `jobId`.

## Retry Policy

- **400 / 403 / 404** — don't retry; fix the request.
- **401** — don't retry; re-check the API key.
- **429** — backoff (exponential) and retry.
- **500 / 502 / 503** — backoff and retry up to 3× over ~30s.
- **504** — switch to async mode or poll the returned `jobId`.
