# OTP Provider — интеграция

Сервис выпускает и проверяет одноразовые коды. Ваше приложение **не хранит и не
проверяет коды само** — оно просит сервис отправить код и потом просит сервис его
проверить. Ниже всё, что нужно для интеграции с нуля.

- **Base URL:** `https://static.53.3.29.2.clients.your-server.de`
- **Формат:** JSON, `Content-Type: application/json`
- **Health:** `GET https://static.53.3.29.2.clients.your-server.de/healthz` · `GET https://static.53.3.29.2.clients.your-server.de/readyz`

## Аутентификация

Два уровня доступа:

| Уровень | Заголовок | Для чего |
|---------|-----------|----------|
| **app** | `Authorization: Bearer <API_KEY>` (или `X-API-Key: <API_KEY>`) | отправка/проверка кодов |
| **admin** | `Authorization: Bearer <ADMIN_TOKEN>` | создание приложений, статус каналов |

Каждый ваш продукт (магазин, приложение) — это отдельное **приложение (тенант)** со
своим `API_KEY`. Ключ выдаётся один раз при создании и хранится только у вас.

## Шаг 1. Получить API-ключ (один раз, admin)

```bash
curl -X POST https://static.53.3.29.2.clients.your-server.de/v1/apps \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-app"}'
# → {"id":"app_...","name":"my-app","api_key":"sk_live_...","created_at":"..."}
```

Сохраните `api_key` — повторно он не показывается. Это же можно сделать на дашборде
(карточка «Приложения»).

### Свой текст сообщения

У каждого приложения свой шаблон — так в коде видно название компании:

```bash
curl -X POST https://static.53.3.29.2.clients.your-server.de/v1/apps -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"name":"Ромашка","template":"{app}: ваш код {code}. Действует {ttl} мин.","email_subject":"Код от Ромашки"}'

# поменять позже (поля, которых нет в теле, не трогаются)
curl -X PATCH https://static.53.3.29.2.clients.your-server.de/v1/apps/app_xxx -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"template":"Ромашка. Ваш код: {code}"}'
```

Плейсхолдеры: `{code}` — код (**обязателен**, иначе шаблон не сохранится), `{ttl}` —
минуты жизни кода, `{app}` — название приложения. Пустой шаблон = текст по умолчанию.

Шаблон применяется к **WhatsApp** и **Email**. **Telegram его игнорирует**: Gateway
рисует собственную заверенную формулировку вокруг кода — именно она делает сообщение
доверенным, и подменить её нельзя.

## Шаг 2. Отправить код

```bash
curl -X POST https://static.53.3.29.2.clients.your-server.de/v1/otp/send \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"channel":"telegram","to":"+15551234567"}'
# → {"request_id":"otp_...","channel":"telegram","to":"+15551234567","expires_at":"..."}
```

- `channel` — как доставить код (см. «Каналы»).
- `to` — получатель: телефон в формате E.164 (`+15551234567`) или email.
- Сохраните `request_id` — по нему удобнее всего проверять.

## Шаг 3. Проверить код

```bash
curl -X POST https://static.53.3.29.2.clients.your-server.de/v1/otp/verify \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"request_id":"otp_...","code":"123456"}'
# успех  → {"status":"approved","request_id":"otp_..."}
# провал → {"status":"denied","reason":"invalid_code","attempts_remaining":4}
```

Вместо `request_id` можно проверять по паре `channel` + `to` — сервис возьмёт
последний активный код для этого получателя:

```bash
-d '{"channel":"telegram","to":"+15551234567","code":"123456"}'
```

`reason` при `denied`: `invalid_code`, `expired`, `already_used`, `too_many_attempts`,
`not_found`. HTTP-код при этом `200` — смотрите на поле `status`.

## Каналы

Список доступных именно вам каналов (и их готовность):

```bash
curl https://static.53.3.29.2.clients.your-server.de/v1/channels -H "Authorization: Bearer $API_KEY"
# → {"channels":[{"name":"telegram","ready":true},{"name":"email","ready":false},...]}
```

| Канал | Доставка | Заметки |
|-------|----------|---------|
| `telegram` | официальный Telegram Gateway | доходит на любой номер с Telegram; ~$0.01/сообщение; номер без Telegram → `delivery_failed` |
| `email` | SMTP (любой провайдер) | письмо с кодом: текст + HTML; требует настройки SMTP на сервере |
| `whatsapp` | WhatsApp Web (неофициально) | один привязанный номер; риск бана; включается сборкой |

**Код никогда не возвращается в ответе.** Все каналы реально доставляют сообщение:
приложение получает только `request_id`, а сам код видит лишь получатель. Проверка —
единственный способ узнать, верен ли введённый код.

## Политики и лимиты

- код живёт ограниченное время (TTL), после — `expired`;
- ограниченное число попыток проверки, дальше — `too_many_attempts`;
- есть пауза между повторными отправками на один и тот же адрес (`429 cooldown`) и
  часовой лимит на адрес (`429 rate_limited`);
- один и тот же код нельзя использовать дважды (`already_used`).

## Ошибки

Ошибки транспорта/валидации приходят с соответствующим HTTP-кодом и телом:

```json
{"error":{"code":"channel_unavailable","message":"channel not ready: email"}}
```

Коды: `invalid_request` (400), `unauthorized` (401), `unknown_channel` (400),
`channel_unavailable` (503), `cooldown` / `rate_limited` (429),
`delivery_failed` (502). Результат `verify` с `denied` — это НЕ ошибка (HTTP 200).

## Примеры кода

**JavaScript (fetch):**

```js
const BASE = "https://static.53.3.29.2.clients.your-server.de", API_KEY = process.env.OTP_API_KEY;
const h = { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" };

async function sendCode(to, channel = "telegram") {
  const r = await fetch(`${BASE}/v1/otp/send`, { method: "POST", headers: h,
    body: JSON.stringify({ channel, to }) });
  if (!r.ok) throw new Error((await r.json()).error?.message);
  return (await r.json()).request_id;
}
async function verifyCode(requestId, code) {
  const r = await fetch(`${BASE}/v1/otp/verify`, { method: "POST", headers: h,
    body: JSON.stringify({ request_id: requestId, code }) });
  return (await r.json()).status === "approved";
}
```

**Go (net/http):**

```go
const base = "https://static.53.3.29.2.clients.your-server.de"
func send(apiKey, channel, to string) (string, error) {
    b, _ := json.Marshal(map[string]string{"channel": channel, "to": to})
    req, _ := http.NewRequest("POST", base+"/v1/otp/send", bytes.NewReader(b))
    req.Header.Set("Authorization", "Bearer "+apiKey)
    req.Header.Set("Content-Type", "application/json")
    resp, err := http.DefaultClient.Do(req)
    if err != nil { return "", err }
    defer resp.Body.Close()
    var out struct{ RequestID string `json:"request_id"` }
    json.NewDecoder(resp.Body).Decode(&out)
    return out.RequestID, nil
}
```

## Для ИИ-агентов

Интеграция сводится к двум вызовам: `POST /v1/otp/send` → сохранить `request_id`;
затем `POST /v1/otp/verify` с `request_id` и введённым пользователем `code` → успех,
если `status == "approved"`. Всё под заголовком `Authorization: Bearer <API_KEY>`.
Коды не хранить и не сверять на своей стороне — это делает сервис. Актуальную версию
этого документа можно всегда получить машинно: `GET https://static.53.3.29.2.clients.your-server.de/v1/docs` (Markdown).
