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

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

Сайт, витрина и формы

Обновлено

Опубликуйте сайт своего бизнеса, настройте форму обращения и переход к покупке. В TelegaFirst сайт помогает начать разговор с AI-фронт-офисом в Telegram, а команда продолжает работу в Telegram-супергруппах. Заявка с сайта сохраняется сразу: переход посетителя в мессенджер помогает установить его личность, но не является условием сохранения обращения.

Подготовка и адреса

Сначала пройдите вход и настройку бизнеса, проверьте активный slug и доступный tools/list. Для сайта нужны полномочия владельца и scopes конкретной операции. MCP использует предусмотренный credential владельца или интеграции; перечисленные REST-маршруты принимают X-Api-Key либо Authorization: Bearer tgf_*, а не surface-session JWT. Полномочия проверяются при каждом вызове.

ДействиеScope
Статус своего доменаdomain:read
Запрос подключения, проверка DNS, отключениеdomain:write
Загрузка/публикация сайта, формы, выдача checkout keysite:write
Manifest, чтение файла через MCP, список имён секретовsite:read
Настройки витриныstorefront:read / storefront:write
Анонимный checkout relayorders
Чтение ответов форм через MCPpull:form_submissions
Отправка и отметка обработки через Hub/MCPpush:form_submissions

Client.slug — единственный читаемый tenant-адрес публичной поверхности. Товар, форма, событие и публичная заявка используют opaque UniversalLink.code; публичную ссылку нельзя построить из их номера. Для управления формой используется отдельный числовой seqNum своего tenant, для управления ответом — его собственный seq_num. Внутренний PK не передаётся ни в одну из этих поверхностей. Идентификаторы.

Две зоны .ru и .com обязательны на запуске. Это отдельные юридические и платёжные зоны; русский — язык по умолчанию, а язык оператора определяется настройкой бизнеса. Домены и примеры ниже — контрактные иллюстрации: acme.ru, UUID и ссылки *.test не подтверждают зарегистрированный домен или опубликованный сайт. Хосты сервисов берите из актуального ответа и настройки окружения.

Подключение своего домена

  1. Прочитайте domain_status или GET /api/v1/tools/domain. Параметр zone=ru / zone=com выбирает свою зону; без него возвращается массив текущих подключений.

  2. Вызовите domain_add_request или POST /api/v1/tools/domain с непустым HTTP Idempotency-Key. Передавайте bare hostname без схемы, пути и порта. Пример тела:

{
  "domain": "acme.ru",
  "zone": "ru"
}

REST 201 не означает, что DNS уже проверен. Например, при anti-phishing admission ответ содержит ownerStatus:"screening", created:false, domain.status:"PENDING_DNS" и failedReason:"domain_screening_pending". Следуйте instructions: пока проверка безопасности не завершена, не публикуйте ни записи направления трафика, ни verification TXT. Повторный domain_add_request сообщает текущий admission status; не заменяйте его угадыванием DNS token.

  1. После допуска опубликуйте точные DNS-записи из возвращённой инструкции, затем вызовите domain_verify / POST /api/v1/tools/domain/verify с {"zone":"ru"} и HTTP idempotency key. Проверка принимает только свою зону, не чужой hostname. Пример ещё незавершённого результата REST 201:

{
  "domain": {
    "domain": "acme.ru",
    "zone": "ru",
    "status": "PENDING_DNS",
    "storefrontLabel": "shop",
    "dnsVerifiedAt": null,
    "certIssuedAt": null,
    "failedReason": "domain_screening_pending"
  },
  "verified": false,
  "pendingReason": "screening_pending"
}

verified:false — ожидаемый результат незавершённой проверки. После screening DNS может сообщать nxdomain, mismatch, ambiguous, timeout или resolver_error; сверяйте возвращённые pendingReason и записи. Распространение DNS зависит от вашего TTL. Успешная проверка переводит свой домен в VERIFIED; сертификат выпускается при первом HTTPS-обращении, а не доказывается одним ответом 201.

  1. Чтобы отключить домен через MCP, сначала вызовите domain_prepare_unlink. Покажите человеку замороженные домен/зону из payload и срок действия expires_at возвращённой квитанции. Только после финального подтверждения человеком вызовите domain_execute_unlink: его JSON-тело содержит ровно preparation_id (UUID) и payload_hash (64 строчных hex-символа), скопированные без изменений из одной и той же квитанции подготовки. Подготовка не разрешает автоматическое отключение. Исполнение прекращает routing и обслуживание сертификата; новое подключение снова требует DNS-проверки. Существующий REST DELETE /api/v1/tools/domain/{zone} — отдельная прямая операция владельца. У неё нет выдуманных REST prepare/execute twins; человек должен заранее понимать эффект.

Загрузка полного сайта и публикация

Подключённый допустимый хост, полный список файлов и подходящий тариф — предпосылки публикации. site_request_upload / POST /api/v1/tools/site/request-upload принимает host и массив файлов. Укажите путь относительно bundle, фактический размер UTF-8/бинарных байтов и MIME. references, когда нужны, — пути зависимостей внутри того же bundle; отсутствующая или недопустимая зависимость отклоняется. Пример минимального запроса из контрактной проверки:

{
  "host": "acme.ru",
  "files": [
    {
      "path": "index.html",
      "sizeBytes": 15,
      "contentType": "text/html"
    }
  ]
}

REST 201 возвращает uploadId UUID, host и uploads с path, staging key, presigned putUrl, expiresAt. Ссылки действуют 300 секунд. Не сочиняйте UUID или staging key: возьмите их из своего ответа. Загрузите каждый файл HTTP PUT непосредственно по его putUrl, сохранив заявленные content type и byte length. Неправильный размер не превращает ticket в разрешение загрузить дополнительные байты.

Затем site_publish / POST /api/v1/tools/site/publish, снова с HTTP idempotency key:

{
  "host": "acme.ru",
  "uploadId": "11111111-1111-4111-8111-111111111111"
}

UUID здесь иллюстративный: в реальном запросе нужен выданный ticket того же tenant и host. Результат REST 201 из проверки:

{
  "host": "acme.ru",
  "uploaded": [
    "index.html"
  ],
  "deleted": [
    "old.html"
  ],
  "unchanged": [
    "assets/app.js"
  ],
  "manifestDigest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "attributionSnippet": "<script src=\"https://gateway.example.test/pixel/telega-pixel.js\" async></script>"
}

uploaded — изменённые пути, deleted — отсутствующие в новой версии, unchanged — совпавшие. В каждой версии объявляйте полный сайт, включая неизменённые файлы; одного изменённого HTML недостаточно. Delta Sync вычисляет изменения по фактическим staged bytes, а index.html записывается последним. На платформенном apex публикуемый контент ограничен префиксом p/; системные пути не занимают файлом клиента. Отказ 422 SITE_PATH_OUTSIDE_PUBLISHABLE_PREFIX указывает исправить путь.

После успеха staged файлы потреблены. Для нового deploy получите новые upload URLs; старый ticket нельзя использовать как постоянный источник контента. Веб-bundle не принимает video/*: используйте разрешённый embed. Количество файлов и суммарный размер ограничиваются тарифом и per-tool budget; сервер проверяет заявленные размеры до выдачи URLs и реальные размеры перед публикацией.

Если страницы ссылаются на вашего бота, вставьте точный attributionSnippet из ответа в шаблон HTML. В приведённом ответе URL gateway.example.test — адрес стенда; его не следует копировать в живой сайт. Переход к боту может работать и без скрипта, но источник страницы/кампании не будет корректно атрибутирован.

Для правки существующего bundle через MCP сначала site_get_manifest с {"host":"acme.ru"}, затем site_fetch_file с {"host":"acme.ru","path":"index.html"}. Manifest несёт checksum/size и manifestDigest; fetch возвращает UTF-8 текст файла, а не секреты или конфигурацию access policy. Новая публикация всё равно содержит полный bundle. Эти чтения не вводят новых REST routes.

site_pages_set_visibility управляет существующими страницами по их tenant-номерам; корневые и системные страницы защищены, показ не отменяет тариф или модерацию. site_page_preview возвращает короткоживущий opaque URL на отдельном origin и не публикует страницу. Для удаления сайта используйте site_prepare_delete, покажите человеку frozen manifest digest, после подтверждения — site_execute_delete. Подготовка сама не удаляет байты. Точные схемы этих MCP операций запрашивайте через актуальное discovery.

Секреты сайта и подтверждение владельца

Секрет сервера не должен находиться в HTML, JavaScript bundle или literal destinations. Для server-side отправки используйте сохранённое имя, например API_TOKEN. Site secret хранится с шифрованием AES-256-GCM и привязкой к tenant/полю. Список возвращает только name, createdAt, lastRotatedAt; прочитать значение назад через API нельзя.

secret_put с site:write либо POST /api/v1/tools/site/secrets начинает отдельное owner approval. Пример с фиктивным значением, которое не является credential:

{
  "name": "API_TOKEN",
  "value": "secret-fixture-value"
}

Первый ответ означает только ожидание подтверждения, а не сохранение:

{
  "status": "pending_approval",
  "actionId": "11111111-1111-4111-8111-111111111111"
}

Владелец подтверждает действие в Telegram. Затем повторите соответствующий вызов с теми же именем/значением и возвращённым UUID в поле action_id. Для REST leg2 используйте новый HTTP idempotency key: повтор leg1 с прежним ключом воспроизведёт pending answer или отклонит изменённое тело. Подтверждение проверяет реального владельца, tenant и действие; одной строки scope без необходимого provenance недостаточно. Не создавайте фиктивный ExternalApp для OAuth/M2M credential.

secret_rotate / PUT /api/v1/tools/site/secrets/{name} меняет значение с такой же двухшаговой проверкой; REST body содержит value, затем action_id. secret_delete / DELETE /api/v1/tools/site/secrets/{name} также требует подтверждения; во второй REST-вызов action_id передаётся query-параметром. Новый секрет под занятым live именем —409 CONFLICT: используйте rotate. Для чтения имён — secret_list с {} / GET /api/v1/tools/site/secrets и site:read. Маршрута GET /secrets/{name} нет.

Обычная публикация и декларация формы не требуют этой secret approval. Их право — проверенный site:write; это не разрешение изменить чужой домен или обойти другие ограничения.

Декларация формы до создания HTML

Сначала вызовите site_form_declare / POST /api/v1/tools/site/forms. Форма получает адрес до первой публикации, поэтому HTML может сразу использовать возвращённый code. Пример создания с разрешённым origin:

{
  "zone": "ru",
  "title": "Обращение",
  "allowedOrigins": [
    "https://acme.ru"
  ],
  "captchaRequired": false,
  "funnelMode": "none",
  "enabled": true
}

REST отвечает 201 с seqNum, clientSlug, code, zone, title, enabled, created, updatedAt. seqNum нужен управлению за tenant guard, code — публичной ссылке. Для обновления передайте свой числовой seqNum; публичные slug/code сохраняются. Без seqNum или с null создаётся новая форма с новым адресом. Если timeout не позволил узнать созданный номер, не повторяйте MCP create вслепую; это не idempotent create. Для REST receipt повторите исходный запрос с тем же HTTP key.

Декларация задаёт envelope: destinations, allowed origins, captcha и funnel. По умолчанию captcha включена; её отключение в примере задано явно. Рисуйте inputs в HTML самостоятельно. Необязательный submissionSchema валидирует совпадающие с HTML имена и значения; он не строит UI. До 20origins и 20destinations; до 50variants и 100 элементов submissionSchema. funnelMode — none, deterministic, ai_agent. Для лестницы отправляйте полный список variants: пропуск при redeclare удаляет лестницу. В deterministic используется when, в ai_agent — hint; подготовленные шаги адресуются stepSeqNum, а не PK. Для checkout outcome нужен свой catalogSeqNum.

Destinations имеют существующие формы: Telegram topic, HTTPS webhook или external API с secretName вместо значения credential. Разрешайте точные origin своего сайта; пустой allowlist не допускает отправку. Остановите приём redeclare с enabled:false, сохранив необходимую конфигурацию. Отдельного удаления формы нет: исторические ответы должны сохранять смысл.

Отправка и немедленная анонимная заявка

Посетитель отправляет JSON в публичный адрес https://fn.telegafirst.ru/f/{clientSlug}/{code}; для COM-зоны — fn.telegafirst.com. Оба значения возьмите из своей декларации. Пример тела:

{
  "payload": {
    "question": "Записаться"
  }
}

Browser передаёт свой Origin; при включённой captcha нужен действительный captcha_token. Это публичная поверхность P1: посетитель не подставляет tenant, form number, submission number или внутренний ID. После проверки origin, captcha, размера, схемы и media сервер сохраняет новую заявку сразу и выделяет её собственный tenant-номер, даже если посетитель остаётся анонимным.

Публичный HTTP 202 возвращает только deeplink и landing_url с opaque адресацией. Он не возвращает seq_num, submission_code отдельным полем или PK. Перейдите по фактическому результату; не собирайте deeplink самостоятельно. Открытие мессенджера связывает личность с уже существующей заявкой. Бизнес может увидеть анонимную заявку раньше перехода и отличает её от linked.

Этот публичный ingest имеет собственные ошибки:400 malformed body/media,403 отсутствие допуска/неверный origin/captcha,413 превышение размера,422 violations объявленной схемы,429 ограничение частоты. Не приписывайте ему Hub Problem Details envelope или Hub scope: это другой HTTP boundary.

Для серверной отправки от интеграции есть отдельный действующий P3 route: POST /api/v1/form-submissions, scope push:form_submissions, HTTP key обязателен. Пример полного тела и результата 202 из принятой проверки:

{
  "form_seq_num": 12,
  "payload": {
    "email": "ada@example.test"
  }
}
{
  "outcome": "accepted",
  "seq_num": 1042,
  "submission_code": "opaque-form-answer",
  "status": "anonymous"
}

Здесь 12 — номер формы,1042 — собственный номер нового ответа; submission_code — отдельный публичный code. Числа передавайте JSON numbers, положительные int4 до 2147483647. MCP submit_form_submission использует тот же контракт, но каждый вызов создаёт новый ответ: HTTP replay не распространяется на него по умолчанию.

Прочитайте pull_form_submissions с {"form_seq_num":12,"status":"anonymous","limit":50}. Scope pull:form_submissions; действуют права и paid capability. Значение limit по умолчанию 50, диапазон 1..200. Cursor зашифрован, привязан к tenant/resource/эффективным фильтрам и живёт 15 минут: передавайте выданную строку как есть, не расшифровывайте PK, не меняйте фильтры между страницами. Просроченный/чужой cursor —400 INVALID_CURSOR; начните заново.

Для detail используйте get_form_submission с {"seq_num":1042}. Ответ показывает seq_num, отдельный submission_code, form_seq_num, title, created_at и статус; linked ответ дополнительно несёт пользовательский tenant-номер и channel. Сам payload опущен, пока человек явно не попросил прочитать ответы. Тогда используйте единственное значение include:"full_payload"; disclosure записывается в аудит. Оно не требуется для подсчёта заявок или проверки работы формы. mark_form_submission_handled получает номер 1042 и требует push:form_submissions.

Витрина и checkout

get_storefront / GET /api/v1/tools/storefront читает singleton с storefront:read. update_storefront / PATCH меняет разрешённые поля с storefront:write. Частичный patch сохраняет пропущенные поля, null очищает subtitle/title. Текущий slug допускается как no-op, другой —409 SLUG_IMMUTABLE. Пример настроек из проверки:

{
  "enabled": false,
  "slug": "sample",
  "title": "After",
  "subtitle": null
}

Ответ 200:

{
  "updated_at": "2026-09-30T00:00:00.000Z",
  "enabled": false,
  "slug": "sample",
  "title": "After",
  "subtitle": null
}

Публичный checkout key выпускает site_checkout_key с {} / bodyless POST /api/v1/tools/site/checkout-key при site:write. Ключ tgf_pub_* показывается один раз, имеет только orders и допускается только на отмеченном relay. При ротации прежний ключ остаётся допустимым 24 часа; разверните новый на своём сайте. Такой ключ не читает каталог, заказы или настройки. Origin разрешается в форме, не при выдаче ключа.

Для POST /api/v1/storefront/checkout нужны свой public key, допустимый Origin и непустой Idempotency-Key. Форма должна быть включённой, иметь checkout outcome и свой продукт. Body содержит только opaque code:

{
  "code": "aB3dE6gH9jK2mN5p"
}

Пример ответа 201; pay.example — искусственный адрес fixture:

{
  "order_id": "qR7tY2uI8oP4aS6d",
  "payment_urls": {
    "stripe": "https://pay.example/fixture"
  }
}

order_id здесь — публичный opaque code, не PK и не buyer number. Price, product/user ID, redirect URL и external order reference из браузера не принимаются. Сервер сам разрешает форму в tenant и выбирает продукт/условия; origin/form/product admission повторяется перед cached replay. В личном кабинете покупателя /checkout/{seq_num} — P2 с отдельной customer session и проверкой владения: покупатель видит тот же номер заказа, что в диалоге. Публичный relay не заменяет этот guard.

Checkout receipt сохраняется 24 часа отдельно для tenant/credential/relay: тот же key/request воспроизводит 201 и тело, иной запрос с тем же key —409 IDEMPOTENCY_CONFLICT. Для settings PATCH HTTP key необязателен; иной body под переданным ключом —422 IDEMPOTENCY_KEY_MISMATCH. Внутренние AI-агенты не меняют деньги; агент клиента использует денежные сценарии через подготовку, review и финальное подтверждение человека.

Ошибки Hub и дальнейшие действия

Для REST POST с непустым body нужен HTTP key; для bodyless checkout-key он необязателен. Generic HTTP replay хранится 24 часа по tenant/credential/handler/key, сохраняет исходный status; mismatch 422 IDEMPOTENCY_KEY_MISMATCH, processing 409 CONFLICT. Ошибка не является доказательством готовой публикации. У этих site/domain/settings ответов нет cursor pagination и ETag/If-Match; form pull cursor описан отдельно выше. У media ingress собственный контракт: site upload UUID и bundle paths нельзя подставлять в /api/v1/media/uploads.

Для показанного выше запроса подключения POST /api/v1/tools/domain с телом domain:"acme.ru", zone:"ru" аутентифицированный credential в активном бизнесе, у которого нет domain:write, получает следующий отказ Hub P3. Это составленный контрактный пример: основные поля подтверждены принятой boundary fixture, а hint добавлен текущим каноническим фильтром ошибок. Он не является записью запроса к живому хосту и не подтверждает подключение домена.

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

Полное тело ответа:

{
  "type": "https://telegafirst.ru/docs/errors#INSUFFICIENT_SCOPE",
  "title": "Forbidden",
  "status": 403,
  "instance": "/api/v1/tools/domain",
  "code": "INSUFFICIENT_SCOPE",
  "traceId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "detail": "Missing required scope(s): domain:write",
  "hint": {
    "reason": "The credential lacks a scope this operation requires.",
    "recovery": "Compare missing_scopes at GET /api/v1/me with this operation and use a key that includes them."
  }
}

Сравните отсутствующий domain:write с описанием доступа и используйте credential с этим scope для нужного бизнеса. Полномочия владельца, допуск домена и DNS-проверка сохраняют силу. Этот Problem Details относится к управлению доменом через Hub; у анонимной отправки формы P1 остаётся собственный протокол ошибок, описанный выше.

Status / код Hub RESTЧто делать
400 VALIDATION_ERROR, IDEMPOTENCY_KEY_REQUIREDИсправьте strict schema/UUID/zone/тип номера или обязательный HTTP key
401 INVALID_API_KEYПроверьте допустимый credential и отзыв доступа
403 INSUFFICIENT_SCOPE, FORBIDDENПроверьте live права, owner provenance/approval, Origin и допуск формы
404 NOT_FOUNDХост не является своим live подключённым хостом либо secret отсутствует; не перебирайте чужие хосты
409 CONFLICTРазберите занятый хост, неверный bundle/dependency, чужой form number, занятое secret name или текущую публикацию
409 SLUG_IMMUTABLE, IDEMPOTENCY_CONFLICTСохраните выбранный slug; для другого checkout используйте новый key
422 SITE_PATH_OUTSIDE_PUBLISHABLE_PREFIXНа платформенном apex перенесите свои пути под p/
422 IDEMPOTENCY_KEY_MISMATCHНе меняйте body/path/query под уже использованным generic key
413 PAYLOAD_TOO_LARGE, ITEM_COUNT_EXCEEDEDУменьшите запрос согласно реальному бюджету операции
429 RATE_LIMIT_EXCEEDED, RATE_LIMIT_UNAVAILABLEСоблюдайте Retry-After, если выдан; отказ не обходят
500 INTERNAL_ERRORСохраните traceId; не объявляйте deploy или secret write успешным

Успешные Hub REST-ответы несут context headers X-Client-Slug и X-Bot-Username, если доступны, а также три X-RateLimit-* headers. Точные DTO, все операции и ошибки — в доменах, сайтах, витрине, формах и общих ошибках. Результат настройки — доступный по своему разрешённому адресу сайт и форма, чьи ответы сразу видны вашему бизнесу; подтверждайте это реальным состоянием своей публикации, а не примером из документации.