HANDWRITTNER · PUBLIC API V1

Документация Public API

Шаблоны, настройка почерка и API-запросы: от первого ключа до готового документа.

Войти и создать API key ↗
Bearer API keyJSON → PDF / PNG / SVGOpenAPI ↗
BASE URLhttps://handwrittner.com/api/v1

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

  1. Войдите в Панель API и создайте ключ.
  2. Откройте Шаблоны и нажмите «Создать шаблон».
  3. Скопируйте templateId из окна сохранения или раздела Templates.
  4. Отправьте текст, дождитесь completed и скачайте PDF с тем же API-ключом.

Ключ формата hw_live_… показывается полностью один раз. Передавайте его в заголовке Authorization: Bearer …. Храните ключ на своём сервере или в переменной окружения.

Шаблоны

Отдельный API Template Builder сохраняет структуру, почерк, размер и цвет текста, интервалы, поля, формат страницы и фон. Выберите Simple Text для одного текста или Structured Template для динамических полей и колонок.

Simple Text принимает templateId + text. Structured Template принимает templateId + data; структуру и оформление задаёт шаблон. Шрифты выбираются из системной и собственной библиотеки. Public API возвращает только метаданные шрифта, без файлов и ссылок на скачивание.

GET /templatesВаши шаблоны: ID, название, режим и даты
GET /templates/{id}Модель Builder с оформлением и блоками; для прежних шаблонов — settings и fontIds
GET /fontsОбщие и собственные шрифты; premium требует соответствующей подписки

Каждое задание хранит снимок настроек. Изменение шаблона влияет на новые задания.

Simple Text

templateId + text

Для обычного текста и абзацев. Структуру из блоков создавать не нужно.

Structured Template

templateId + data

Для заголовков, списков, картинок, таблиц и колонок. Порядок блоков хранится в шаблоне.

Интерфейс Handwrittner
Выберите режим при создании шаблона. Он определяет поля вашего API-запроса.

Конструктор шаблонов API

Добавьте блок, выберите Static или Dynamic и задайте уникальный API key. Для Dynamic приложение передаёт значение с этим именем в объекте data.

Интерфейс Handwrittner
Добавляйте блоки слева, задавайте их порядок в центре и настраивайте выбранный элемент справа.
ЭлементЗначение в 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». Выбранный шрифт сохраняется при переходе в полный редактор.

Интерфейс Handwrittner
В /main сохраняется оформление шаблона. Текст редактора служит примером для настройки.
Что сохраняется в шаблоне

Почерк, размер и цвет текста, интервалы, поля, красная строка, сетка, бумага и модификаторы. Текст в /main — демонстрационный; данные каждого документа приходят через API.

Свой формат создаётся кнопкой «Создать свой формат» под полем «Формат». Укажите название и размеры в мм или px. Максимальный размер — A3 (297 × 420 мм), включая альбомную ориентацию.

Модификаторы текста

Используется тот же редактор эффектов, что на обычном сайте: слова, буквы, строки, бумага и фон. Можно выбрать пресет или настроить отдельные параметры. Доступ к отдельным функциям проверяется по условиям аккаунта.

Интерфейс Handwrittner
Включайте эффекты, выбирайте пресеты и уточняйте параметры перед сохранением шаблона.

Начните с небольших отклонений наклона, размеров и расположения букв. Проверьте результат в 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 /generations202 Accepted: ID, статус queued и даты
GET /generations/{id}Статус, pages, creditsSpent, downloadUrl после завершения
GET /generations/{id}/downloadPDF; требуется Bearer API key владельца
POST /generations/{id}/cancelОтмена queued/processing без списания

Статусы: queued → processing → completed, либо failed / cancelled. Проверяйте статус примерно раз в 10 секунд. Результат хранится 7 дней; после этого download возвращает 410.

Обычная отправка POST создаёт новое задание. При сетевом сбое не повторяйте её вслепую: проверьте последние задания в dashboard.

Рабочие примеры

Укажите API-ключ и templateId. Выполните три шага по порядку, чтобы сохранить handwrittner.pdf.

Интерфейс Handwrittner
После сохранения шаблона Builder подставляет его ID и данные в готовые примеры кода.

Генерация PDF выполняется асинхронно. Первый запрос создаёт задачу и возвращает её ID. Затем проверьте статус задачи и после completed скачайте PDF по downloadUrl.

POST generation → queued / processing → GET status → completed → GET download → PDF

Для чтения 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: задача поставлена в очередь. PDF не приходит в ответе POST.

{
  "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"
done

queued → 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. Polling не нужен.

Генерация PDF через POST /generations остаётся способом создавать полноценные документы для печати, включая несколько страниц.

Стиль берётся из templateId. Допустим только override fontSize (6–96 pt). SVG содержит готовые контуры букв, PNG рисуется на сервере. Исходный TTF/OTF/WOFF/WOFF2, font URL, base64 font и исходные glyphs не выдаются. 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_JSON

PNG

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_JSON

Content-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. Рендер останавливается после первых четырёх страниц; изображения содержат watermark. PDF для preview недоступен.

Параметры page/pages и диапазоны не поддерживаются. Повтор неизменённого preview возвращает существующее задание, пока его результат не истёк. Дополнительно действует лимит 3 новых preview в минуту на аккаунт.

Usage и баланс

A51 лист баланса за страницу
A41,5 листа баланса за страницу
A32 листа баланса за страницу

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 Dashboard доступны подписки API Week, Basic, Pro и Premium, а также пакеты от 50 до 1 000 листов без срока действия. Сначала расходуется лимит API-подписки, затем дополнительный баланс. Купленные листы сохраняются при подключении или смене тарифа. Дневной лимит обновляется в 00:00 UTC, период тарифа длится 7 или 30 дней. В balance.subscription доступны лимиты и расход тарифа, в balance.usage — pagesToday и pagesThisPeriod. В тестовом режиме пополнения не списывают деньги, а тестовые листы не переходят на реальный баланс.

Сервер проверяет доступный лимит тарифа и баланс перед работой и повторно при завершении. PDF, итоговый статус и списание сохраняются атомарно. Если страниц не хватает на весь документ или произошла внутренняя ошибка, PDF не выдаётся и списания нет. Preview не входит в платные генерации; API-запросы с действующим ключом, включая ошибочные, учитываются.

Лимиты

  • 10 запросов в минуту на ключ, включая polling и download.
  • 2 незавершённых задания на аккаунт, очередь до 32 заданий в одном экземпляре backend.
  • Тяжёлый рендер использует общий пул PDF сайта: по умолчанию 2 одновременно, не больше одного на пользователя.
  • Текст до 100 000 символов; без подписки сохраняется ограничение аккаунта 4 000 символов.
  • До 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 / 413INVALID_REQUEST
401INVALID_API_KEY, API_KEY_REVOKED
402INSUFFICIENT_BALANCE
404TEMPLATE_NOT_FOUND, GENERATION_NOT_FOUND, ENDPOINT_NOT_FOUND
409 / 410GENERATION_NOT_READY, PREVIEW_ONLY, GENERATION_EXPIRED
429RATE_LIMIT_EXCEEDED
500INTERNAL_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 изображений. Общий лимит текста и тарификация по фактическим страницам сохранены. Сервер может задавать меньшие лимиты.

Нужна помощь?

По любым вопросам пишите в Telegram (@lsamf). У каждого API-запроса есть заголовок X-Request-ID. Передавайте его в поддержку при проблемах.