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

# Подписки

> Список пакетов и выдача подписки пользователю

Пользователь оплачивает пакет **на вашей стороне**, после чего вы выдаёте ему подписку в Ai-Seven через API. Взаиморасчёт между компаниями происходит отдельно, по итогам периода.

## Список пакетов

```http theme={null}
GET /api/v1/partner/subscription-plans
```

Возвращает пакеты, доступные для выдачи. У каждого варианта зафиксированы срок и объём токенов.

```json theme={null}
[
  {
    "title": "Базовый",
    "prices": [
      { "id": 2, "period_months": 3,  "tokens": 200000, "price": 5500 },
      { "id": 3, "period_months": 6,  "tokens": 400000, "price": 8500 },
      { "id": 4, "period_months": 12, "tokens": 700000, "price": 12500 }
    ]
  }
]
```

<ResponseField name="title" type="string">
  Название пакета.
</ResponseField>

<ResponseField name="prices" type="array">
  Варианты пакета с разными сроками.

  <Expandable title="поля">
    <ResponseField name="id" type="integer">
      Идентификатор варианта. Его нужно передавать при выдаче как `plan_price_id`.
    </ResponseField>

    <ResponseField name="period_months" type="integer">
      Срок подписки в месяцах.
    </ResponseField>

    <ResponseField name="tokens" type="integer">
      Количество токенов, начисляемых при выдаче.
    </ResponseField>

    <ResponseField name="price" type="number">
      Стоимость.
    </ResponseField>
  </Expandable>
</ResponseField>

<Note>
  Запрашивайте список перед выдачей — состав пакетов и цены могут меняться. Выдать можно только существующий вариант.
</Note>

## Выдача подписки

```http theme={null}
POST /api/v1/partner/subscriptions
```

### Заголовки

```text theme={null}
Authorization: Bearer <JWT>
Idempotency-Key: <уникальный ключ операции>
```

<ParamField header="Idempotency-Key" type="string" required>
  Уникальный ключ операции, от 6 до 64 символов. Защищает от повторной выдачи при таймаутах и ретраях.
</ParamField>

### Тело запроса

```json theme={null}
{
  "user_id": "u_12345",
  "plan_price_id": 3,
  "payment_id": "pay_998877",
  "user_info": {
    "first_name": "Иван",
    "last_name": "Петров"
  }
}
```

<ParamField body="user_id" type="string" required>
  Идентификатор пользователя в вашей системе — тот же, что вы передаёте в `sub` токена входа.
</ParamField>

<ParamField body="plan_price_id" type="integer" required>
  Идентификатор варианта пакета из `GET /subscription-plans`.
</ParamField>

<ParamField body="payment_id" type="string" required>
  Идентификатор платежа в вашей системе. Используется при сверке взаиморасчётов и защищает от повторной выдачи по одному платежу. **Важно:** параметр уникален в рамках партнёра.
</ParamField>

<ParamField body="user_info" type="object">
  Данные пользователя для отображения в кабинете. Учитываются только при создании нового аккаунта.

  <Expandable title="поля">
    <ParamField body="first_name" type="string">
      Имя.
    </ParamField>

    <ParamField body="last_name" type="string">
      Фамилия.
    </ParamField>
  </Expandable>
</ParamField>

### Ответ

```json theme={null}
{
  "user_id": "u_12345",
  "activated_until": 1770252120,
  "plan_title": "Базовый",
  "plan_price_id": 3
}
```

<ResponseField name="user_id" type="string">
  Ваш идентификатор пользователя.
</ResponseField>

<ResponseField name="activated_until" type="integer">
  Момент окончания действия пакета, unix-время.
</ResponseField>

<ResponseField name="plan_title" type="string">
  Название выданного пакета.
</ResponseField>

<ResponseField name="plan_price_id" type="integer">
  Идентификатор выданного варианта — тот же, что вы передали в запросе.
</ResponseField>

### Пример

<CodeGroup>
  ```python Python theme={null}
  import uuid
  import requests

  requests.post(
      "https://aisevenai.ru/api/v1/partner/subscriptions",
      headers={
          "Authorization": f"Bearer {api_token}",
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json={
          "user_id": "u_12345",
          "plan_price_id": 3,
          "payment_id": "pay_998877",
          "user_info": {"first_name": "Иван", "last_name": "Петров"},
      },
  )
  ```

  ```javascript Node.js theme={null}
  import { randomUUID } from "crypto";

  await fetch("https://aisevenai.ru/api/v1/partner/subscriptions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiToken}`,
      "Idempotency-Key": randomUUID(),
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      user_id: "u_12345",
      plan_price_id: 3,
      payment_id: "pay_998877",
      user_info: { first_name: "Иван", last_name: "Петров" },
    }),
  });
  ```

  ```bash cURL theme={null}
  curl -X POST https://aisevenai.ru/api/v1/partner/subscriptions \
    -H "Authorization: Bearer $API_TOKEN" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
      "user_id": "u_12345",
      "plan_price_id": 3,
      "payment_id": "pay_998877"
    }'
  ```
</CodeGroup>

## Поведение

<AccordionGroup>
  <Accordion title="Пользователь ещё не заходил в Ai-Seven">
    Аккаунт создаётся автоматически, пакет ждёт первого входа. Сценарий «купил, потом зашёл» работает — пользователю достаточно перейти по кнопке из вашего кабинета.
  </Accordion>

  <Accordion title="У пользователя уже есть подписка">
    Срок подписки на платформу **продлевается** на период нового пакета. Сам пакет при этом **заменяется** на выданный — пакеты не суммируются.
  </Accordion>

  <Accordion title="Повторный запрос с тем же Idempotency-Key">
    Возвращается результат первой операции — включая ошибку, если она была. Повторной выдачи не происходит.

    Если первая операция ещё выполняется, вернётся `409`. Если ключ пришёл с другим телом запроса — `422`.
  </Accordion>

  <Accordion title="Повторный запрос с тем же payment_id">
    Возвращается `409`. Один платёж может быть использован для выдачи только один раз, независимо от ключа идемпотентности.
  </Accordion>
</AccordionGroup>

## Ошибки

Помимо [ошибок авторизации](/partner-api/overview#ошибки-авторизации):

| Код   | Сообщение                                               | Причина                                                    |
| ----- | ------------------------------------------------------- | ---------------------------------------------------------- |
| `404` | Пакет подписки не найден                                | Указан несуществующий `plan_price_id`                      |
| `409` | Платеж с таким payment\_id уже есть                     | По этому платежу подписка уже выдавалась                   |
| `409` | Операция выполняется                                    | Запрос с этим `Idempotency-Key` ещё обрабатывается         |
| `422` | Ключ идемпотентности использован с другим телом запроса | Тот же ключ прислан с изменёнными данными                  |
| `422` | —                                                       | Тело запроса не прошло валидацию                           |
| `500` | Не удалось создать пользователя                         | Внутренняя ошибка при создании аккаунта — повторите запрос |
| `503` | Не удалось обработать запрос, повторите попытку         | Временная ошибка — повторите запрос с тем же ключом        |

<Warning>
  Используйте новый `Idempotency-Key` на каждую операцию выдачи. При повторном использовании ключа с другими данными вернётся `422`, и выдача не выполнится.
</Warning>
