APIНачало работы

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 сами принимают заявку, выпускают документ, списывают квоту, держат «один документ в руки» и шлют письмо. Поэтому свой сервер, прокси между сайтом и «Вручай», выгрузка списков и хранение данных участников у себя не нужны.

ЗадачаГотовое решениеMCPREST
Участники получают документ на сайте: Тильда, WordPress, Битрикс, любая страницаФорма на сайте — кнопка «Получить документ», код вставки приходит готовымsetup_form, kind: "site"POST /api/integrations/tilda
Закрытая страница с личным кабинетом (Tilda Members и другие)Та же форма: имя и почту берёт из кабинета сама, ФИО человек может поправить; requireAccount: true — только вошедшим; без писем — authMode: "none", sendEmail: falsesetup_form, kind: "site"то же
Своего сайта нетФорма по ссылке на vruchay.ru/f/… или на своём доменеsetup_form, kind: "link"то же
Документ по событию в CRM, LMS или ботеВыдача одномуissue_documentPOST /api/documents/{id}/issue
Список известен заранееТаблица → выпуск → архивimport_recipients → start_generationPOST …/recipients/import → POST …/generate
Разослать выпущенноеПисьма по шаблону документаset_mail_template → send_mailPOST /api/mailing/send
Свёрстать лист, поставить ФИО и другие поляВсё оформление делается инструментами, кабинет не нуженapply_preset, edit_layout, preview_sheetPATCH …/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: trueid, title, projectId, sheets
Лист (sheet)Одна страница документа: фон и блоки. У листа есть version — растёт с каждой правкойid, version, backgroundFileId
Блок (element)Текст, картинка, QR, ссылка или фигура на листе. Координаты в мм от левого верхнего угла, кегль в ptid, 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. Создать и свёрстать документ

ШагMCPREST
Посмотреть шрифты, форматы, раскладкиget_layout_catalogGET /api/layout/catalog
Завести документ со свёрстанным листомcreate_document с format, orientation, preset: {kind, style}POST /api/documents
…или взять существующийlist_documents, get_documentGET /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_presetPATCH …/sheets/{sheetId}/elements, POST …/sheets/{sheetId}/preset
Мероприятие, подписант, датаupdate_document (eventName, constants, issueDate)PATCH /api/documents/{id}
Проверитьcheck_sheetPOST …/sheets/{sheetId}/check
Посмотреть глазамиpreview_sheetPOST …/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. Таблица → выпуск → ссылка

ШагMCPREST
Загрузить таблицу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: truePOST …/validation/fix с fixes из suggestion замечаний; снять строки — …/validation/exclude
Отметить, кого выпускатьedit_recipients (checkAll, update[].checked)POST …/recipients/checked
Посмотреть на самой длинной строкеpreview_sheet с rowIdPOST …/preview с {"rowId": …}
Показать человеку план и получить «да»——
Выпуститьstart_generation (format: pdf или jpg)POST /api/documents/{id}/generate, тело минимум {}
Дождатьсяget_job с waitSeconds: 60, повторятьGET /api/jobs/{jobId} раз в 2–3 секунды
Остановить ошибочный выпускcancel_jobPOST /api/jobs/{jobId}/cancel
Доделать прерванныйstart_generation с resume: truePOST /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. Документ с макетом уже есть (сценарий 1).
  2. MCP: issue_document с documentId, data: {"name": "Иванова Анна Сергеевна"}, email, send: true и idempotencyKey. REST: POST /api/documents/{id}/issue с телом {"data": {"name": "Иванова Анна Сергеевна"}, "email": "…", "send": true} и заголовком Idempotency-Key.
  3. Ключ — номер заказа или заявки, 8–100 знаков: латиница, цифры и _ - : .. В MCP он обязателен. В REST обязателен по смыслу, хоть и не по схеме: без него повтор после обрыва связи выпустит и спишет второй документ.
  4. Ответ 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.

ШагMCPREST
Форма по ссылке (своего сайта не нужно)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_formsGET /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. Письма

ШагMCPREST
Шаблон письмаset_mail_template (subject, bodyHtml, attachGeneratedFile)POST /api/mail/templates/{documentId}
Пробное письмо себеapi_request POST /api/mail/templates/{documentId}/test-send, тело — как у шаблонато же
Кому уйдёт и кому нет — показать человеку, получить «да»get_mail → audiencePOST /api/mailing/audience
Разослатьsend_mail с documentIdPOST /api/mailing/send с {"documentIds": [id], "kind": "transactional", "source": "table"}
Судьба писемget_mail → log (problemsOnly: true — только недоставленные)GET /api/mailing/log?documentId=…
Повторить недоставленныеsend_mail с documentId и onlyFailed: truePOST /api/mailing/resend
  • В теме и тексте подстановки — %name и другие поля строки.
  • Письма уходят отмеченным строкам, у которых документ уже выпущен; кому не поставлено — в skipped с причиной.
  • Пробное письмо по токену приходит тому, кто выдал токен (или разрешил подключение OAuth), а не на адрес из запроса.
  • С общего адреса сервиса — не больше 300 писем за раз и 500 за сутки на организацию; со своего подтверждённого домена предела нет.
  • Повторный send_mail безопасен: кто уже получил письмо, второй раз его не получит — так можно дослать письма строкам, выпущенным позже.

Сценарий 6. Исправить выданное

Опечатка в ФИО, письмо не дошло, документ выдан по ошибке:

ШагMCPREST
Найти выданноеsearch_registry (search — имя, почта или код)GET /api/registry
Карточка и файл одного документаsearch_registry с fileIdGET /api/registry/files/{fileId}, ссылка — GET /api/documents/files/{fileId}/url
Поправить строку таблицыedit_recipients (update)PATCH …/recipients/rows/{rowId}
Перевыпустить по исправленной строкеreissue_documents с fileIdsPOST /api/registry/reissue
Переслать письмоsend_mail с fileIdsPOST /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 и никуда больше: ни в сторонние сервисы, ни в публичные чаты, ни в код, который уйдёт в репозиторий.
  • Таблица участников и выпущенные документы — персональные данные. Не выкладывай их в сторонние сервисы и не показывай больше, чем нужно для задачи.
  • Ссылка на архив открывается без входа: отдавай её только тому, кто поручил работу.
  • Сомневаешься, тратит ли действие квоту или можно ли его отменить, — спроси.
Вручай