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

Как свёрстать лист: руководство для агента

Это руководство для программы, которая верстает сертификат, грамоту или диплом сама, без человека у экрана: где стоит каждый блок, каким шрифтом и кеглем он набран, как подставляются ФИО и падежи и как до выпуска убедиться, что всё влезло.

Точный список полей — в справочнике макета, здесь — как ими пользоваться. Запросы идут на https://vruchay.ru/api с заголовком Authorization: Bearer $VRUCHAY_TOKEN; рядом назван инструмент MCP, который делает то же самое. … в адресе ниже означает /api/documents/{id}.

Порядок работы

  1. GET /api/layout/catalog — единицы, форматы, шрифты, раскладки, умолчания блоков (MCP: get_layout_catalog).
  2. POST /api/documents с format, orientation, при желании preset (MCP: create_document). Запомнить id и sheets[0].id.
  3. PATCH /api/documents/{id}: мероприятие (eventName, eventDate, eventPlace, eventHours) и постоянные поля constants (MCP: update_document).
  4. Загрузить таблицу получателей: проверка меряет текст на её самых длинных значениях, а раскладка выбирает строки по её колонкам.
  5. Фон и картинки (MCP: upload_image по ссылке, файл с диска — get_upload_link).
  6. Свёрстать: …/sheets/{sheetId}/preset или …/sheets/{sheetId}/elements (MCP: apply_preset, edit_layout, set_layout).
  7. …/check → исправить → …/preview → посмотреть → исправить (MCP: check_sheet, preview_sheet), пока замечаний нет.
  8. POST …/validation — проверка каждой строки таблицы перед выпуском (MCP: validate_recipients).

Редактор в кабинете записывает лист с номером версии и правку агента молча не затрёт: человек с открытым листом увидит «Лист изменили в другом месте» и выберет, чья версия остаётся. Если в редакторе ничего не ждёт записи, свежий лист подхватится сам, когда человек вернётся во вкладку. Чтобы обойтись без выбора, просите не трогать лист, пока агент верстает.

1. Как устроен лист

У документа один или несколько листов (sheets по position): нулевой — лицевая сторона, дальше — оборот и следующие страницы. Лист — это id, position, layout (до 500 блоков), backgroundFileId и version (номер правки). Размер один на документ: pageWidthMm × pageHeightMm, 50…600 мм, по умолчанию 297 × 210 (A4 альбомный).

Координаты — мм от левого верхнего угла: x вправо, y вниз; блок — прямоугольник x, y, w, h. Кегль — в pt: 1 pt ≈ 0,3528 мм.

pt81012141824364872
мм2,83,54,24,96,48,512,716,925,4
  • rotation — градусы −360…360, по часовой вокруг центра блока.
  • z — целое −10000…10000, по умолчанию 0; больший поверх. При равных `z` поверх тот, кто позже в массиве.
  • opacity 0…1 — прозрачность блока целиком.
  • hidden: true — не печатается, но остаётся в листе; проверка его пропускает, кроме повтора id (duplicate_id): id должен быть единственным среди всех блоков.
  • locked, groupId — замок и группа для мыши в редакторе; на печать и API не влияют.
  • name — подпись в панели слоёв («ФИО», «Рамка»): ставьте, человеку после агента так проще.
  • id — 1–100 символов, уникален на листе. Говорящие id (title, name, qr) удобнее: по ним идёт правка и ими проверка называет блоки.

Отрисовка: белый лист → фон → видимые блоки по возрастанию z. Всё за краем листа обрезается. Текст, не влезший в блок, обрезается по краю блока — высоту считают, а не угадывают (раздел 4).

2. Формат и ориентация

formatКнижный, ммАльбомный, мм
a3297 × 420420 × 297
a4210 × 297297 × 210
a5148 × 210210 × 148
a6105 × 148148 × 105
letter (US Letter)216 × 279279 × 216
legal (US Legal)216 × 356356 × 216
square210 × 210210 × 210
wide (экран 16:9)167 × 297297 × 167

POST /api/documents принимает format + orientation (portrait | landscape) или pageWidthMm + pageHeightMm. Формат сильнее миллиметров; без `orientation` он альбомный. Одна orientation без format поворачивает миллиметры: {"orientation": "portrait"} даёт 210 × 297.

bash
curl -X POST "https://vruchay.ru/api/documents" \
  -H "Authorization: Bearer $VRUCHAY_TOKEN" -H "Content-Type: application/json" \
  -d '{"title": "Сертификаты участников", "format": "a4", "orientation": "landscape"}'

Ответ — документ с одним пустым листом и колонками name, email. PATCH /api/documents/{id} формата не знает, только миллиметры (раздел 13).

3. Поля печати

Типография срезает до 3 мм с края, офисный принтер не печатает ближе 5 мм. Текст и QR держите в 10–15 мм от края, рамку — в 5–10 мм; фон, полосы и подложки можно тянуть до края. PDF — ровно размер листа, без вылетов: если типография просит вылеты по 3 мм, сделайте лист на 6 мм больше (A4 альбомный — 303 × 216). Проверка сообщает off_page (блок целиком за листом), partly_off_page (текст, QR или ссылка частью за краем) и outside_safe_area (текст или QR ближе 5 мм к краю).

4. Арифметика раскладки

W, H — лист, m — поле, w, h — блок.

  • Центр: x = (W − w) / 2. На всю ширину между полями: x = m, w = W − 2m (A4 альбомный, m = 30: x = 30, w = 237; A4 книжный, m = 20: x = 20, w = 170) и align: "center".
  • Две колонки с промежутком g: c = (W − 2m − g) / 2, x₁ = m, x₂ = m + c + g (A4 альбомный, m = 30, g = 17: c = 110, x₂ = 157); правую выравнивайте align: "right".
  • Стопка: y₍ᵢ₊₁₎ = yᵢ + hᵢ + отбивка; 1–3 мм внутри группы, 6–15 мм между группами (шапка, имя, текст, подвал). Стопку поднимайте на 2,5 % высоты листа над центром — так делают заготовки: точно по центру она кажется съехавшей вниз.
  • Строки на одной высоте (должность слева, фамилия справа) — равные y, h и verticalAlign: "bottom".

Высота текстового блока. Строка — fontSize × lineHeight × 0,3528 мм. Буквы занимают 1,3 кегля, и при lineHeight меньше 1,3 выносные последней строки выступают — измеритель прибавляет выступ:

h ≥ n × fontSize × lineHeight × 0,3528 + max(0, 1,3 − lineHeight) × fontSize × 0,3528
    + strokeWidth + 2 × (padding + borderWidth)

n — строк; прибавьте 1–2 мм запаса. Пример 1 ниже (A4 альбомный) посчитан так:

БлокКегль, межстрочный, строкРасчёт, ммyh
Логотип——1623
Заголовок34 pt, 1,2, 114,4 + 1,2 = 15,64217
Подводка13 pt, 1,3, 16,0628
ФИО44 pt, 1,15, 235,7 + 2,3 = 38,07240
«принял участие»17 pt, 1,3, 17,81139
Мероприятие20 pt, 1,25, 217,6 + 0,4 = 18,012319
Место и даты13 pt, 1,3, 16,01437
Подписи11 pt, 1,25, 29,7 + 0,2 = 9,916311
Дата и номер10 pt, 1,3, 14,61816

Ширина строки — прикидка: знаков ≈ w / (fontSize × 0,3528 × k), где k — средняя ширина знака строчного русского текста в кеглях: ≈0,5 у PT Sans, Alegreya, Tinos, Marck Script; 0,53–0,57 у PT Serif, Playfair Display, Lora, Roboto, Arimo; 0,57–0,6 у Inter, Montserrat, Merriweather; 0,65–0,75 у Unbounded, Comfortaa; ≈0,4 у Oswald, Caveat. Прописные шире на 20–30 %, letterSpacing прибавляется после каждого знака. Точно меряет проверка листа — по метрикам самих шрифтов.

5. Кегли по ролям

Заготовки задают кегли долями от кегля имени и подбирают его под свободное поле. На A4 альбомном:

РольПримерДоляЗаготовкиДиапазон, pt
Заголовок«Сертификат», «Диплом»0,7231–37 pt, полужирный, разрядка 10 % кегля28–40
Подводка«Награждается», «Настоящим удостоверяется, что»0,3013–15 pt, прописные, разрядка 10 %12–16
ФИО%name, %name_dat143–51 pt, межстрочный 1,15, autoFit32–54
Команда, город%team · %city0,2611–13 pt11–14
Текст, мероприятие«принял участие», %event0,4218–21 pt, мероприятие полужирным16–22
Доп. строкиместо и даты, «в объёме 72 ч.»0,3013 pt12–14
Подписи, дата, номер%signer_role, %date0,2410–12 pt9–12
Подпись к QR, сноски«Проверить подлинность»——7–9

На A4 книжном заготовки крупнее примерно в 1,4 раза, но лист уже: имя крупнее 48 pt держится там только на autoFit, спокойнее 32–40 pt. A5 — кегли × 0,7, A3 — × 1,4; мельче 7 pt не читается. Цвет: заголовок и подводка — акцент, имя — тёмный тон акцента, текст — тёмно-серый или тёмно-коричневый, не чистый чёрный (#8B2020, #7A1E1E, #4A3628).

6. Шрифты

Печатаются только гарнитуры набора (файлы, кириллица, метрики; тот же список — fonts в каталоге):

Группа (id)Полужирный и курсивТолько полужирныйТолько обычное
Без засечек (sans)PT Sans, Inter, Montserrat, Roboto, Open Sans, Arimo, Alegreya SansComfortaa, Oswald—
С засечками (serif)PT Serif, Lora, Playfair Display, Merriweather, Alegreya, Alegreya SC, Tinos—Alice
Крупные жирные (display)—Unbounded, Unbounded ExtraBold, Unbounded BlackDela Gothic One, Rubik Mono One, Ruslan Display
Рукописные (script)—Caveat, Amatic SCMarck Script, Bad Script, Lobster, Pacifico, Pangolin, Pattaya
  • Файлы есть только у начертаний 400 и 700; недостающий полужирный или курсив браузер нарисует сам, грубо. fontWeight в разметке кроме 400 и 700 возьмёт ближайший файл.
  • Arimo и Tinos — замена Arial и Times New Roman с теми же ширинами (самих их нет). Широкие — Montserrat, Merriweather, Unbounded, Comfortaa; узкие — Oswald, Caveat, Amatic SC; Alegreya SC — капитель.
  • Иное имя схема пропустит, но печать подставит системный шрифт, а проверка ответит unknown_font. Пишите имя ровно как в таблице.
Стиль заготовкиЗаголовок, подводкаФИОТекст
classicPT SerifPlayfair DisplayPT Serif
solemnPlayfair DisplayPlayfair DisplayLora
academicLoraLoraLora
strictPT SansPT SansPT Sans
modernMontserratMontserratInter
scriptPT SerifMarck ScriptPT Serif
handwrittenPT SansCaveatPT Sans

Не больше двух-трёх гарнитур на лист; рукописные — только для имени, без прописных и разрядки; display — только для короткого заголовка.

7. Блоки

Умолчания — в каталоге (elements); непереданное свойство сервер дописывает и хранит лист полным.

Текст (type: "text")

json
{"id": "name", "type": "text", "name": "ФИО", "x": 30, "y": 72, "w": 237, "h": 40, "z": 5,
 "props": {"text": "%name", "fontFamily": "Playfair Display", "fontSize": 44, "bold": true, "color": "#7A1E1E",
  "align": "center", "verticalAlign": "middle", "lineHeight": 1.15, "letterSpacing": 0, "autoFit": true}}
СвойствоУмолчаниеЧто делает
text—Строка: %поле — подстановка, \n — новый абзац; сервер превращает её в doc
doc—Дерево с оформлением кусков (ниже). Нужно одно из text, doc
fontFamilyPT SansРаздел 6
fontSize16pt, до 500
color#000000#RRGGBB
align / verticalAligncenter / middleleft, center, right, justify / top, middle, bottom
lineHeight1.2Множитель кегля
letterSpacing0Разрядка в pt после каждого знака, может быть < 0
bold, italic, underline, uppercasefalseВесь блок; прописные — оформлением, данные не меняются
strokeWidth, strokeColor0, #ffffffОбводка букв, мм, 0…2 — текст на пёстром фоне
autoFitfalseУжимать текст, если не влезает
padding0Отступ текста от краёв, мм, 0…50
backgroundnullЗаливка блока; null — прозрачный
borderWidth, borderColor0, #000000Рамка блока, мм, 0…5
shadowfalseМягкая тень под буквами

Тексту остаётся w − 2 × (padding + borderWidth) в ширину и столько же меньше в высоту. `autoFit` только ужимает, ступенями: кегль до 65 %, межстрочный до 1,0 (не больше чем на пятую часть), кегль до 50 %, разрядка до −3 % кегля. Слова не рвутся: длинная фамилия ужимает кегль, пока не встанет в ширину целиком. Не влезло и так — обрежется, проверка скажет text_overflow. Включайте его у всего, что приходит из данных; кегль ставьте как для обычного имени, блок — с запасом на две строки.

Дерево текста (doc)

Для разного оформления в одном блоке. Формат — как у ProseMirror:

json
{"type": "doc", "content": [
  {"type": "paragraph", "attrs": {"align": "center"}, "content": [
    {"type": "text", "text": "за "},
    {"type": "mergeField", "attrs": {"source": "place_word", "format": "upper"},
     "marks": [{"type": "bold"}, {"type": "textStyle", "attrs": {"color": "#8A6A22"}}]},
    {"type": "text", "text": " место"},
    {"type": "hardBreak"},
    {"type": "mergeField", "attrs": {"source": "team", "fallback": "в личном зачёте"}, "marks": [{"type": "italic"}]}]},
  {"type": "paragraph", "content": [
    {"type": "text", "text": "Проверить: vruchay.ru/c/"},
    {"type": "mergeField", "attrs": {"source": "code"},
     "marks": [{"type": "link", "attrs": {"href": "https://vruchay.ru/c/{{code}}", "underline": false}}]}]}]}
  • Абзацы идут без отбивки; пустой ("content": []) — пустая строка. attrs.align перекрывает выравнивание блока, attrs.indent 0…8 — ступени по 6 мм. Внутри: text (непустой), mergeField, hardBreak.
  • Списки: bulletList (marker: disc, dash, circle, square), orderedList (numbering: decimal-dot, decimal-paren, upper-roman, lower-alpha; start) из listItem с абзацами; checked: true/false рисует ☑/☐.
  • Марки (до 8): bold, italic, underline, strike, superscript, subscript; textStyle — color, background, fontFamily, fontSize (pt), fontWeight, letterSpacing, wordSpacing (pt), transform (uppercase, lowercase, smallcaps); link — href (http(s)://, поля {{code}}/%code кодируются как часть адреса), underline, color.
  • Марка сильнее блока: кусок со своим textStyle.fontFamily не сменит гарнитуру вслед за блоком. До 1000 узлов, списки до 6 уровней.

Картинка (type: "image")

props.fileId — картинка блока, загруженная в документ (MCP: upload_image, target: "element"; файл с диска — get_upload_link с тем же target, см. раздел 9 «Фон»):

bash
curl -X POST "https://vruchay.ru/api/documents/$DOCUMENT_ID/assets" \
  -H "Authorization: Bearer $VRUCHAY_TOKEN" -F "file=@logo.png"
curl -X POST "https://vruchay.ru/api/documents/$DOCUMENT_ID/assets/from-url" \
  -H "Authorization: Bearer $VRUCHAY_TOKEN" -H "Content-Type: application/json" \
  -d '{"url": "https://example.ru/logo.png"}'

Ответ: {"fileId": "…", "url": "…", "widthPx": 800, "heightPx": 320}.

json
{"id": "logo", "type": "image", "x": 30, "y": 18, "w": 40, "h": 16, "props": {"fileId": "9b8a7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"}}
  • Фон сюда не годится: его fileId отвергается с 404 «Картинка не найдена».
  • Картинка вписывается без обрезки и искажения, лишнее место пустое. Держите пропорции: h = w × heightPx / widthPx.
  • PNG или JPEG до 20 МБ; подпись и печать — PNG с прозрачностью. Для печати — w / 25,4 × 300 пикселей по ширине. По ссылке — только публичный http(s) на портах 80/443, до 3 переадресаций, 15 с, 30 загрузок в минуту.
  • Без файла лист не напечатается (missing_image).
  • Картинки ложатся в PDF почти без сжатия: фон и картинки листа вместе держите до 4 МБ (heavy_images), иначе почта может не принять PDF вложением.

QR-код (type: "qr")

json
{"id": "qr", "type": "qr", "x": 244, "y": 151, "w": 30, "h": 30, "props": {"template": "", "color": "#1A1D20",
 "background": "#ffffff", "dotStyle": "square", "cornerStyle": "square", "cornerColor": null, "margin": 0, "level": "M"}}
  • Пустой template — адрес проверки экземпляра https://vruchay.ru/c/K7M2-9QXR-4TVB. При verifyEnabled: false такой QR не печатается.
  • Свой template — до 5000 знаков, поля только %поле, значения без кодирования (в адрес — поля без пробелов и кириллицы: https://example.ru/check?c=%code). http(s)-текст в PDF ещё и кликается.
  • Квадрат от 15 мм, лучше 20–30; рисуется по меньшей стороне.
  • background null — прозрачная подложка, только на светлом однотонном; dotStyle square/rounded/dots; cornerStyle square/rounded/circle; cornerColor (null — как точки); margin 0…4 — белое поле в клетках внутри блока (код мельче); level L/M/Q/H — запас 7/15/25/30 %, выше — гуще узор. Точки тёмные на светлом; подпись — отдельным блоком под QR.

Ссылка (type: "link")

Невидимая кликабельная область в PDF: props.url — http(s)://, до 2000 знаков, поля {{code}}/%code кодируются; не вышло адреса — область без ссылки. Поверх картинки — z больше.

json
{"id": "logo-link", "type": "link", "x": 30, "y": 18, "w": 40, "h": 16, "z": 2, "props": {"url": "https://edu.example.ru"}}

Фигура (type: "shape")

json
{"id": "frame", "type": "shape", "x": 8, "y": 8, "w": 281, "h": 194, "z": 0, "locked": true,
 "props": {"kind": "rect", "fill": null, "stroke": "#8B2020", "strokeWidth": 0.8, "radius": 2, "dash": 0}}

kind: rect (умолчание), ellipse (вписан в блок), line; fill — null или цвет; stroke — #000000; strokeWidth — мм, 0…20, по умолчанию 0,5; radius — скругление, мм; dash — штрих пунктира, мм (0 — сплошная).

  • Линия горизонтальна и идёт через середину блока: от (x, y + h/2) до (x + w, y + h/2); толщина — strokeWidth, h ставьте 1–2 мм. Вертикальная — rotation: 90 (раздел 16).
  • strokeWidth печатается ровно в миллиметрах: линия подписи — 0,3–0,5 мм, рамка грамоты — 0,5–1,5 мм. Тонкая линия может быть и залитым прямоугольником без обводки: {"kind": "rect", "fill": "#33383F", "strokeWidth": 0} при h = 0.3.
  • Подложка под текстом — меньший z и opacity 0,8–0,9.

8. Поля подстановки

Поле — ключ латиницей. В text и шаблоне QR — %ключ, в дереве — mergeField с attrs.source, в адресе ссылки — {{ключ}} или %ключ. GET …/fields перечисляет колонки, постоянные, мероприятие, служебные, поля каждого листа и unknown — те, что взять неоткуда.

Сила: служебные < постоянные поля документа < колонки строки.

Колонки — name, email (всегда) и заведённые при импорте (place, team, city…). Из surname, firstname, patronymic считаются падежи, сокращение, латиница и пол, но %name печатает только колонку name — заполняйте её или ставьте на лист части.

Постоянные поля — одно значение на всех (подписант, номер приказа); список целиком заменяет прежний. Ключ ^[a-z][a-z0-9_]{0,39}$, до 30 полей, значение до 1000 знаков, служебные ключи заняты.

bash
curl -X PATCH "https://vruchay.ru/api/documents/$DOCUMENT_ID" \
  -H "Authorization: Bearer $VRUCHAY_TOKEN" -H "Content-Type: application/json" \
  -d '{"constants": [{"key": "signer_role", "title": "Должность подписанта", "value": "Директор"},
                    {"key": "signer_name", "title": "Подписант", "value": "А. А. Петров"}]}'

Мероприятие — поля документа: eventName → %event, eventDate → %event_date, eventPlace → %event_place, eventHours → %hours; даты — свободным текстом.

Служебное полеПример
date / date_long / date_iso / date_en — дата выдачи12.10.2026 / 12 октября 2026 г. / 2026-10-12 / 12 October 2026
year, number (строка с 1), total2026, 3, 120
reg_number — сквозной номер за год142/2026
code — проверочный код (тот, что в QR)K7M2-9QXR-4TVB
org, valid_until (конец срока, если задан)АНО «Пример», 12.10.2027
event, event_date, event_place, hoursмероприятие
name_dat / name_gen / name_shortПетровой Анне Сергеевне / Петровой Анны Сергеевны / Петрова А. С.
name_lat_gost / name_lat_icao (как в загранпаспорте)Petrova Anna Sergeevna
place_word — из колонки placeвторое
hours_word — из hoursсемьдесят два часа
  • Дата — issueDate документа, иначе день выпуска, по Москве.
  • Падеж выбирает фраза: после «Награждается», «Объявляется» — %name_dat; в «подтверждает, что …» — %name.
  • place_word знает места 1–10 («1», «1 место», «1-е»), иное — как в ячейке. hours_word — только из чистого числа: пишите eventHours: "72".
  • Своя колонка (name_dat, place_word) сильнее вычисленного.

Род. В тексте макета: награждён(а), занял(а), участник(-ца), победитель(-ница), прошёл(ла) — скобка вплотную, 1–5 букв; или пара принял|приняла — для любых слов. Пол — из колонки пол/pol/gender/sex (м, муж, male, ж, жен, female…), иначе угадывается по ФИО. Не угадался — скобка остаётся как есть, пара печатается «принял/приняла». Незнакомое («студент(ка)») не трогается — пишите пару.

Печать значения. Подставленное значение проходит типографику: кавычки → «ёлочки», « - » → « — », «17-19» → «17–19», неразрывные пробелы в инициалах, после «г.», «ул.», «№», перед «%» и после однобуквенных предлогов; code и reg_number — буква в букву. Текст макета не трогается — набирайте сразу с «ёлочками» и тире. У mergeField: format none/upper/lower/title, fallback — если значения нет. Пустое поле — пустота; абзац только из полей и разделителей (, ; : · • – — -) при всех пустых полях исчезает, разделитель между двумя полями уходит, если одно пусто (%event_place, %event_date без даты → «г. Москва»). Абзац со словами остаётся.

9. Фон

Картинка бланка под всеми блоками, своя у каждого листа (MCP: upload_image по ссылке или get_upload_link для файла с диска, target: "background"):

bash
BG="https://vruchay.ru/api/documents/$DOCUMENT_ID/sheets/$SHEET_ID/background"
curl -X POST "$BG" -H "Authorization: Bearer $VRUCHAY_TOKEN" -F "file=@blank.jpg"
curl -X POST "$BG/from-url" -H "Authorization: Bearer $VRUCHAY_TOKEN" \
  -H "Content-Type: application/json" -d '{"url": "https://example.ru/blank.jpg"}'
curl -X DELETE "$BG" -H "Authorization: Bearer $VRUCHAY_TOKEN"   # убрать фон

Бланк с диска через MCP. Агенту с оболочкой (Claude Code, Codex, Cursor) файл передать аргументом нельзя — инструменты ходят JSON-ом. Вместо этого:

  1. get_upload_link {"documentId": "…", "target": "background", "sheetId": "…"} — в ответе готовая команда с одноразовой ссылкой на 15 минут;
  2. выполнить её, подставив путь: curl -sS -F "file=@бланк.png" "https://vruchay.ru/api/v1/uploads/u.…";
  3. curl ответит {"target": "background", "fileId": "…", "widthPx": 3508, "heightPx": 2480} — фон уже на листе; дальше preview_sheet.

Токена в команде нет: пропуск — сама ссылка, она принимает один файл. Файл не того типа ссылку не тратит — повторите с верным; за следующим файлом — новая ссылка. Логотип, подпись и печать — так же, с target: "element": полученный fileId ставится в props.fileId блока image.

  • PNG или JPEG до 20 МБ; ответ — fileId, url, widthPx, heightPx.
  • Заполняет лист с обрезкой, а не растягивается: лишнее по краям срезается поровну. Пропорции картинки — как у листа.
  • Пикселей = мм / 25,4 × dpi; для печати 300, ниже 150 мылит. A4 альбомный — 3508 × 2480 (минимум 1754 × 1240), книжный — 2480 × 3508, A5 альбомный — 2480 × 1748, A3 альбомный — 4961 × 3508.
  • Фон — не блок: нет в layout, нет z, его fileId не годится для image.

10. Готовые раскладки

Вид (kind) + набор шрифтов и цветов (style, раздел 6): award (грамота за место: %name_dat, «за %place_word место»), winner, laureate, participant, school, thanks, letter (все с %name_dat), attendee (%name, «принял|приняла участие»), course, graduate (%name, «в объёме %hours»).

bash
curl -X POST "https://vruchay.ru/api/documents/$DOCUMENT_ID/sheets/$SHEET_ID/preset" \
  -H "Authorization: Bearer $VRUCHAY_TOKEN" -H "Content-Type: application/json" \
  -d '{"kind": "attendee", "style": "modern", "titlePrinted": false,
       "palette": {"ink": "#2B2F33", "accent": "#2C6E63"}, "mode": "replace"}'

То же при создании: POST /api/documents с "preset": {"kind": "award", "style": "classic"}.

  • titlePrinted (по умолчанию false) — заголовок уже на бланке: своего не будет, текст начнётся с 34 % высоты листа.
  • palette — ink, accent, nameInk (иначе accent); в MCP — то же поле у apply_preset и у preset в create_document.
  • mode: replace (умолчание) или append (совпавшим id — суффикс; z блоков заготовки идут с нуля, под блоками с большим z).
  • Строки с полями, которых нет в колонках на момент применения (%team, %city, %place_word без place), выбрасываются; колонки result, category, coach, event_place, number добавляются «подпись: значение» (до двух). Сначала таблица, потом заготовка.
  • Подвал: %signer_role слева, %signer_name справа, %date · %org по центру. Если постоянных signer_role, signer_name нет, заготовка заводит их с образцами «Руководитель» и «И. О. Фамилия» — замените.
  • При включённой проверке подлинности заготовка ставит QR проверки (preset-{kind}-qr) в правый нижний угол поля, 20–30 мм (на A4 — 23): фамилия подписанта кончается левее него, строка даты сужается с обеих сторон, стопка поднимается. Если QR уже есть на другом листе (или на этом при append), второй не ставится. Убрали QR — проверка напомнит verify_without_qr.
  • Ответ — {sheet, problems}; id — preset-{kind}-0, -1…, -signer-role, -signer-name, -issued; у всех autoFit.

11. Правка по частям и версии

PATCH …/sheets/{sheetId}/elements (MCP: edit_layout):

json
{"baseVersion": 7, "remove": ["old-note"],
 "update": [{"id": "name", "h": 40, "props": {"fontSize": 40, "autoFit": true}},
            {"id": "event", "props": {"text": "за участие в %event"}}],
 "add": [{"id": "qr", "type": "qr", "x": 244, "y": 151, "w": 30, "h": 30, "props": {}},
         {"type": "text", "y": 190, "w": 120, "h": 6, "props": {"text": "Рег. № %reg_number", "fontSize": 9}}]}
  • Порядок: remove, update, add; убрать и добавить тот же id — заменить блок (так меняют его вид).
  • add: без id — UUID, без z — поверх всех, без x/y — по центру листа, без w/h — текст 120 × 20, картинка 50 × 50, QR 30 × 30, ссылка 80 × 10, прямоугольник 60 × 40, овал 40 × 40, линия 80 × 2. Занятый id — 400.
  • update: верхние поля заменяются, props сливаются на один уровень; новый props.text или props.doc заменяет весь текст; type и id не меняются.
  • До 500 операций каждого вида. Ответ — {sheet, problems}; ошибка схемы называет блок по id (name.props.fontSize: …).

Лист целиком — PATCH …/sheets/{sheetId} с {"layout": [...], "baseVersion": 7} (MCP: set_layout, он же сразу проверяет).

Версии. version растёт с каждым сохранением. Если лист изменился после baseVersion, ответ — 409 {"message": "Лист изменился (версия 9, а правка сделана к версии 7)…", "version": 9}: перечитайте (GET /api/documents/{id}, MCP: get_document с full: true) и повторите. Без baseVersion правка по частям сама ложится на свежий лист (до 4 попыток), а лист целиком пишется поверх. Новые картинки должны быть картинками организации — иначе 404 «Картинка не найдена».

12. Проверка и превью

POST …/sheets/{sheetId}/check (MCP: check_sheet): без тела — сохранённый лист, {"layout": [...]} — черновик. Ответ — {version, problems} (code, severity, elementId, message); скрытые блоки пропускаются — кроме повтора id, он ищется среди всех. Текст примеряется к образцу «Иванов Иван» и к самым длинным значениям каждой колонки; код — «WWWW-WWWW-WWWW», номер — «1000/2026», мероприятие и постоянные — из документа.

КодЧто значит → что делать
duplicate_iderrorДва блока с одним id → развести, сохранив лист целиком
off_pageerrorБлок целиком за листом → поправить x, y
partly_off_pagewarningТекст, QR или ссылка частью за краем → внутрь
outside_safe_areawarningТекст или QR ближе 5 мм к краю → 10–15 мм
text_overflowerrorНе влезает → больше h/w, меньше fontSize, autoFit
unknown_fielderrorПоля нет у документа → исправить имя или завести поле
unknown_fontwarningГарнитуры нет в наборе → раздел 6
missing_imageerrorНет картинки с таким fileId → загрузить в …/assets
small_qrwarningQR меньше 15 мм → 20–30 мм
qr_not_squarewarningw и h QR различаются больше 0,5 мм → квадрат
qr_hidden_without_verifywarningПустой QR при выключенной проверке → verifyEnabled или template
verify_without_qrwarningПроверка включена, а QR проверки нет ни на одном листе → добавить QR с пустым template или выключить verifyEnabled
empty_textwarningНи текста, ни полей → заполнить или убрать
overlapwarningQR на тексте или QR; текст на тексте больше чем на 1/5 меньшего → развести
heavy_imageswarningФон и картинки вместе тяжелее 4 МБ — PDF не уйдёт вложением → JPEG, до 3500 px по длинной стороне

Наложения фигур, картинок и ссылок — не ошибка: рамка под текстом обычна.

Превью — POST …/sheets/{sheetId}/preview (MCP: preview_sheet), тело {} или {"rowId": "…"}: JPEG сохранённого листа таким, каким его напечатает выпуск, 96 dpi (A4 альбомный — 1123 × 794). Данные — первая строка таблицы или образец «Иванов Иван» (прочие колонки — заголовок в скобках); код — PRVW-0000-0000, номер — 0/2026. Квоту не тратит, 30 в минуту на организацию; 503 — повторить, 422 — лист не печатается (не загрузилась картинка или шрифт). Посмотрите и строку с самым длинным ФИО.

Вся таблица — POST …/validation (MCP: validate_recipients) меряет каждый блок с полями на каждой строке; overflow там — текст, обрезанный у конкретного человека. Коды — в справочнике проверки.

13. Смена размера листа

PATCH /api/documents/{id} с pageWidthMm, pageHeightMm и resize (MCP: update_document) — для всех листов:

  • keep (умолчание) — блоки не трогаются и могут оказаться за краем;
  • scale — всё пропорционально с min(W₂/W₁, H₂/H₁), по центру: координаты, размеры (от 5 мм), кегли (от 4 pt), разрядка, отступы, рамки, обводки, кегли в разметке;
  • reposition — размеры и кегли прежние, центр блока на той же доле листа.

Фон ляжет с обрезкой; под другие пропорции загрузите новый. Потом — проверка и превью.

14. Несколько листов

POST …/sheets — пустой лист в конец (MCP: add_sheet); DELETE …/sheets/{sheetId} — убрать (MCP: delete_sheet), единственный нельзя (404 «Нельзя удалить единственный лист документа»). Лист — следующая страница PDF; в JPEG листы идут один под другим. Размер общий, фон и блоки свои.

15. Полные примеры

Проходят схему и проверку без замечаний при заполненном мероприятии и постоянных signer_role, signer_name. fileId — заглушки, подставьте свои из …/assets. Отправляются как {"layout": [...]} (PATCH …/sheets/{sheetId}, MCP: set_layout).

Пример 1. Сертификат участника, A4 альбомный

Поля 30 мм, проверка подлинности включена, высоты — из таблицы раздела 4. Логотип по центру (297 − 23) / 2 = 137; подпись к QR под ним по центру (236 + 46/2 = 244 + 30/2); линия подписи на высоте 172 + 2/2 = 173 — у низа строк подписи, прижатых книзу.

json
[
 {"id": "frame", "type": "shape", "name": "Рамка", "x": 8, "y": 8, "w": 281, "h": 194, "z": 0, "locked": true,
  "props": {"kind": "rect", "stroke": "#8B2020", "strokeWidth": 0.8, "radius": 2}},
 {"id": "frame-inner", "type": "shape", "name": "Рамка тонкая", "x": 11, "y": 11, "w": 275, "h": 188, "z": 1, "locked": true,
  "props": {"kind": "rect", "stroke": "#8B2020", "strokeWidth": 0.3, "radius": 1}},
 {"id": "logo", "type": "image", "name": "Логотип", "x": 137, "y": 16, "w": 23, "h": 23, "z": 2,
  "props": {"fileId": "3f2a9c1e-5b7d-4e8a-9c0f-1a2b3c4d5e6f"}},
 {"id": "title", "type": "text", "name": "Заголовок", "x": 30, "y": 42, "w": 237, "h": 17, "z": 3,
  "props": {"text": "Сертификат", "fontFamily": "PT Serif", "fontSize": 34, "bold": true, "uppercase": true,
   "letterSpacing": 3.4, "lineHeight": 1.2, "color": "#8B2020"}},
 {"id": "lead", "type": "text", "name": "Подводка", "x": 30, "y": 62, "w": 237, "h": 8, "z": 4,
  "props": {"text": "Настоящим удостоверяется, что", "fontFamily": "PT Serif", "fontSize": 13, "uppercase": true,
   "letterSpacing": 1.3, "lineHeight": 1.3, "color": "#4A3628"}},
 {"id": "name", "type": "text", "name": "ФИО", "x": 30, "y": 72, "w": 237, "h": 40, "z": 5,
  "props": {"text": "%name", "fontFamily": "Playfair Display", "fontSize": 44, "bold": true,
   "lineHeight": 1.15, "autoFit": true, "color": "#7A1E1E"}},
 {"id": "participation", "type": "text", "name": "Участие", "x": 30, "y": 113, "w": 237, "h": 9, "z": 6,
  "props": {"text": "принял|приняла участие в мероприятии", "fontFamily": "PT Serif", "fontSize": 17,
   "lineHeight": 1.3, "color": "#4A3628"}},
 {"id": "event", "type": "text", "name": "Мероприятие", "x": 30, "y": 123, "w": 237, "h": 19, "z": 7,
  "props": {"text": "%event", "fontFamily": "PT Serif", "fontSize": 20, "bold": true, "lineHeight": 1.25,
   "autoFit": true, "color": "#4A3628"}},
 {"id": "event-meta", "type": "text", "name": "Место и даты", "x": 30, "y": 143, "w": 237, "h": 7, "z": 8,
  "props": {"text": "%event_place, %event_date", "fontFamily": "PT Serif", "fontSize": 13, "lineHeight": 1.3,
   "color": "#4A3628"}},
 {"id": "signer-role", "type": "text", "name": "Должность", "x": 30, "y": 163, "w": 75, "h": 11, "z": 9,
  "props": {"text": "%signer_role", "fontFamily": "PT Serif", "fontSize": 11, "align": "left",
   "verticalAlign": "bottom", "lineHeight": 1.25, "autoFit": true, "color": "#4A3628"}},
 {"id": "sign-line", "type": "shape", "name": "Линия подписи", "x": 110, "y": 172, "w": 45, "h": 2, "z": 10,
  "props": {"kind": "line", "stroke": "#4A3628", "strokeWidth": 0.3}},
 {"id": "signer-name", "type": "text", "name": "Подписант", "x": 160, "y": 163, "w": 60, "h": 11, "z": 11,
  "props": {"text": "%signer_name", "fontFamily": "PT Serif", "fontSize": 11, "align": "left",
   "verticalAlign": "bottom", "lineHeight": 1.25, "color": "#4A3628"}},
 {"id": "issued", "type": "text", "name": "Дата и номер", "x": 30, "y": 181, "w": 190, "h": 6, "z": 12,
  "props": {"text": "Выдан %date · рег. № %reg_number", "fontFamily": "PT Serif", "fontSize": 10, "align": "left",
   "lineHeight": 1.3, "color": "#6B5B4E"}},
 {"id": "qr", "type": "qr", "name": "QR проверки", "x": 244, "y": 151, "w": 30, "h": 30, "z": 13,
  "props": {"template": "", "color": "#4A3628"}},
 {"id": "qr-caption", "type": "text", "name": "Подпись к QR", "x": 236, "y": 182, "w": 46, "h": 6, "z": 14,
  "props": {"text": "Проверить подлинность", "fontFamily": "PT Sans", "fontSize": 8, "lineHeight": 1.3,
   "color": "#6B5B4E"}}
]

«Константинопольская Александра Владимировна» встаёт при 44 pt в две строки; длиннее — ужмёт autoFit.

Пример 2. Диплом за место, A4 книжный

Рамка и герб напечатаны на фоне-бланке; поля 20 мм, в таблице есть place. Имя в дательном, место словом прописными (format: "upper"), линия подписи — тонкий залитый прямоугольник, номер и код по бокам от QR. Подписи слоёв (name) в примерах 2 и 3 опущены для краткости.

json
[
 {"id": "title", "type": "text", "x": 20, "y": 52, "w": 170, "h": 24, "z": 2,
  "props": {"text": "Диплом", "fontFamily": "Lora", "fontSize": 48, "bold": true, "uppercase": true,
   "letterSpacing": 4.8, "lineHeight": 1.2, "color": "#2F4A6B"}},
 {"id": "lead", "type": "text", "x": 20, "y": 84, "w": 170, "h": 9, "z": 3,
  "props": {"text": "Награждается", "fontFamily": "Lora", "fontSize": 15, "uppercase": true,
   "letterSpacing": 1.5, "lineHeight": 1.3, "color": "#2F4A6B"}},
 {"id": "name", "type": "text", "x": 20, "y": 96, "w": 170, "h": 34, "z": 4,
  "props": {"text": "%name_dat", "fontFamily": "Lora", "fontSize": 36, "bold": true, "lineHeight": 1.15,
   "autoFit": true, "color": "#1F2A38"}},
 {"id": "place", "type": "text", "x": 20, "y": 134, "w": 170, "h": 14, "z": 5,
  "props": {"fontFamily": "Lora", "fontSize": 26, "bold": true, "lineHeight": 1.2, "color": "#2F4A6B",
   "doc": {"type": "doc", "content": [{"type": "paragraph", "content": [
    {"type": "text", "text": "за "},
    {"type": "mergeField", "attrs": {"source": "place_word", "format": "upper"},
     "marks": [{"type": "textStyle", "attrs": {"color": "#8A6A22", "letterSpacing": 2}}]},
    {"type": "text", "text": " место"}]}]}}},
 {"id": "event", "type": "text", "x": 20, "y": 152, "w": 170, "h": 26, "z": 6,
  "props": {"text": "%event", "fontFamily": "Lora", "fontSize": 18, "lineHeight": 1.3, "autoFit": true, "color": "#33383F"}},
 {"id": "event-meta", "type": "text", "x": 20, "y": 180, "w": 170, "h": 7, "z": 7,
  "props": {"text": "%event_place, %event_date", "fontFamily": "Lora", "fontSize": 12, "italic": true,
   "lineHeight": 1.3, "color": "#33383F"}},
 {"id": "signer-role", "type": "text", "x": 20, "y": 234, "w": 70, "h": 11, "z": 8,
  "props": {"text": "%signer_role", "fontFamily": "Lora", "fontSize": 11, "align": "left",
   "verticalAlign": "bottom", "lineHeight": 1.25, "autoFit": true, "color": "#33383F"}},
 {"id": "sign-line", "type": "shape", "x": 93, "y": 244.7, "w": 34, "h": 0.3, "z": 9,
  "props": {"kind": "rect", "fill": "#33383F", "strokeWidth": 0}},
 {"id": "signer-name", "type": "text", "x": 130, "y": 234, "w": 60, "h": 11, "z": 10,
  "props": {"text": "%signer_name", "fontFamily": "Lora", "fontSize": 11, "align": "right",
   "verticalAlign": "bottom", "lineHeight": 1.25, "color": "#33383F"}},
 {"id": "reg", "type": "text", "x": 20, "y": 262, "w": 66, "h": 10, "z": 11,
  "props": {"text": "Рег. № %reg_number\nот %date_long", "fontFamily": "Lora", "fontSize": 9, "align": "left",
   "lineHeight": 1.3, "color": "#33383F"}},
 {"id": "qr", "type": "qr", "x": 92, "y": 252, "w": 26, "h": 26, "z": 12, "props": {"template": "", "color": "#1F2A38"}},
 {"id": "code", "type": "text", "x": 124, "y": 262, "w": 66, "h": 10, "z": 13,
  "props": {"text": "Код проверки\n%code", "fontFamily": "Lora", "fontSize": 9, "align": "right",
   "lineHeight": 1.3, "color": "#33383F"}}
]

Второе место печатается «за ВТОРОЕ место»; без place останется «за место» — перед выпуском проверьте, что колонка заполнена у всех.

Пример 3. Сертификат об обучении, A4 альбомный

Montserrat и Inter, выключка влево, полоса у левого края (фигуре до края можно), ссылка поверх логотипа, часы цифрами и прописью при eventHours: "72" — «в объёме 72 ч. (семьдесят два часа)». При пустом hours останется «в объёме ч. ()»: абзац со словами не схлопывается.

json
[
 {"id": "band", "type": "shape", "x": 0, "y": 0, "w": 14, "h": 210, "z": 0, "locked": true,
  "props": {"kind": "rect", "fill": "#2C6E63", "strokeWidth": 0}},
 {"id": "logo", "type": "image", "x": 30, "y": 18, "w": 40, "h": 16, "z": 1,
  "props": {"fileId": "9b8a7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"}},
 {"id": "logo-link", "type": "link", "x": 30, "y": 18, "w": 40, "h": 16, "z": 2, "props": {"url": "https://edu.example.ru"}},
 {"id": "title", "type": "text", "x": 30, "y": 44, "w": 200, "h": 18, "z": 3,
  "props": {"text": "Сертификат", "fontFamily": "Montserrat", "fontSize": 36, "bold": true, "align": "left",
   "lineHeight": 1.2, "color": "#1A1D20"}},
 {"id": "subtitle", "type": "text", "x": 30, "y": 62, "w": 200, "h": 9, "z": 4,
  "props": {"text": "о повышении квалификации", "fontFamily": "Montserrat", "fontSize": 16, "align": "left",
   "lineHeight": 1.2, "color": "#2C6E63"}},
 {"id": "lead", "type": "text", "x": 30, "y": 80, "w": 237, "h": 7, "z": 5,
  "props": {"text": "Настоящий сертификат подтверждает, что", "fontFamily": "Inter", "fontSize": 12,
   "align": "left", "lineHeight": 1.3, "color": "#2B2F33"}},
 {"id": "name", "type": "text", "x": 30, "y": 89, "w": 237, "h": 28, "z": 6,
  "props": {"text": "%name", "fontFamily": "Montserrat", "fontSize": 30, "bold": true, "align": "left",
   "lineHeight": 1.15, "autoFit": true, "color": "#1A1D20"}},
 {"id": "body", "type": "text", "x": 30, "y": 121, "w": 237, "h": 30, "z": 7,
  "props": {"fontFamily": "Inter", "fontSize": 14, "align": "left", "verticalAlign": "top", "lineHeight": 1.35,
   "autoFit": true, "color": "#2B2F33",
   "doc": {"type": "doc", "content": [
    {"type": "paragraph", "content": [
     {"type": "text", "text": "прошёл|прошла обучение по дополнительной профессиональной программе"}]},
    {"type": "paragraph", "content": [{"type": "mergeField", "attrs": {"source": "event"}, "marks": [{"type": "bold"}]}]},
    {"type": "paragraph", "content": [
     {"type": "text", "text": "в объёме "}, {"type": "mergeField", "attrs": {"source": "hours"}},
     {"type": "text", "text": " ч. ("}, {"type": "mergeField", "attrs": {"source": "hours_word"}},
     {"type": "text", "text": ")"}]}]}}},
 {"id": "issued", "type": "text", "x": 30, "y": 160, "w": 120, "h": 7, "z": 8,
  "props": {"text": "Дата выдачи: %date_long", "fontFamily": "Inter", "fontSize": 11, "align": "left",
   "lineHeight": 1.3, "color": "#2B2F33"}},
 {"id": "signer-role", "type": "text", "x": 30, "y": 175, "w": 90, "h": 11, "z": 9,
  "props": {"text": "%signer_role", "fontFamily": "Inter", "fontSize": 10, "align": "left",
   "verticalAlign": "bottom", "lineHeight": 1.25, "autoFit": true, "color": "#2B2F33"}},
 {"id": "sign-line", "type": "shape", "x": 125, "y": 185.7, "w": 40, "h": 0.3, "z": 10,
  "props": {"kind": "rect", "fill": "#2B2F33", "strokeWidth": 0}},
 {"id": "signer-name", "type": "text", "x": 170, "y": 175, "w": 60, "h": 11, "z": 11,
  "props": {"text": "%signer_name", "fontFamily": "Inter", "fontSize": 10, "align": "left",
   "verticalAlign": "bottom", "lineHeight": 1.25, "color": "#2B2F33"}},
 {"id": "reg", "type": "text", "x": 30, "y": 190, "w": 120, "h": 6, "z": 12,
  "props": {"text": "Рег. № %reg_number", "fontFamily": "Inter", "fontSize": 9, "align": "left",
   "lineHeight": 1.3, "color": "#6B7075"}},
 {"id": "qr", "type": "qr", "x": 247, "y": 158, "w": 30, "h": 30, "z": 13,
  "props": {"template": "", "color": "#1A1D20", "dotStyle": "rounded", "cornerStyle": "rounded", "cornerColor": "#2C6E63"}},
 {"id": "code", "type": "text", "x": 240, "y": 189, "w": 44, "h": 6, "z": 14,
  "props": {"text": "%code", "fontFamily": "Inter", "fontSize": 8, "letterSpacing": 0.5, "lineHeight": 1.3,
   "color": "#6B7075"}}
]

16. Рецепты правок

Тела PATCH …/sheets/{sheetId}/elements (MCP: edit_layout) для листа из примера 1.

Сдвинуть всё на 10 мм вниз — пакетного сдвига нет: прибавьте 10 к y всех блоков, кроме рамок, одним запросом. Сдвигайте всё содержимое, иначе сдвинутое наедет на оставшееся.

json
{"update": [{"id": "logo", "y": 26}, {"id": "title", "y": 52}, {"id": "lead", "y": 72}, {"id": "name", "y": 82},
 {"id": "participation", "y": 123}, {"id": "event", "y": 133}, {"id": "event-meta", "y": 153},
 {"id": "signer-role", "y": 173}, {"id": "sign-line", "y": 182}, {"id": "signer-name", "y": 173},
 {"id": "issued", "y": 191}, {"id": "qr", "y": 161}, {"id": "qr-caption", "y": 192}]}

Сменить шрифт везде — {"id": "title", "props": {"fontFamily": "Lora"}} для каждого текстового блока. Куски со своим textStyle.fontFamily это не меняет: перепишите их doc или замените текст строкой props.text.

Длинные ФИО — блок на всю ширину, высота на две строки, плотный межстрочный, автоподбор. Имя в дательном — "text": "%name_dat" и подводка «Награждается».

json
{"update": [{"id": "name", "x": 30, "w": 237, "h": 40, "props": {"fontSize": 44, "lineHeight": 1.15, "autoFit": true}}]}

Текст на пёстром фоне — подложка с меньшим z, либо background и padding у блока (отступ съедает высоту — блок выше), либо белая обводка букв strokeWidth 0,2–0,4 мм.

json
{"add": [{"id": "name-plate", "type": "shape", "x": 26, "y": 70, "w": 245, "h": 44, "z": 4, "opacity": 0.85,
  "props": {"kind": "rect", "fill": "#FFFFFF", "strokeWidth": 0, "radius": 3}}],
 "update": [{"id": "event-meta", "y": 142, "h": 9, "props": {"background": "#FFF8EE", "padding": 1}}]}

Два блока в ряд по колонкам раздела 4: равные y, h, verticalAlign: "bottom", левый влево, правый вправо.

json
{"add": [
 {"id": "org-note", "type": "text", "x": 30, "y": 192, "w": 110, "h": 6,
  "props": {"text": "%org", "fontFamily": "PT Serif", "fontSize": 9, "align": "left", "verticalAlign": "bottom"}},
 {"id": "date-note", "type": "text", "x": 157, "y": 192, "w": 110, "h": 6,
  "props": {"text": "%event_date", "fontFamily": "PT Serif", "fontSize": 9, "align": "right", "verticalAlign": "bottom"}}]}

Подпись картинкой — линию заменить залитым прямоугольником (убрать и добавить тот же id), сверху PNG подписи с прозрачностью:

json
{"remove": ["sign-line"], "add": [
 {"id": "sign-line", "type": "shape", "x": 110, "y": 173.7, "w": 45, "h": 0.3, "z": 10,
  "props": {"kind": "rect", "fill": "#4A3628", "strokeWidth": 0}},
 {"id": "signature", "type": "image", "x": 115, "y": 158, "w": 35, "h": 17.5, "z": 15,
  "props": {"fileId": "5d6e7f80-91a2-4b3c-8d4e-5f6a7b8c9d0e"}}]}

Вертикальная линия 30 мм с центром в (232; 166): линия w = 30, h = 2, повёрнутая на 90° вокруг центра, — x = 232 − 15, y = 166 − 1. И ссылка на страницу проверки поверх подписи к QR:

json
{"add": [
 {"id": "divider", "type": "shape", "x": 217, "y": 165, "w": 30, "h": 2, "rotation": 90,
  "props": {"kind": "line", "stroke": "#8B2020", "strokeWidth": 0.3}},
 {"id": "qr-link", "type": "link", "x": 236, "y": 182, "w": 46, "h": 6, "z": 20, "props": {"url": "https://vruchay.ru/c/{{code}}"}}]}

Мелочи: скрыть, не удаляя, — {"update": [{"id": "logo", "hidden": true}]}; поверх всех — {"update": [{"id": "qr", "z": 100}]}; начать заново — …/preset с "mode": "replace".

Оборот — POST …/sheets (MCP: add_sheet), затем блоки на новый лист:

json
{"add": [
 {"id": "back-title", "type": "text", "x": 30, "y": 30, "w": 237, "h": 12,
  "props": {"text": "Программа мероприятия", "fontFamily": "PT Serif", "fontSize": 22, "bold": true, "color": "#8B2020"}},
 {"id": "back-list", "type": "text", "x": 40, "y": 50, "w": 217, "h": 60,
  "props": {"fontFamily": "PT Serif", "fontSize": 13, "align": "left", "verticalAlign": "top", "lineHeight": 1.5,
   "doc": {"type": "doc", "content": [{"type": "orderedList", "content": [
    {"type": "listItem", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Пленарное заседание"}]}]},
    {"type": "listItem", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Работа секций"}]}]}]}]}}}]}

17. Особенности MCP

Инструменты повторяют REST (названы выше), с отличиями: get_document с full: true отдаёт макет в sheets[].elements; upload_image берёт картинку по ссылке, а файл с диска приносит curl по ссылке из get_upload_link; set_layout сразу возвращает замечания проверки; поля документа — в get_document (fields); проверка черновика и удаление фона — через api_request; это руководство и справочник — get_guide с темами layout и layout_reference.

Вручай