Початок роботи
- Увійдіть до Панель API і створіть ключ.
- Відкрийте Шаблони і натисніть «Створити шаблон».
- Скопіюйте templateId із вікна збереження або розділу «Шаблони».
- Надішліть текст, дочекайтеся completed і завантажте PDF тим самим ключем API.
Ключ у форматі hw_live_… показується повністю лише раз. Передавайте його в заголовку Authorization: Bearer …. Зберігайте ключ на своєму сервері або в змінній середовища.
Шаблони
Конструктор шаблонів API зберігає структуру, почерк, розмір і колір тексту, інтервали, поля, формат сторінки й фон. Виберіть простий текст для одного тексту або структурований шаблон для динамічних полів і колонок.
Простий текст приймає templateId + text. Структурований шаблон приймає templateId + data; структуру й оформлення задає шаблон. Шрифти вибираються із системної та власної бібліотеки. Public API повертає лише метадані шрифтів, без файлів і посилань на завантаження.
GET /templates | Ваші шаблони: ID, назва, режим і дати |
GET /templates/{id} | Модель конструктора з оформленням і блоками; для старих шаблонів — settings і fontIds |
GET /fonts | Спільні й власні шрифти; premium потребує відповідної підписки |
Кожне завдання зберігає знімок налаштувань. Зміни шаблону впливають на нові завдання.
Simple Text
templateId + textДля звичайного тексту й абзаців. Структуру з блоків створювати не потрібно.
Structured Template
templateId + dataДля заголовків, списків, зображень, таблиць і колонок. Порядок блоків зберігається в шаблоні.
Конструктор шаблонів API
Додайте блок, виберіть Static або Dynamic і задайте унікальний API key. Для Dynamic застосунок передає значення з цим ім’ям в об’єкті data.
| Елемент | Значення в data |
|---|---|
| Текст і заголовок | "body": "Текст..." |
| Список | "topics": ["Пункт 1", "Пункт 2"] |
| Зображення | "photo": { "assetId": "asset_…" } |
| Таблиця | "results": [{ "x": "1", "value": "2" }] |
Колонки задають розташування блоків. Зображення й текст можна розмістити поруч; кожне динамічне поле передається окремо в data. Роздільник, проміжок і розрив сторінки задаються в шаблоні.
{
"templateId": "tpl_…",
"data": {
"title": "Границі функцій",
"body": "Сьогодні ми вивчали границі…",
"topics": [
"Границя",
"Неперервність"
],
"photo": {
"assetId": "asset_…"
},
"results": [
{
"x": "1",
"value": "2"
},
{
"x": "2",
"value": "4"
}
]
}
}Перевіряйте нові значення в Test Data, потім оновіть Preview кнопкою. Історія допомагає скасувати й повторити зміни структури.
Text і Heading приймають рядок, List — масив рядків, Image — об’єкт assetId, Table — масив об’єктів із ключами колонок шаблону. Необов’язкове поле можна пропустити; некоректне значення повертає INVALID_TEMPLATE_DATA з назвою field. Для v2 не можна передавати content або overrides. Старі шаблони Document v1 працюють за попереднім контрактом без автоматичної міграції.
Налаштування тексту та сторінки
У Builder натисніть «Налаштувати шаблон» або «Почерк і модифікації в /main». Вибраний шрифт зберігається під час переходу в повний редактор.
Почерк, розмір і колір тексту, інтервали, поля, червоний рядок, сітка, папір і модифікатори. Текст у /main — демонстраційний; дані кожного документа надходять через API.
Власний формат створюється кнопкою «Створити власний формат» під полем «Формат». Укажіть назву й розміри в мм або px. Максимальний розмір — A3 (297 × 420 мм), включно з альбомною орієнтацією.
Модифікатори тексту
Використовується той самий редактор ефектів, що на звичайному сайті: слова, літери, рядки, папір і фон. Виберіть пресет або налаштуйте окремі параметри. Доступ до окремих функцій визначається умовами акаунта.
Почніть із невеликих відхилень нахилу, розміру й розташування літер. Перевірте Preview, потім збережіть шаблон: ці налаштування застосовуватимуться до нових API-документів.
Власний шрифт
Виберіть стандартний почерк, готовий шрифт із бібліотеки або створіть новий у FontCreator. Для створення й редагування власного шрифту потрібна відповідна підписка.
Збережений і зібраний власний шрифт залишається доступним для API. Для його редагування знову потрібна підписка. Аркуші API оплачуються окремо.
GET /fonts — Метадані доступних шрифтів. Вихідні файли шрифтів через API не видаються.
Простий текст · генерація PDF
POST /generations
{
"templateId": "tpl_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"text": "Hello from Handwrittner API"
}Для Simple Text передайте text, для Structured Template — data. Використовуйте лише одне поле вмісту в запиті. Для шаблонів v2 оформлення змінюється в Builder, а не через overrides.
POST /generations | 202 Accepted: ID, статус queued і дати |
GET /generations/{id} | Статус, pages, creditsSpent, downloadUrl після завершення |
GET /generations/{id}/download | PDF; потрібен Bearer API key власника |
POST /generations/{id}/cancel | Скасування queued/processing без списання |
Статуси: queued → processing → completed, або failed / cancelled. Перевіряйте статус приблизно раз на 10 секунд. Результат зберігається 7 днів; після цього download повертає 410.
Звичайний POST створює нове завдання. Після мережевої помилки не повторюйте його навмання: перевірте останні завдання в панелі API.
Робочі приклади
Вкажіть API-ключ і templateId. Виконайте три кроки послідовно, щоб зберегти handwrittner.pdf.
Генерація PDF виконується асинхронно. Перший запит створює завдання та повертає його ID. Потім перевірте статус завдання і після completed завантажте PDF за downloadUrl.
Для читання JSON у Bash потрібен jq. Виконуйте три кроки послідовно в одному терміналі.
Крок 1. Створити генерацію
POST /api/v1/generations# Requires jq.
command -v jq >/dev/null || { printf '%s\n' 'Install jq first.' >&2; exit 1; }
export HW_API_KEY='your_api_key'
JOB=$(curl --fail-with-body 'https://handwrittner.com/api/v1/generations' \
-H "Authorization: Bearer $HW_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary @- <<'HW_TEMPLATE_JSON'
{
"templateId": "tpl_REPLACE_WITH_YOUR_TEMPLATE_ID",
"text": "Hello from Handwrittner API"
}
HW_TEMPLATE_JSON
) || exit 1
BASE='https://handwrittner.com/api/v1'
printf '%s\n' "$JOB"
GENERATION_ID=$(printf '%s' "$JOB" | jq -er '.id') || exit 1Сервер повертає ID генерації та статус queued: завдання поставлено в чергу. Відповідь POST не містить PDF.
{
"id": "gen_...",
"status": "queued"
}Крок 2. Перевірити статус генерації
GET /api/v1/generations/{generationId}while true; do
STATUS=$(printf '%s' "$JOB" | jq -er '.status') || exit 1
case "$STATUS" in
completed) break ;;
queued|processing) sleep 10 ;;
*) printf '%s\n' "$JOB" >&2; exit 1 ;;
esac
JOB=$(curl --fail-with-body \
"$BASE/generations/$GENERATION_ID" \
-H "Authorization: Bearer $HW_API_KEY") || exit 1
printf '%s\n' "$JOB"
donequeued → processing → completed. Перевіряйте статус раз на 10 секунд. При failed покажіть error і не завантажуйте PDF. Приклад відповіді completed:
{
"id": "gen_...",
"status": "completed",
"pages": 2,
"creditsSpent": 2,
"downloadUrl": "/api/v1/generations/gen_.../download"
}Крок 3. Завантажити PDF
GET downloadUrl[ "$(printf '%s' "$JOB" | jq -r '.status')" = completed ] || exit 1
DOWNLOAD_URL=$(printf '%s' "$JOB" | jq -er '.downloadUrl') || exit 1
case "$DOWNLOAD_URL" in
https://*|http://*) ;;
/*) AUTHORITY="${BASE#*://}"; DOWNLOAD_URL="${BASE%%://*}://${AUTHORITY%%/*}$DOWNLOAD_URL" ;;
*) printf '%s\n' 'Invalid downloadUrl' >&2; exit 1 ;;
esac
curl --fail-with-body "$DOWNLOAD_URL" \
-H "Authorization: Bearer $HW_API_KEY" \
-o 'handwrittner.pdf' || exit 1Завантажуйте PDF лише після completed. Адреса береться з downloadUrl відповіді; відносний шлях розв’язується відносно адреси API.
Рендеринг тексту
Render API підходить для підписів, повідомлень, Telegram-ботів і тексту в застосунках та на сайтах. POST /renders синхронно повертає готовий SVG або PNG з HTTP 200. Опитування статусу не потрібне.
Генерація PDF через POST /generations залишається способом створювати повноцінні документи для друку, зокрема багатосторінкові.
Стиль береться з templateId. Дозволено перевизначити лише fontSize (6–96 pt). SVG містить готові контури літер, PNG малюється на сервері. Початкові TTF/OTF/WOFF/WOFF2, URL шрифтів, base64 і вихідні гліфи не видаються. HTML і WebFont не підтримуються.
SVG
export HW_API_KEY='your_api_key'
curl --fail-with-body 'https://handwrittner.com/api/v1/renders' \
-H "Authorization: Bearer $HW_API_KEY" \
-H 'Content-Type: application/json' \
--output 'handwriting.svg' \
--data-binary @- <<'HW_TEMPLATE_JSON'
{
"templateId": "tpl_REPLACE_WITH_YOUR_TEMPLATE_ID",
"text": "Hello from Handwrittner",
"format": "svg"
}
HW_TEMPLATE_JSONPNG
export HW_API_KEY='your_api_key'
curl --fail-with-body 'https://handwrittner.com/api/v1/renders' \
-H "Authorization: Bearer $HW_API_KEY" \
-H 'Content-Type: application/json' \
--output 'handwriting.png' \
--data-binary @- <<'HW_TEMPLATE_JSON'
{
"templateId": "tpl_REPLACE_WITH_YOUR_TEMPLATE_ID",
"text": "Hello from Handwrittner",
"format": "png"
}
HW_TEMPLATE_JSONContent-Type: image/svg+xml; charset=utf-8 або image/png. Відповідь містить Content-Length і один X-Request-ID.
Помилки: 400 INVALID_REQUEST (зокрема перевищення розмірів і рядків), 401 INVALID_API_KEY / API_KEY_REVOKED, 403 RENDER_ACCESS_DENIED / FONT_ACCESS_DENIED, 404 TEMPLATE_NOT_FOUND / FONT_NOT_FOUND, 429 RATE_LIMIT_EXCEEDED, 500 RENDER_FAILED, 503 RENDER_DISABLED. Тіло помилки містить code, message і requestId.
Типові ліміти: 3000 символів UTF-16, 20 рядків із перенесеннями, одна сторінка, до 2048 px на сторону й 4 Мп при 144 DPI; результат до 8 МБ, обробка до 15 секунд. Переповнення відхиляється без обрізання. Додаткові текстові й графічні накладання шаблону поки не підтримуються.
На обліковий запис: один активний важкий render, 5 прийнятих render-запитів на хвилину й 100 на добу UTC, незалежно від кількості ключів. Помилки після допуску теж витрачають квоту. Загальний ліміт ключа — 10 запитів на хвилину. Сервер може задавати інші ліміти.
Успішні результати враховуються окремо в renders, svgRenders і pngRenders у відповіді GET /usage. У режимі credits результат витрачає аркуші API за розміром сторінки (A5 — 1, A4 — 1,5, A3 — 2); доступ до premium-шрифтів і функцій усе одно перевіряється. Якщо режим не ввімкнено на сервері, відповідь — 503 RENDER_DISABLED.
Попередній перегляд
POST /previews приймає templateId і text або data залежно від режиму шаблону. Після completed отримайте зображення через GET /generations/{id}/preview. Рендеринг зупиняється після перших чотирьох сторінок; зображення містять водяний знак. PDF для перегляду недоступний.
Параметри page/pages і діапазони не підтримуються. Повтор незміненого перегляду повертає наявне завдання, доки результат не прострочений. До 3 нових переглядів на хвилину на обліковий запис.
Використання й баланс
GET /usage?from=2026-09-01&to=2026-10-01. Дати в UTC, to не включається; максимум 366 днів. За замовчуванням — поточний місяць до сьогодні включно.
Відповідь: requests, generations, successfulGenerations, failedGenerations, cancelledGenerations, pages, creditsSpent, period і balance. Аркушів API за сторінку: A5 — 1, A4 — 1,5, A3 — 2. Власний формат оплачується як найменший стандарт, що його вміщує, до A3. Баланс API окремий; підписка сайту не замінює списання аркушів API. У панелі доступні API Week, Basic, Pro й Premium та пакети 50–1000 аркушів без терміну дії. Спочатку витрачається ліміт підписки, потім додатковий баланс. Куплені аркуші зберігаються при підключенні чи зміні тарифу. Денний ліміт оновлюється о 00:00 UTC; період триває 7 або 30 днів. balance.subscription містить ліміти й витрати тарифу, balance.usage — pagesToday і pagesThisPeriod. У тестовому режимі гроші не списуються, а тестові аркуші не переходять на реальний баланс.
Сервер перевіряє доступний ліміт тарифу й баланс перед роботою та при завершенні. PDF, кінцевий статус і списання зберігаються атомарно. Якщо аркушів не вистачає на весь документ або сталася внутрішня помилка, PDF не видається й списання немає. Перегляд не входить до платних генерацій; запити з чинним ключем, включно з помилковими, враховуються.
Ліміти
- 10 запитів на хвилину на ключ, включно з опитуванням статусу й завантаженнями.
- 2 незавершені завдання на обліковий запис; до 32 завдань у черзі на екземпляр backend.
- Важкий рендеринг використовує спільний PDF-пул сайту: за замовчуванням 2 одночасно, не більше одного на користувача.
- Текст до 100 000 символів; без підписки залишається ліміт 4000 символів.
- До 200 сторінок; запит до 512 КіБ; PDF до 55 МБ; час рендерингу до 60 секунд за замовчуванням.
- До 100 шаблонів і 10 активних ключів API.
Відповідь 429 містить Retry-After: 60. Зачекайте вказаний час перед повтором.
Помилки й requestId
{
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Ліміту тарифу API й додаткового балансу недостатньо для документа. Докупіть аркуші або виберіть тариф.",
"requestId": "req_…"
}
}Кожен API-запит має заголовок X-Request-ID. Передавайте його до підтримки в разі проблем.
| HTTP | Коди |
|---|---|
| 400 / 413 | INVALID_REQUEST |
| 401 | INVALID_API_KEY, API_KEY_REVOKED |
| 402 | INSUFFICIENT_BALANCE |
| 404 | TEMPLATE_NOT_FOUND, GENERATION_NOT_FOUND, ENDPOINT_NOT_FOUND |
| 409 / 410 | GENERATION_NOT_READY, PREVIEW_ONLY, GENERATION_EXPIRED |
| 429 | RATE_LIMIT_EXCEEDED |
| 500 | INTERNAL_ERROR |
GENERATION_FAILED та INSUFFICIENT_BALANCE можуть бути в полі error завдання зі статусом failed. Чужі ресурси повертають 404, наче відсутні.
Сумісність із Document v1
Цей розділ стосується попередніх шаблонів Document v1. Для нових структурованих шаблонів Builder використовуйте data, як показано вище.
Передайте content замість text. Handwrittner Document Model v1 підтримує paragraph, heading (1–3), bulletList, orderedList, image, table, columns, pageBreak, divider і spacer. HTML і Tiptap JSON не приймаються.
{
"templateId": "tpl_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"content": [
{
"type": "heading",
"level": 1,
"text": "Границі функцій"
},
{
"type": "paragraph",
"text": "Сьогодні ми вивчали основні властивості границь."
},
{
"type": "bulletList",
"items": [
"Границя функції",
"Неперервність",
"Асимптоти"
]
},
{
"type": "columns",
"columns": [
{
"width": 55,
"content": [
{
"type": "paragraph",
"text": "Ліворуч розміщено пояснення теми."
},
{
"type": "table",
"rows": [
[
"x",
"f(x)"
],
[
"1",
"2"
],
[
"2",
"4"
]
]
}
]
},
{
"width": 45,
"content": [
{
"type": "image",
"assetId": "asset_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"width": "100%",
"align": "center"
},
{
"type": "paragraph",
"text": "Рисунок 1."
}
]
}
]
},
{
"type": "divider"
},
{
"type": "spacer",
"height": 12
},
{
"type": "pageBreak"
},
{
"type": "paragraph",
"text": "Продовження на новій сторінці."
}
]
}Спочатку завантажте зображення окремим запитом POST /assets: raw PNG або JPEG, Content-Type image/png або image/jpeg. Приклад: curl -H 'Authorization: Bearer …' -H 'Content-Type: image/png' --data-binary @image.png …/api/v1/assets. Відповідь містить id, type, width, height. Використовуйте отриманий assetId.
Зображення приватні. URL, SVG і base64 у content не приймаються. До 5 МіБ, 4096 px на сторону й 8 Мп. На обліковий запис — 200 зображень / 100 МіБ. WebP поки не підтримується.
Колонки: 2–6, сума ширин 100%; без width — порівну. columnGap задається в шаблоні (12 pt за замовчуванням). Таблиці переносяться всередині колонок. Вкладені колонки й pageBreak усередині колонки не підтримуються. Spacer: 1–500 pt.
Типові ліміти: 200 блоків загалом, 10 000 символів у текстовому блоці, 100 елементів списку, 200 × 20 комірок таблиці, 4000 комірок загалом, 20 зображень. Загальний ліміт тексту й оплата за фактичні сторінки зберігаються. Сервер може задавати менші ліміти.