# Каталог и продажи

Source: <https://telegafirst.com/docs/catalog-and-sales>
Locale: ru
releaseGitSha: 0bb11ef116e17ec64c802d5476eaff442e53d252
sourceContentDigest: 6b7f6f9f1b3c7ebde46d4c844aafba09ccd88e47f2412bfcce025ff08ebe417a
Version: 2

Каталог помогает превратить разговор с покупателем в заказ. В TelegaFirst — AI-фронт-офисе в Telegram для микробизнеса — ваш собственный агент может прочитать позиции, подготовить изменение или продажу и выполнить согласованное действие через Hub. Владелец и операторы видят диалоги в Telegram и могут перехватить разговор у AI.

Результат этого руководства: вы находите позицию своего бизнеса, меняете её со свежей версией, создаёте согласованный заказ и отдельно готовите отправку счёта. Созданный заказ ещё не означает оплату.

## Что нужно до начала

Завершите [подключение бизнеса](https://telegafirst.com/docs/getting-started), [авторизацию](https://telegafirst.com/docs/authorization) и проверьте активный бизнес. В MCP прочитайте `get_active_bot`, затем обновите `tools/list`. В REST проверьте `X-Client-Slug` и `X-Bot-Username` ответа. API key закреплён за своим бизнесом; OAuth-подключение действует от имени человека с его текущими правами.

Чтение каталога требует `catalog:read`, изменение — `catalog:write`, ручная продажа и денежные действия — `orders:write`. Для REST-чтения заказов нужен `pull:orders`; обычный `POST /api/v1/orders` использует отдельное право `orders`. Если нужны данные покупателя, отдельно требуется `pull:users`. Сервер проверяет все права операции при каждом вызове; видимый инструмент не заменяет эту проверку. Для чтения данных каталога и заказов действует тарифная проверка: в пробном периоде она пройдена, после него личному агенту владельца или оператора нужно открытое платное окно любого тарифа, ключу интеграции — AI Manager или выше с открытым окном; при `PLAN_UPGRADE_REQUIRED` откройте [тарифы](https://telegafirst.com/docs/pricing-and-trial), не пытайтесь заменить отказ другим ключом.

Внутренние Qualifier, Manager и Coach могут советовать и передавать разговор оператору. Денежные действия выполняет уполномоченный собственный агент клиента через Hub. Рабочее место операторов остаётся в Telegram.

## Найти позицию и проверить доступность

Вызов MCP `get_catalog` принимает верхнеуровневые `zone`, `limit` и полученный ранее `cursor`:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_catalog",
    "arguments": {
      "zone": "ru",
      "limit": 10
    }
  }
}
```

REST использует `GET /api/v1/catalog?zone=ru&limit=10`. `limit` — целое число от 1 до 100, по умолчанию 50; `zone` — `ru` или `com`, по умолчанию `ru`. Обе зоны предусмотрены для бизнеса; выбор зоны влияет на представление, а русский остаётся языком этого руководства. Следующую страницу запрашивайте только с `meta.next_cursor` из ответа. Сохраняйте зону и условия чтения; не считайте cursor номером страницы и не конструируйте его самостоятельно.

Ниже полный пример тела успешного списка для `GET /api/v1/catalog?zone=ru&cursor=Ng&limit=1` с одной позицией. Номера, даты и cursor — учебные значения из контрактного примера, а не существующие объекты вашего бизнеса. HTTP-статус — `200`; тело инструмента MCP имеет ту же структуру, но не показанный здесь внешний транспортный envelope MCP.

```json
{
  "success": true,
  "data": [
    {
      "seq_id": 7,
      "title": "Course",
      "title_alt": "Course EN",
      "is_active": true,
      "is_public": false,
      "prices": [
        {
          "currency": "RUB",
          "amount": "125.50",
          "trial_amount": null
        },
        {
          "currency": "USD",
          "amount": "2.50",
          "trial_amount": null
        },
        {
          "currency": "EUR",
          "amount": null,
          "trial_amount": null
        }
      ],
      "available_providers": [],
      "item_type": "product",
      "sku_code": null,
      "measurement_unit": "шт",
      "requires_postal_code": false,
      "updated_at": "2026-10-01T10:00:00.000Z",
      "editable_config": {
        "etag": "W/\"57f60dd745c6591b\"",
        "editable_fields": [
          "title",
          "title_alt",
          "description",
          "mediaRefs",
          "description_alt",
          "is_active",
          "is_public",
          "prices",
          "item_type",
          "sku_code",
          "measurement_unit",
          "requires_postal_code",
          "booking",
          "trial",
          "variants",
          "ru_vat_rate",
          "ru_price_includes_tax",
          "intl_tax_rate_percent",
          "intl_price_includes_tax"
        ]
      }
    }
  ],
  "meta": {
    "has_more": true,
    "next_cursor": "Nw",
    "limit": 1
  }
}
```

Доступность задаёт пара `is_active` / `is_public`: `is_active:false` выключает продажи; оба значения `true` делают позицию открытой для предложения; `is_active:true` вместе с `is_public:false` означает доступ по ссылке. Такую позицию агент не предлагает сам без соответствующего контекста. Пустой `available_providers` не обещает работающую онлайн-оплату: проверьте [платёжную настройку](https://telegafirst.com/docs/payments).

Для detail используйте `get_catalog_item` с `{"seq_id":7,"zone":"ru"}` либо `GET /api/v1/catalog/7?zone=ru`. Берите номер из своего списка. `seq_id` — номер позиции внутри выбранного бизнеса. Внутренний ID базы данных не является адресом Hub. Ссылку для покупателя стройте только из возвращённого opaque `deeplink_code` и адреса своего бизнеса; не переносите `seq_id` в анонимный URL. Подробнее: [идентификаторы и страницы списков](https://telegafirst.com/docs/identifiers-and-pagination).

## Изменить позицию со свежей версией

Перед записью прочитайте detail, покажите человеку текущую позицию и согласованное изменение. Сохраните точный `editable_config.etag`; REST также возвращает `ETag`. Передавайте только разрешённые поля из `editable_fields`.

Для REST PATCH требуется API credential в `X-Api-Key` либо `Authorization: Bearer tgf_*`; проверка Bearer сессии на GET не делает такую сессию разрешённой для PATCH. Собственный агент через MCP действует под своим OAuth grant или другим разрешённым креденшлом и проходит те же проверки записи.

MCP-запрос для согласованного изменения названия:

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "update_catalog_item",
    "arguments": {
      "seq_id": 7,
      "zone": "ru",
      "etag": "W/\"57f60dd745c6591b\"",
      "idempotency_key": "catalog-change",
      "patch": {
        "title": "Changed"
      }
    }
  }
}
```

В REST это `PATCH /api/v1/catalog/7?zone=ru`, заголовки `If-Match: W/"57f60dd745c6591b"` и `Idempotency-Key: catalog-change`, тело:

```json
{
  "title": "Changed"
}
```

Успех — `200`, новый `ETag` и полный detail. Здесь приведён полный ответ для учебной позиции:

```json
{
  "seq_id": 7,
  "title": "Changed",
  "title_alt": "Course EN",
  "description": "Frozen course",
  "description_alt": "Frozen EN",
  "is_active": true,
  "is_public": false,
  "is_free": false,
  "groups": [],
  "editable_config": {
    "etag": "W/\"bc0efc7576cb13b2\"",
    "editable_fields": [
      "title",
      "title_alt",
      "description",
      "mediaRefs",
      "description_alt",
      "is_active",
      "is_public",
      "prices",
      "item_type",
      "sku_code",
      "measurement_unit",
      "requires_postal_code",
      "booking",
      "trial",
      "variants",
      "ru_vat_rate",
      "ru_price_includes_tax",
      "intl_tax_rate_percent",
      "intl_price_includes_tax"
    ]
  },
  "prices": [
    {
      "currency": "RUB",
      "amount": "125.50",
      "trial_amount": null
    },
    {
      "currency": "USD",
      "amount": "2.50",
      "trial_amount": null
    },
    {
      "currency": "EUR",
      "amount": null,
      "trial_amount": null
    }
  ],
  "item_type": "product",
  "sku_code": null,
  "measurement_unit": "шт",
  "requires_postal_code": false,
  "booking": {
    "enabled": false,
    "mode": "hourly",
    "duration_value": null,
    "buffer_minutes": 0,
    "resource_seq_nums": [
      12
    ]
  },
  "trial": null,
  "deeplink_code": "ProductCode12345",
  "ru_vat_rate": null,
  "ru_price_includes_tax": null,
  "intl_tax_rate_percent": null,
  "intl_price_includes_tax": null,
  "variants": null,
  "updated_at": "2026-10-01T10:01:00.000Z"
}
```

PATCH и его точный повтор возвращают сохранённый detail команды. Текущую доступность способов оплаты в `available_providers` проверяйте свежим `GET /api/v1/catalog/7?zone=ru`.

Для нового изменения используйте новый ключ и свежий ETag. Для повтора того же запроса после потери ответа сохраните прежний ключ и тело: каталог использует долговременную квитанцию команды. Даже прежний ETag может сопровождать точный подтверждённый повтор; это не разрешение менять тело под тем же ключом. [Версии и повторы](https://telegafirst.com/docs/etag-and-idempotency) объясняют диагностику конфликта.

## Продажа агентом: подготовить, показать, подтвердить

Сначала найдите покупателя и позицию в текущем бизнесе. Для самостоятельной продажи нужны `user_seq_num` и `catalog_seq_num`; для продажи по регистрации — только `registration_seq_num`. Эти две формы нельзя смешивать. Учебные номера ниже не нужно копировать в свой бизнес.

1. Вызовите `prepare_manual_sale`. Подготовка не создаёт заказ.
2. Прочитайте возвращённый `payload`, проверьте выбранный бизнес, покупателя, позицию или регистрацию, а также сумму, валюту и способ оплаты. Если задаёте собственную цену, передайте `amount`, `currency` и `provider` вместе: сумма — положительная десятичная строка с максимум двумя знаками после точки. Подготовка замораживает введённый сценарий, а проверка актуальной возможности продажи выполняется перед исполнением.
3. Покажите человеку понятное описание: кому, что, за какую сумму и каким способом продаётся. Спросите: «Создать этот заказ на этих условиях?» Дождитесь явного подтверждения именно показанного сценария.
4. Вызовите `execute_manual_sale` с двумя значениями из той же подготовки: `preparation_id` и `payload_hash`. Не добавляйте `confirmed`, новую цену, tenant ID или собственный idempotency key в execute.

Пример запроса подготовки:

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "prepare_manual_sale",
    "arguments": {
      "user_seq_num": 17,
      "catalog_seq_num": 23
    }
  }
}
```

В успешном теле подготовки сервер возвращает `preparation_id` (UUID), `payload_hash` (64 шестнадцатеричных символа), `expires_at` и полный `payload`. Копируйте их из фактического результата. Следующий фрагмент показывает точное построение execute-аргументов из просмотренной подготовки; `reviewedPreparation` — её реальное тело, а не созданный клиентом объект:

```javascript
const executeArguments = {
  preparation_id: reviewedPreparation.preparation_id,
  payload_hash: reviewedPreparation.payload_hash
};
```

Эти аргументы передаются инструменту `execute_manual_sale` только после подтверждения человека. Сервер связывает подготовку с бизнесом, видом и конкретным креденшлом, операцией и содержимым. Текущий срок подготовки — 15 минут; ориентируйтесь на возвращённый `expires_at`. После истечения срока или переключения бизнеса подготовьте сценарий заново и повторите человеческую проверку. Одинаковый владелец у двух подключений не делает их подготовки взаимозаменяемыми.

Полное тело результата исполнения для учебного заказа:

```json
{
  "orderSeqNum": 45,
  "replayed": false
}
```

Прочитайте созданный заказ через `GET /api/v1/orders/45` с правом `pull:orders`; этот номер принадлежит бизнесу. Для отправки счёта нужен отдельный подтверждённый шаг из [руководства по платежам](https://telegafirst.com/docs/payments#invoice). Не выполняйте `mark_paid` после создания заказа автоматически.

## REST для уже согласованной продажи

У REST нет придуманных маршрутов подготовки: `prepare_manual_sale` и `prepare_send_invoice` — инструменты MCP. Интеграция, которая использует REST, сама показывает человеку сценарий до отправки денежной команды.

После согласования `POST /api/v1/tools/sales/manual-sales` с правом `orders:write` и `Idempotency-Key: sales-manual-001` принимает одну из двух строгих форм:

```json
{
  "user_seq_num": 17,
  "catalog_seq_num": 23
}
```

Либо продажу по уже существующей регистрации:

```json
{
  "registration_seq_num": 32
}
```

Успех — `201` с `{"orderSeqNum":45,"replayed":false}`. REST-форма не принимает ручную цену, `price`, raw ID, `external_app_id` или actor; собственная цена доступна в описанной MCP-подготовке. Креденшл и бизнес сервер получает из авторизации. Возможны настоящие API key, OAuth grant и M2M credential в пределах их выданных прав; создавать фиктивное приложение для агента не требуется.

Для REST денежная квитанция действует 30 дней и связывает бизнес, операцию и ключ. Точный повтор возвращает сохранённый результат с `replayed:true`; права и принадлежность креденшла проверяются и перед повтором. Не используйте новый ключ, пока выясняете исход операции с потерянным ответом.

## Если операция отклонена

| Где | HTTP / код | Что делать |
| - | - | - |
| Авторизация REST | `401 INVALID_API_KEY` | Повторить вход или использовать действующий разрешённый креденшл. |
| Права | `403 INSUFFICIENT_SCOPE` | Проверить недостающее право в [описании доступа](https://telegafirst.com/docs/scopes-and-permissions). |
| Тариф чтения данных | `403 PLAN_UPGRADE_REQUIRED` | Проверить доступный тариф, пробный период и оплаченное окно. |
| Detail каталога | `404 RESOURCE_NOT_FOUND` | Получить актуальный `seq_id` из своего списка. |
| PATCH каталога без версии или ключа | `428 PRECONDITION_REQUIRED` | Прочитать detail и передать оба обязательных заголовка. |
| Устаревшая версия каталога | `412 PRECONDITION_FAILED` | Прочитать свежие данные, проверить изменение с человеком и взять новый ETag. |
| Строгая форма запроса | `400 VALIDATION_ERROR` | Исправить указанные `validation_errors`, не подставлять внутренние ID. |
| Продажа: нет ключа | `400 IDEMPOTENCY_KEY_REQUIRED` | Дать согласованной команде стабильный ключ. |
| Продажа: цель не найдена | `404 NOT_FOUND` | Сверить номера покупателя, позиции, регистрации или заказа в текущем бизнесе. |
| Продажа: бизнес-условия неверны | `422 INVALID_REQUEST` | Проверить получателя, доступность позиции и платёжные условия; не менять их молча. |
| Продажа: тот же ключ, другое содержимое | `422 IDEMPOTENCY_KEY_MISMATCH` | Для нового согласованного действия создать новый ключ. |
| Продажа: квитанция ещё исполняется | `409 IDEMPOTENCY_KEY_IN_FLIGHT` | Сохранить ключ, дождаться результата и повторить ту же команду. |
| Лимит ресурса | `413 PAYLOAD_TOO_LARGE` или `429 RATE_LIMIT_EXCEEDED` | Уменьшить запрос либо ждать `Retry-After`. |

Ошибки REST имеют `application/problem+json`, `code`, `status`, `instance` и `traceId`. Полный пример PATCH без If-Match:

```json
{
  "type": "https://telegafirst.ru/docs/errors#PRECONDITION_REQUIRED",
  "title": "Precondition Required",
  "status": 428,
  "instance": "/api/v1/catalog/7",
  "code": "PRECONDITION_REQUIRED",
  "traceId": "0123456789abcdef0123456789abcdef",
  "detail": "If-Match header is required for PATCH",
  "hint": {
    "reason": "This write is guarded by optimistic concurrency and needs the version it is based on.",
    "recovery": "Read the object first and send the write with its ETag in the If-Match header."
  }
}
```

Передайте traceId поддержке при непонятном отказе. MCP-бизнес-ошибка приходит с `isError:true`; читайте её содержание, а не ожидайте REST-статус внутри JSON-RPC. При `PREPARED_SCENARIO_TENANT_CHANGED` остановите execute и проверьте бизнес. Недоступная или несовпавшая подготовка может дать безопасную внутреннюю ошибку с traceId; сервер не обещает раскрывать внутреннюю причину. Сначала проверьте исход уже начатого действия, затем подготовку и подключение. Полный справочник: [ошибки](https://telegafirst.com/docs/errors).
