AGENTS.md — «Вручай» для ИИ-агента
Для агента, которому человек поручил работу с «Вручай»: как устроена предметная область, в каком порядке действовать и обо что спотыкаются. Каждый шаг дан двумя путями — инструментом MCP и запросом REST.
- Подключиться самому: connect.md. Все клиенты и протокол: mcp.md.
- Вёрстка листа: layout-guide.md.
- Способы выдачи: integrations.md. Сценарии целиком: recipes.md.
- Карта всех страниц:
https://vruchay.ru/llms.txt; всё одним файлом:https://vruchay.ru/llms-full.txt.
Что делает сервис
«Вручай» выпускает именные документы: сертификаты, грамоты, дипломы, благодарности. На входе — макет листа и люди (таблицей, по одному через API или через форму), на выходе — по PDF (или JPEG) на человека: с ФИО в нужном падеже, номером, кодом и QR, ведущим на страницу проверки подлинности. Выданное попадает в реестр, его можно разослать письмами, перевыпустить или отозвать.
Работа идёт от имени организации. Токен принадлежит ей, а не человеку, и видит ровно её данные.
Прежде чем писать свой код
Выдача уже решена готовыми способами — сначала подбери подходящий. Форма и API сами принимают заявку, выпускают документ, списывают квоту, держат «один документ в руки» и шлют письмо. Поэтому свой сервер, прокси между сайтом и «Вручай», выгрузка списков и хранение данных участников у себя не нужны.
| Задача | Готовое решение | MCP | REST |
|---|---|---|---|
| Участники получают документ на сайте: Тильда, WordPress, Битрикс, любая страница | Форма на сайте — кнопка «Получить документ», код вставки приходит готовым | setup_form, kind: "site" | POST /api/integrations/tilda |
| Закрытая страница с личным кабинетом (Tilda Members и другие) | Та же форма: имя и почту берёт из кабинета сама, ФИО человек может поправить; requireAccount: true — только вошедшим; без писем — authMode: "none", sendEmail: false | setup_form, kind: "site" | то же |
| Своего сайта нет | Форма по ссылке на vruchay.ru/f/… или на своём домене | setup_form, kind: "link" | то же |
| Документ по событию в CRM, LMS или боте | Выдача одному | issue_document | POST /api/documents/{id}/issue |
| Список известен заранее | Таблица → выпуск → архив | import_recipients → start_generation | POST …/recipients/import → POST …/generate |
| Разослать выпущенное | Письма по шаблону документа | set_mail_template → send_mail | POST /api/mailing/send |
| Свёрстать лист, поставить ФИО и другие поля | Всё оформление делается инструментами, кабинет не нужен | apply_preset, edit_layout, preview_sheet | PATCH …/sheets/{sheetId}/elements |
Как настроить каждый путь — integrations.md. Если ни один не подходит, сначала скажи человеку, чего не хватает, и только потом пиши своё.
Как подключиться
- MCP — удобнее для агента. Адрес
https://vruchay.ru/mcp, вход через OAuth: человек один раз входит в кабинет и разрешает доступ, токен не нужен. Как подключить себя самому — по шагам в connect.md. Если инструментыvruchayу тебя уже есть — работай через них. Начни сget_account, инструкции —get_guide(start,layout,integrations,recipes). Готовые задания — подсказки MCP (prompts):design_sheet,issue_from_table,site_formи другие. - REST —
https://vruchay.ru/api, заголовокAuthorization: Bearer vru_…(токен выдаёт владелец в кабинете). Проверка связи —GET /api/auth/me.
Права одинаковые: инструменты MCP вызывают тот же REST изнутри сервера. Чего нет отдельным инструментом, делается через api_request с путём REST. Права токена — «Выпускать документы» (member) или «Полный доступ» (admin); формы, удаление и отзыв требуют второго.
Файлы инструменты MCP не передают: они ходят JSON-ом. Бланк, логотип или таблица у тебя на диске и есть оболочка (Claude Code, Codex, Cursor) — возьми get_upload_link и выполни curl -F "file=@путь" "<ссылка>" из ответа: ссылка одноразовая, на 15 минут, токен в команду не попадает. Base64 в аргументах не передавай. Без оболочки — upload_image по публичной ссылке или кабинет.
Сущности
| Сущность | Что это | Ключевые поля |
|---|---|---|
| Организация | Владелец всего остального: проба, план или оплаченный тариф старого образца | plan |
| План | Сколько документов, до какого числа, какие возможности | limit, left, expired, features |
| Проект | Мероприятие или сайт: свои документы, реестр, аналитика, письма. Отбор — projectId (UUID или none). Прежнее имя — папка (folderId) | id, name |
| Документ | Заготовка награждения: размер листа, листы, таблица получателей, мероприятие. В кабинете — «материал». Шаблон — документ с isTemplate: true | id, title, projectId, sheets |
Лист (sheet) | Одна страница документа: фон и блоки. У листа есть version — растёт с каждой правкой | id, version, backgroundFileId |
Блок (element) | Текст, картинка, QR, ссылка или фигура на листе. Координаты в мм от левого верхнего угла, кегль в pt | id, type, x, y, w, h, props |
| Поле | То, что подставляется в текст: %name, колонки таблицы, постоянные поля документа, служебные (date, code, name_dat…) | имя латиницей |
| Колонка / строка | Таблица получателей. name и email есть всегда. В выпуск идут отмеченные строки | data, checked, source |
Задание (job) | Один запуск выпуска, асинхронный | status, total, done, failed |
Файл (file) | Выпущенный документ в реестре | fileId, code, state |
| Форма | Выдача посторонним: на сайте организации (site) или по ссылке (link) | kind, token, connect |
| Письмо | Документ, отправленный получателю | status |
Перед началом
Узнай, кто ты и сколько можно выпустить: get_account — или GET /api/auth/me и GET /api/org/usage.
left— сколько документов ещё можно выпустить (null— без предела);expired: true— срок плана вышел: выпуск, выдача, формы и рассылка не работают при любомleft;features— что входит в план:mailing(письма),tilda(формы на сайте и по ссылке),api(токены),awards(правила наград);null— всё.
Ноль в left или expired: true — скажи человеку сразу, до вёрстки: сам собой остаток не вырастет.
Сценарий 1. Создать и свёрстать документ
| Шаг | MCP | REST |
|---|---|---|
| Посмотреть шрифты, форматы, раскладки | get_layout_catalog | GET /api/layout/catalog |
| Завести документ со свёрстанным листом | create_document с format, orientation, preset: {kind, style} | POST /api/documents |
| …или взять существующий | list_documents, get_document | GET /api/documents, GET /api/documents/{id} |
| Фон или логотип по ссылке | upload_image (target: background или element) | POST …/sheets/{sheetId}/background/from-url, POST /api/documents/{id}/assets/from-url |
| Фон или логотип файлом с диска (агент с оболочкой) | get_upload_link → curl -F "file=@…" по ссылке из ответа | POST /api/uploads → `POST /api/v1/uploads/{link}` |
| Поправить блоки | edit_layout (add, update, remove) или apply_preset | PATCH …/sheets/{sheetId}/elements, POST …/sheets/{sheetId}/preset |
| Мероприятие, подписант, дата | update_document (eventName, constants, issueDate) | PATCH /api/documents/{id} |
| Проверить | check_sheet | POST …/sheets/{sheetId}/check |
| Посмотреть глазами | preview_sheet | POST …/sheets/{sheetId}/preview (JPEG) |
Порядок работы — цикл: правка → check_sheet → preview_sheet → правка, пока замечаний нет и картинка выглядит как задумано. Замечания: блок за краем (off_page), блоки наложились (overlap), текст не влез на самой длинной строке таблицы (text_overflow), неизвестное поле или шрифт. Превью квоту не тратит.
- Виды раскладок (
kind):award(за место),winner,laureate,participant,attendee,course,graduate,school,thanks,letter. Стили:classic,solemn,academic,strict,modern,script,handwritten. - Бланк с напечатанным заголовком —
titlePrinted: true, а сам бланк — фоном. - Готовая раскладка ставит подпись из постоянных полей
signer_roleиsigner_nameс образцами «Руководитель» и «И. О. Фамилия» — замени их черезupdate_document(constants— список целиком). formatбезorientationдаёт альбомный лист.- Поля документа и где они стоят:
get_document→fields(REST —GET /api/documents/{id}/fields).
Как верстать вручную — координаты, кегли, шрифты, примеры блоков — в layout-guide.md; все поля блоков — в справочнике макета.
Сценарий 2. Таблица → выпуск → ссылка
| Шаг | MCP | REST |
|---|---|---|
| Загрузить таблицу | import_recipients (columns, titles, rows, mode) | POST /api/documents/{id}/recipients/import; файлом — сначала …/recipients/parse |
…или файлом .xlsx/.csv с диска | get_upload_link (target: recipients, mode) → curl -F "file=@…" | POST /api/uploads → POST /api/v1/uploads/{link} |
| Проверить данные | validate_recipients — только смотрит | POST …/validation |
| Принять однозначные исправления | edit_recipients с applySuggestions: true | POST …/validation/fix с fixes из suggestion замечаний; снять строки — …/validation/exclude |
| Отметить, кого выпускать | edit_recipients (checkAll, update[].checked) | POST …/recipients/checked |
| Посмотреть на самой длинной строке | preview_sheet с rowId | POST …/preview с {"rowId": …} |
| Показать человеку план и получить «да» | — | — |
| Выпустить | start_generation (format: pdf или jpg) | POST /api/documents/{id}/generate, тело минимум {} |
| Дождаться | get_job с waitSeconds: 60, повторять | GET /api/jobs/{jobId} раз в 2–3 секунды |
| Остановить ошибочный выпуск | cancel_job | POST /api/jobs/{jobId}/cancel |
| Доделать прерванный | start_generation с resume: true | POST /api/jobs/{jobId}/resume |
| Отдать человеку | get_download_link (format: zip или pdf) | POST /api/jobs/{jobId}/archive-link |
columns— имена латиницей:name(ФИО),email, свои (place,course,hours…). Колонки создаются сами. До 30 колонок и 10 000 строк за раз.mode: "replace"стирает прежние строки (кроме выданных формой),appendдописывает.- Через MCP тело ограничено 2 МБ: большую таблицу шли частями с
append. - Задание кончается статусом
done,failedилиcanceled. Причины сбоевget_jobпоказывает сам; в REST —GET /api/jobs/{jobId}/failures. Забирать их нужно до продолжения: оно стирает прежние причины. Прошлые выпуски документа и ихjobId—get_document→jobs.stuck: trueприqueued— задание висит больше 15 минут, его пора продолжить. - Ссылка от
get_download_linkоткрывается в браузере без входа и живёт час.name— шаблон имён файлов в архиве, до 120 знаков:"%name","%number %name". Архив по адресу задания (GET /api/jobs/{jobId}/archive) отдаётся только с заголовкомAuthorizationи потоком — для программ, а не для человека.
Сценарий 3. Один документ одному человеку
Когда документы выдаются не пачкой, а по одному — человек закончил курс, сдал тест, записался, — таблица не нужна:
- Документ с макетом уже есть (сценарий 1).
- MCP:
issue_documentсdocumentId,data: {"name": "Иванова Анна Сергеевна"},email,send: trueиidempotencyKey. REST:POST /api/documents/{id}/issueс телом{"data": {"name": "Иванова Анна Сергеевна"}, "email": "…", "send": true}и заголовкомIdempotency-Key. - Ключ — номер заказа или заявки, 8–100 знаков: латиница, цифры и
_ - : .. В MCP он обязателен. В REST обязателен по смыслу, хоть и не по схеме: без него повтор после обрыва связи выпустит и спишет второй документ. - Ответ
201(status: "done") — документ готов: вfileкод проверки, номер, ссылка на страницу проверки и ссылка получателя на PDF (recipientUrl, 30 дней).202(processing) — опрашивайstatusUrlраз в 1–2 секунды.200сstatus: "failed"— не вышел, причина вerror; повтор с тем же ключом заведёт новое задание.
Поля data — колонки и поля листа этого документа (список — get_document → fields.issue, в REST — GET /api/documents/{id}/fields, раздел issue); лишнее имя — 400 со списком допустимых. Квота та же, что у выпуска; отказ по ней — сразу, 400. Предел — 120 документов в минуту на организацию. Письмо (send: true) требует mailing в плане и адреса: без возможности — 403, без адреса — 400, и документ тогда не выпускается вовсе. Если письмо не ушло уже после выпуска, документ остаётся выпущенным, а причина — в emailError.
Сценарий 4. Форма по ссылке или на сайте
Люди получают документ сами — заполняют форму. Создавать формы может только токен с «Полным доступом», а в плане должна быть возможность tilda.
| Шаг | MCP | REST |
|---|---|---|
| Форма по ссылке (своего сайта не нужно) | setup_form с kind: "link", name, documentIds: [id], formFields: ["name"] | POST /api/integrations/tilda |
| Форма на сайте организации | setup_form с kind: "site", name, documentIds, allowedDomains: ["example.ru"] | POST /api/integrations/tilda |
| Изменить, выключить | setup_form с integrationId (active: false) | PATCH /api/integrations/tilda/{id} |
| Найти готовые | list_forms | GET /api/integrations/tilda |
| Заявки | api_request GET /api/integrations/tilda/requests | то же |
name — название формы для кабинета, при создании обязательно.
В ответе — connect: connect.formUrl — ссылка https://vruchay.ru/f/{token} для формы по ссылке; connect.embedHtml[documentId] — готовый HTML для вставки на сайт. Отдай человеку ровно это. Форма по ссылке выдаёт один документ, форма на сайте — до 50; поддомены разрешённого домена тоже считаются своими. Форма на закрытой странице личного кабинета подставляет имя и почту вошедшего сама — см. «Закрытая страница и личный кабинет» в integrations.md.
Каждая заявка выпускает документ и списывает квоту. Кончилась квота или вышел срок плана — форма перестаёт выдавать, и человек видит просьбу написать организатору. authMode — подтверждать ли почту кодом: по умолчанию email_code у формы на сайте и none у формы по ссылке; singleFilePerEmail: true (по умолчанию) — один документ в руки. Подробнее — integrations.md.
Сценарий 5. Письма
| Шаг | MCP | REST |
|---|---|---|
| Шаблон письма | set_mail_template (subject, bodyHtml, attachGeneratedFile) | POST /api/mail/templates/{documentId} |
| Пробное письмо себе | api_request POST /api/mail/templates/{documentId}/test-send, тело — как у шаблона | то же |
| Кому уйдёт и кому нет — показать человеку, получить «да» | get_mail → audience | POST /api/mailing/audience |
| Разослать | send_mail с documentId | POST /api/mailing/send с {"documentIds": [id], "kind": "transactional", "source": "table"} |
| Судьба писем | get_mail → log (problemsOnly: true — только недоставленные) | GET /api/mailing/log?documentId=… |
| Повторить недоставленные | send_mail с documentId и onlyFailed: true | POST /api/mailing/resend |
- В теме и тексте подстановки —
%nameи другие поля строки. - Письма уходят отмеченным строкам, у которых документ уже выпущен; кому не поставлено — в
skippedс причиной. - Пробное письмо по токену приходит тому, кто выдал токен (или разрешил подключение OAuth), а не на адрес из запроса.
- С общего адреса сервиса — не больше 300 писем за раз и 500 за сутки на организацию; со своего подтверждённого домена предела нет.
- Повторный
send_mailбезопасен: кто уже получил письмо, второй раз его не получит — так можно дослать письма строкам, выпущенным позже.
Сценарий 6. Исправить выданное
Опечатка в ФИО, письмо не дошло, документ выдан по ошибке:
| Шаг | MCP | REST |
|---|---|---|
| Найти выданное | search_registry (search — имя, почта или код) | GET /api/registry |
| Карточка и файл одного документа | search_registry с fileId | GET /api/registry/files/{fileId}, ссылка — GET /api/documents/files/{fileId}/url |
| Поправить строку таблицы | edit_recipients (update) | PATCH …/recipients/rows/{rowId} |
| Перевыпустить по исправленной строке | reissue_documents с fileIds | POST /api/registry/reissue |
| Переслать письмо | send_mail с fileIds | POST /api/registry/resend |
| Отозвать | revoke_documents: без expectedCount — предпросмотр, затем с ним | POST /api/registry/revoke/preview, затем POST /api/registry/revoke |
- Перевыпуск тратит квоту; новый документ получает новый номер, старый помечается заменённым, и его страница проверки ведёт на новый.
- Отзыв виден сразу на странице проверки по QR. Вернуть проверку можно (
restore: true), но человек с бумагой мог уже увидеть «отозван». - Перевыпуск и отзыв — только с «Полным доступом».
Что важно знать заранее
- Асинхронный только выпуск. Остальное отвечает результатом сразу. Вебхуков о готовности нет — состояние узнают опросом (
get_jobждёт сам до 60 секунд). - Тело `POST /generate` обязательно. Пустое тело —
400; посылай{}. - Выпускаются только отмеченные строки. Импорт отмечает строки сам; снятые проверкой и строки, выданные формой или по API, в пакет не попадают.
- Один выпуск на документ. Пока по документу идёт задание, второе не начнётся; у организации — не больше трёх заданий в очереди.
- Чужое отвечает `404`, а не `403`. Так не подтверждается существование чужих объектов.
- Лист правят по очереди. Редактор кабинета записывает лист с версией и твою правку молча не затрёт: человек с открытым листом увидит «Лист изменили в другом месте» и сам выберет, взять твою версию или записать свою поверх. Чтобы обойтись без выбора, попроси не трогать лист, пока ты верстаешь. Сам передавай
baseVersion— версию листа изget_document: если лист успел измениться, ответ409с текущейversion; перечитай лист и повтори. - Превью — образец. Код и номер на нём ненастоящие; данные — первой строки таблицы или образца,
rowId— конкретной строки. - Только человек: сотрудники и роли, пароль и вход, токены и разрешение OAuth, оплата картой и счета, удаление учётной записи. Такие запросы отвечают
403,api_requestв эти разделы не пускает. - Подпись PDF ставится, только когда у организации отметка
paidи на сервере настроен сертификат подписи. JPEG подписи не носит. - Выданное остаётся действительным и после истечения плана.
Что подтверждать у человека
Перед этими действиями покажи, что и сколько будет сделано, и дождись согласия:
- выпуск (
start_generation,POST …/generate) — тратит квоту: сколько документов, по какому документу, сколько останется; - выдача одному (
issue_document) — тратит квоту; в потоке из CRM согласие даётся один раз на всю интеграцию; - рассылка (
send_mail) — необратима: сколько писем, с какого адреса, текст; - форма (
setup_form) — открывает выдачу посторонним и тратит квоту на каждую заявку; - отзыв (
revoke_documents) — сначала предпросмотр, число документов показать человеку; перевыпуск (reissue_documents) — тратит квоту; - удаление — листа (
delete_sheet), документа в корзину (delete_document), формы; удалить насовсем может только владелец в кабинете; import_recipientsиget_upload_linkдля таблицы сmode: "replace"— стирают прежнюю таблицу.
Если что-то пошло не так
| Ответ | Что значит | Что делать |
|---|---|---|
400 с errors[] (в MCP — «Поля: …») | Не прошла проверка полей | Исправить по errors[].field и errors[].message |
400 про остаток или план | Не хватает документов или вышел срок | Сказать человеку; состояние — get_account |
401 | Токена нет, он отозван или заменён | Проверить заголовок; для OAuth — переподключиться |
403 | Не та роль, действие только для человека, возможности нет в плане или план истёк | Не повторять, объяснить человеку |
404 | Объекта нет или он чужой | Проверить идентификатор |
409 у листа | Лист изменился после чтения | Перечитать (get_document), повторить с новой baseVersion |
409 у выдачи | Idempotency-Key уже использован для другого документа | Ключ — на каждого получателя, а не на программу |
422 у превью | Лист не отрисовался | check_sheet, проверить fileId картинок |
429 | Слишком часто | Подождать столько секунд, сколько названо в тексте |
job.status = failed | Задание не доделано | Причины (get_job или …/failures), потом start_generation с resume: true (REST — POST /api/jobs/{jobId}/resume) |
503 при запуске выпуска | Очередь недоступна; задание заведено и помечено failed | Продолжить его (start_generation с resume: true), а не запускать второе |
202 у выдачи | Документ ещё печатается | Опрашивать statusUrl, а не повторять выдачу |
Разбор форматов — в Кодах ошибок.
Границы приличия
- Токен отправляется только на
vruchay.ruи никуда больше: ни в сторонние сервисы, ни в публичные чаты, ни в код, который уйдёт в репозиторий. - Таблица участников и выпущенные документы — персональные данные. Не выкладывай их в сторонние сервисы и не показывай больше, чем нужно для задачи.
- Ссылка на архив открывается без входа: отдавай её только тому, кто поручил работу.
- Сомневаешься, тратит ли действие квоту или можно ли его отменить, — спроси.