# Помилки

Усі помилки повертаються у єдиному форматі — `ok: false` плюс об'єкт `error`:

```json
{
  "ok": false,
  "error": {
    "code": "INVALID_API_KEY",
    "message": "Invalid API key"
  }
}
```

- `code` — машиночитний `UPPER_SNAKE_CASE` код, **стабільний**, не змінюється без мажорного бампа. Свіч по ньому, не по `message`.
- `message` — людиночитне пояснення, англійською. Текст може уточнюватись без бампа.
- `details` — опціональний. Для `INVALID_INPUT` це масив `{ path, message }` по кожному проваленому полю.
- `request_id` — додається лише до `5xx` та `BAD_JSON` відповідей; цитуй його при зверненні в підтримку.

## HTTP-коди

| Статус | Значення |
|---|---|
| `200 OK` | Успіх |
| `400 Bad Request` | Битий JSON, відсутні поля або провалена Zod-валідація |
| `401 Unauthorized` | Відсутній або невалідний `X-Api-Key` |
| `402 Payment Required` | Потрібен вищий тариф / пак, або підписка неактивна |
| `403 Forbidden` | Ключ валідний, але немає прав (scope, origin, suspended) |
| `404 Not Found` | Ресурс, ключ або job не існує |
| `422 Unprocessable Entity` | Вхід валідний, але рушій не зміг порахувати |
| `429 Too Many Requests` | Перевищено rate limit, вичерпано кредити або spend-cap |
| `500 Internal Server Error` | Внутрішня помилка — з нашого боку, цитуй `request_id` |
| `503 Service Unavailable` | AI-шлюз / черга тимчасово недоступні |

## Error codes

### Автентифікація (401)

| Код | Значення |
|---|---|
| `MISSING_API_KEY` | Header `X-Api-Key` не надіслано |
| `INVALID_API_KEY` | Ключ не існує або revoked |
| `MISSING_BEARER` | Схема `Authorization: Bearer` обрана, але токен відсутній |
| `WRONG_AUTH_METHOD` | На `/v1/me/*` (Bearer-only) надіслано `X-Api-Key`. Для перевірки ключа — `GET /v1/auth/keys/me`; для викликів API — будь-який `/v1/*` з `X-Api-Key` |

### Права (403)

| Код | Значення |
|---|---|
| `INSUFFICIENT_SCOPE` | Ключ не має прав на цю дію |
| `DOMAIN_MISMATCH` / `FORBIDDEN_ORIGIN` | Origin запиту не в allow-list ключа |
| `KEY_SUSPENDED` | Ключ призупинено |

### Білінг (402)

| Код | Значення |
|---|---|
| `PLAN_UPGRADE_REQUIRED` | Ендпоінт потребує вищого тарифу або add-on пака |
| `PLAN_PACK_MISMATCH` | Ендпоінт входить у пак, якого немає на ключі |
| `SUBSCRIPTION_EXPIRED` | Підписка касована або expired |

### Rate limiting та кредити (429)

| Код | Значення |
|---|---|
| `RATE_LIMIT` | Перевищено ліміт RPM. Відповідь містить header `Retry-After: <sec>` |
| `CREDITS_EXHAUSTED` | Місячний бюджет кредитів вичерпано |
| `SPEND_CAP_REACHED` | Досягнуто ліміт витрат на AI-ендпоінти |

### Валідація (400)

| Код | Значення |
|---|---|
| `INVALID_INPUT` | Одне або кілька полів провалили Zod-валідацію. `details` — масив `{ path, message }` |
| `MISSING_FIELDS` | Відсутнє обов'язкове поле |
| `BAD_JSON` | Битий JSON у тілі запиту |

Приклад `400`:

```json
{
  "ok": false,
  "error": {
    "code": "INVALID_INPUT",
    "message": "Validation failed: date: Invalid input: expected string, received undefined",
    "details": [
      { "path": "date", "message": "Invalid input: expected string, received undefined" },
      { "path": "time", "message": "Invalid input: expected string, received undefined" }
    ]
  }
}
```

### Розрахункові помилки (422)

| Код | Значення |
|---|---|
| `CALCULATION_ERROR` | Вхід валідний, але рушій не зміг порахувати (наприклад, полярна широта для Placidus). Спробуй Whole Sign / Equal або іншу дату. |

### Серверні

| Код | HTTP | Значення |
|---|---|---|
| `INTERNAL_ERROR` | 500 | Неочікувана помилка. Логуємо зі stack trace, цитуй `request_id` |
| `GATEWAY_UNAVAILABLE` / `AI_UNAVAILABLE` / `LLM_UNAVAILABLE` | 503 | AI-шлюз недоступний. Retry з експоненційним backoff |
| `QUEUE_UNAVAILABLE` | 503 | Черга фонових задач недоступна |

## Обробка в клієнтському коді

```ts
type ApiError = {
  ok: false;
  error: {
    code: string;
    message: string;
    details?: unknown;
    request_id?: string;
  };
};

async function chart(input: ChartInput) {
  const res = await fetch('https://api.astroway.info/v1/chart', {
    method: 'POST',
    headers: {
      'X-Api-Key': process.env.ASTROWAY_API_KEY!,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(input),
  });

  const body = await res.json();
  if (!body.ok) {
    throw new AstrowayError(body.error.code, body.error.message, body.error.request_id);
  }

  return body.data;
}

class AstrowayError extends Error {
  constructor(
    public code: string,
    message: string,
    public requestId?: string,
  ) {
    super(`[${code}] ${message}${requestId ? ` (request_id=${requestId})` : ''}`);
  }
}
```

Свіч по `code`, не по `message`. Офіційні SDK ([`@astroway/sdk`](https://www.npmjs.com/package/@astroway/sdk), [`astroway`](https://pypi.org/project/astroway/)) кидають типізовані підкласи помилок за HTTP-статусом (`AuthenticationError`, `RateLimitError`, `BadRequestError` …), тож код обробки писати не доведеться.

## Запит підтримки

Для звернення надішли на [support@astroway.info](mailto:support@astroway.info):

1. `request_id` з відповіді (якщо це `5xx`)
2. Ендпоінт і скорочений JSON body (без секретів)
3. Очікувана поведінка vs фактична
4. Часовий пояс і приблизний час запиту (UTC)

Відповідь — у межах SLA твого плану (48 год Starter, 24 год Pro, власна для Enterprise).
