# Taxi Watcher — API для B2B-клиентов

Руководство по подключению сервиса расчёта стоимости такси к вашему продукту.

Версия документа: 1.0

Базовый URL: `https://taxi-watcher.ru`

---

## 1. Что это и как это работает

Taxi Watcher отдаёт **актуальную стоимость и время поездки такси** между
двумя адресами по вашему запросу. Вы передаёте адреса отправления и
назначения, получаете цену, дистанцию, время в пути и ожидания подачи.

Один запрос = одна поездка. Ваш бэкенд вызывает API, сервис считает цену и
возвращает готовый результат. Дальше вы используете его как хотите — показываете
цену пользователям, сравниваете тарифы, автоматизируете заказы.

Схема работы:

```
Ваш бэкенд  --(адреса)-->  Taxi Watcher API  --(цена, время)-->  Ваш бэкенд
                              |  X-API-Key
```

---

## 2. Быстрый старт

Подключение занимает 3 шага:

1. **Получите API-ключ.** Ключ выпускает администратор сервиса. Он выглядит
   как `tw_live_` + 32 символа, например `tw_live_a1b2c3…`.
2. **Передавайте ключ в заголовке** каждого запроса:

   ```
   X-API-Key: tw_live_<ваш ключ>
   ```

3. **Сделайте первый запрос** — посчитайте стоимость поездки:

```bash
curl -X POST https://taxi-watcher.ru/api/v1/estimate \
  -H "X-API-Key: tw_live_<ваш ключ>" \
  -H "Content-Type: application/json" \
  -d '{"from_address":"Москва, улица Леси Украинки, 4к1","to_address":"Москва, Люсиновская улица, 7/8с1","tariff":"econom"}'
```

В ответе придёт цена, дистанция и время в пути (подробно — в разделе 4).

---

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

Единственный механизм — заголовок **`X-API-Key`** с вашим ключом.

| Параметр | Значение |
|---|---|
| Заголовок | `X-API-Key: tw_live_<токен>` |
| Формат токена | `tw_live_` + 32 hex-символа |
| Как передавать | В каждом запросе к `/api/v1/*` |
| Ключ неактивен / неверен | Ответ `401 Unauthorized` |

> Ключ выдаётся один раз и показывается полностью **только при создании**.
> Храните его в секрете — это ваш доступ к сервису и ваш биллинг.
> Если ключ утёк — **ротируйте его** (см. раздел 7), старый перестанет работать.

---

## 4. Расчёт стоимости поездки

### `POST /api/v1/estimate`

**Заголовки:**

| Заголовок | Обязателен | Описание |
|---|---|---|
| `X-API-Key` | да | Ваш ключ клиента |
| `Content-Type` | да | `application/json` |

**Тело запроса (JSON):**

```json
{
  "from_address": "Москва, улица Леси Украинки, 4к1",
  "to_address": "Москва, Люсиновская улица, 7/8с1",
  "tariff": "econom"
}
```

| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| `from_address` | string | да | Адрес отправления (свободный текст) |
| `to_address` | string | да | Адрес назначения (свободный текст) |
| `tariff` | string | нет | Класс тарифа (по умолчанию `econom`) |

**Допустимые тарифы:**

| Значение | Тариф |
|---|---|
| `econom` | Эконом |
| `comfort` | Комфорт |
| `comfortplus` | Комфорт+ |
| `business` | Бизнес |
| `vip` | VIP / Premier |
| `minivan` | Минивэн |
| `cargo` | Грузовой |
| `express_d2d` | Доставка |

> Если запрошенный тариф недоступен на маршруте, сервер сам выберет первый
> доступный — фактический класс придёт в поле `data.tariff`.

**Пример успешного ответа (`200 OK`):**

```json
{
  "status": "success",
  "timestamp": 1700000000,
  "data": {
    "from": "Москва, улица Леси Украинки, 4к1",
    "to": "Москва, Люсиновская улица, 7/8с1",
    "tariff": "econom",
    "tariff_name": "Эконом",
    "price": 2050,
    "base_price": 1339,
    "surge_active": true,
    "currency": "RUB",
    "distance": "12,45 км",
    "duration": "~ 35 мин",
    "wait_time": "~ 3 мин",
    "cached_at": 1700000000
  }
}
```

**Поля `data`:**

| Поле | Тип | Описание |
|---|---|---|
| `from` / `to` | string | Адреса, по которым считался маршрут |
| `tariff` | string | Фактический класс тарифа |
| `tariff_name` | string | Человекочитаемое имя тарифа |
| `price` | int | Итоговая цена с учётом спроса (в копейках) |
| `base_price` | int | Базовая цена без спроса |
| `surge_active` | bool | Признак повышенного спроса (`price > base_price`) |
| `currency` | string | Валюта (обычно `RUB`) |
| `distance` | string | Дистанция, например `"12,45 км"` |
| `duration` | string | Время в пути, например `"~ 35 мин"` |
| `wait_time` | string | Ожидание подачи, например `"~ 3 мин"` |
| `cached_at` | int | Unix-время расчёта |

---

## 5. Лимиты

На каждый ключ действуют два ограничения:

| Уровень | Лимит | Что происходит при превышении |
|---|---|---|
| В минуту (RPM) | задаётся на ключе (по умолчанию **10/мин**) | `429 Too Many Requests` |
| За месяц (квота) | задаётся на ключе (по умолчанию **1000/мес**) | `429 Too Many Requests` |

Текущее использование и остаток видны в личном кабинете. Если упёрлись в лимит —
обратитесь к администратору, чтобы увеличить квоту.

Одинаковые запросы (одинаковые адреса и тариф) кэшируются сервером на
несколько минут — повторные вызовы отдаются быстро и **не тратят квоту Яндекса**
(но учитываются в вашей статистике).

---

## 6. Коды ответов и ошибки

| Код | Что значит | Как реагировать |
|---|---|---|
| `200 OK` | Успех — вернулась цена | Обрабатывайте `data` |
| `400 Bad Request` | Не удалось распознать адрес | Проверьте адреса, попробуйте полнее |
| `401 Unauthorized` | Ключ неверный или деактивирован | Проверьте ключ, активируйте его |
| `404 Not Found` | Для маршрута нет доступных тарифов | Сообщите пользователю |
| `422 Unprocessable Entity` | Не хватает обязательных полей | Проверьте тело запроса |
| `429 Too Many Requests` | Превышен лимит (в минуту или за месяц) | Подождите / увеличьте квоту |
| `500 Internal Server Error` | Внутренняя ошибка расчёта | Повторите позже |
| `502 Bad Gateway` | Сбой сети / партнёра | Повторите позже |

**Формат ошибок** — всегда строка в поле `detail`:

```json
{ "detail": "описание ошибки" }
```

Пример — превышение лимита в минуту:

```json
{ "detail": "Rate limit exceeded: max 10 requests per minute." }
```

Пример — исчерпана месячная квота:

```json
{ "detail": "Monthly quota exceeded (1000 requests). Please upgrade your plan." }
```

При ошибках валидации (`422`) `detail` — это массив объектов с описанием
конкретных проблем (какое поле, что не так).

---

## 7. Ваш личный кабинет

Кабинет B2B-клиента доступен по адресу **`/b2b`**. Здесь вы управляете своими
ключами, смотрите статистику и лимиты.

### Вход и выход

- Вход — `/b2b/login` (email + пароль).
- Выход — кнопка в кабинете (`POST /b2b/logout`).
- После входа открывается дашборд `/b2b/dashboard`: ваши ключи, квоты,
  быстрый старт с готовым cURL-запросом и ссылка на эту документацию.

### Возможности кабинета

| Действие | Где | Что даёт |
|---|---|---|
| Список моих ключей | Дашборд `/b2b/dashboard` | Превью ключа, лимит, использовано, остаток |
| Показать полный ключ | Кнопка «Показать токен» | Полный токен для копирования |
| Статистика по ключу | Кнопка «Графики» | Всего/сегодня/месяц, ошибки, последние запросы |
| Ротация ключа | по API, см. ниже | Новый токен — старый перестаёт работать |
| Активация/деактивация | по API, см. ниже | Включить/выключить ключ |

### JSON-эндпоинты кабинета

Все требуют авторизованной B2B-сессии (вы в кабинете). Доступ — **только к
своим ключам**.

**Список моих ключей** — `GET /b2b/api/keys`:
```json
[
  {
    "id": 1,
    "key_preview": "tw_live_abc••••1234",
    "client_name": "Demo Logistics Co.",
    "is_active": true,
    "limit_monthly": 1000,
    "rate_limit_per_minute": 10,
    "used_this_month": 12,
    "quota_percent": 1.2,
    "remaining": 988,
    "total_requests": 80,
    "created_at": "2024-01-01 12:00",
    "expires_at": null
  }
]
```

**Показать полный ключ** — `GET /b2b/api/keys/{key_id}/reveal`:
```json
{ "id": 1, "key": "tw_live_<токен>" }
```

**Статистика по ключу** — `GET /b2b/api/keys/{key_id}/stats`:
```json
{
  "key_id": 1,
  "client_name": "Demo Logistics Co.",
  "is_active": true,
  "limit_monthly": 1000,
  "total_requests": 80,
  "today_requests": 3,
  "month_requests": 40,
  "avg_response_ms": 350.5,
  "error_count": 5,
  "redis_usage": {
    "used": 12,
    "limit": 1000,
    "remaining": 988,
    "percent": 1.2,
    "month": "2024-01"
  },
  "recent_logs": [
    { "endpoint": "/api/v1/estimate", "status_code": 200, "response_time_ms": 300 }
  ]
}
```

**Ротация ключа** — `POST /b2b/api/keys/{key_id}/rotate`:
```json
{ "status": "rotated", "id": 1, "key": "tw_live_<новый токен>" }
```
Старый токен немедленно перестаёт работать. Используйте, если ключ утёк.

**Активация / деактивация** — `PATCH /b2b/api/keys/{key_id}`:
```json
{ "is_active": false }
```

---

## 8. Примеры запросов

### Расчёт стоимости (основной сценарий)

```bash
curl -X POST https://taxi-watcher.ru/api/v1/estimate \
  -H "X-API-Key: tw_live_<токен>" \
  -H "Content-Type: application/json" \
  -d '{"from_address":"Москва, улица Леси Украинки, 4к1","to_address":"Москва, Люсиновская улица, 7/8с1","tariff":"business"}'
```

### Сравнить тарифы на маршруте

Повторите запрос с разными `tariff` (`econom`, `comfort`, `business`, …) —
выберите лучшее значение `price`.

### Ротация ключа (если он утёк)

```bash
curl -X POST https://taxi-watcher.ru/b2b/api/keys/1/rotate \
  -H "Cookie: session=<сессия из кабинета>"
```

> Эндпоинты кабинета (`/b2b/api/*`) используют сессию из вашего входа в
> кабинет. В повседневной работе вы обычно управляете ключами через интерфейс
> кабинета, а программно вызываете только `/api/v1/estimate`.

---

## 9. Советы

- **Повышенный спрос:** сравнивайте `price` и `base_price` — если
  `surge_active: true`, цена выше базовой. Можно подсказать пользователю
  подождать.
- **Проверяйте `tariff` в ответе** — сервер мог подставить доступный класс,
  если запрошенный не нашёлся на маршруте.
- **Следите за квотой** в кабинете, чтобы не упереться в лимит в самый нужный
  момент.
- **Не храните ключ в коде клиента** — держите его на бэкенде и передавайте
  только в заголовке.
- **Используйте кэш:** одинаковые маршруты повторно не списывают квоту Яндекса.

---

*Если нужен дополнительный ключ, больший лимит или нестандартный тариф —
свяжитесь с владельцем Taxi Watcher в Telegram – @fancycode*
