К содержанию
TelegaFirst

Для AI-агентов: markdown этой страницы — /docs/catalog-and-sales.mdиндекс документации — /llms.txt

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

Обновлено

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

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

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

Завершите подключение бизнеса, авторизацию и проверьте активный бизнес. В 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 откройте тарифы, не пытайтесь заменить отказ другим ключом.

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

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

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

{
  "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.

{
  "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 не обещает работающую онлайн-оплату: проверьте платёжную настройку.

Для 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. Подробнее: идентификаторы и страницы списков.

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

Перед записью прочитайте 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-запрос для согласованного изменения названия:

{
  "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, тело:

{
  "title": "Changed"
}

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

{
  "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 может сопровождать точный подтверждённый повтор; это не разрешение менять тело под тем же ключом. Версии и повторы объясняют диагностику конфликта.

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

Сначала найдите покупателя и позицию в текущем бизнесе. Для самостоятельной продажи нужны 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.

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

{
  "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 — её реальное тело, а не созданный клиентом объект:

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

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

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

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

Прочитайте созданный заказ через GET /api/v1/orders/45 с правом pull:orders; этот номер принадлежит бизнесу. Для отправки счёта нужен отдельный подтверждённый шаг из руководства по платежам. Не выполняйте 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 принимает одну из двух строгих форм:

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

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

{
  "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 / кодЧто делать
Авторизация REST401 INVALID_API_KEYПовторить вход или использовать действующий разрешённый креденшл.
Права403 INSUFFICIENT_SCOPEПроверить недостающее право в описании доступа.
Тариф чтения данных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:

{
  "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; сервер не обещает раскрывать внутреннюю причину. Сначала проверьте исход уже начатого действия, затем подготовку и подключение. Полный справочник: ошибки.