Telegraph API обещает 64 КБ. Валится на 17. Замерил и обошёл⁠⁠

В документации Telegraph API на эндпоинт "createPage" написано:

"content (Array of Node, up to 64 KB)". По факту русскоязычный markdown в районе 20 КБ ловит "CONTENT_TOO_BIG". Замерил, где реально проходит граница, и расскажу, как обойти.

Поймал я это так. У меня сервис генерит посты блога и публикует их в двух местах сразу: на сайте и параллельно отправляет в Telegraph ради красивой OG-карточки в TG-канале. Англоязычные переводы летели нормально. Русские оригиналы с конца марта молча перестали публиковаться в Telegraph, в обработке стоял fallback «если ссылка не вернулась, шлём в канал без неё», и недели две никто этого не замечал. В логи смотреть надо было раньше.

Telegraph API обещает 64 КБ. Валится на 17. Замерил и обошёл

Минимальное воспроизведение

Создаём свежий аккаунт через "createAccount" (он сразу выдаёт "access_token"), кидаем через "createPage" контент разного размера:

const para = 'Тестовый абзац с нормальным текстом. ';

let md = '';

while (md.length < kbTarget * 1024) md += para + '\n\n';

const content = md.split('\n\n').filter(Boolean).map(

p => ({ tag: 'p', children: [p] })

);

const res = await fetch('https://api.telegra.ph/createPage', {

method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: JSON.stringify({ access_token, title: `${kbTarget} KB`, content }),

});

Прогнал от 5 до 40 КБ с паузой полторы секунды между вызовами, чтобы исключить rate-limit. Что вышло.

5 КБ исходного markdown - это после "JSON.stringify" 8 581 символ, по UTF-8 примерно 13 600 байт. Прошло. 10 КБ markdown - 17 096 символов JSON, около 27 200 байт. Тоже прошло. 20 КБ - 34 191 символов JSON, около 54 400 байт - "CONTENT_TOO_BIG". 30 и 40 КБ markdown упираются ровно в ту же стену.

Реальный потолок где-то между 10 и 20 КБ исходного markdown. Не 64, как в документации, и даже не близко.

Пробовал отправлять через "application/x-www-form-urlencoded" вместо JSON - поведение идентично, ошибка на тех же порогах. Envelope добавляет 200-300 байт, разница до обещанных 64 не заполняется. Скорее всего, Telegraph внутри парсит payload в свою структуру "Node" и меряет лимит уже после парсинга, где она пухнет за счёт служебных полей. Но это домыслы, проверить нечем.

Почему русский язык упирается раньше английского

Это вторая половина бага. В JavaScript длина строки считается в UTF-16 code units, и "String.prototype.length" для кириллической буквы вернёт 1, как и для латинской. А вот в HTTP-теле строка едет в UTF-8, и там кириллический символ занимает 2 байта на букву. Получается, при одинаковом "length" русский текст по сети ровно в два раза тяжелее английского.

Если Telegraph меряет лимит по байтам после кодирования (это правдоподобная версия), русский контент упирается в потолок где-то на 40-50% быстрее. EN-переводы у меня делает Gemini Flash Lite, они на 30-40% короче по символам, и на все 50% - по байтам. Большой пост в RU даёт 60-80 КБ, его перевод 20-30 КБ. Перевод помещается в зелёную зону, оригинал нет. Вот вам и «на одном языке работает, на другом нет», хотя код между ними общий.

Налажал я именно тут: думал, что байтовый лимит и символьный - это одно и то же. Получил по голове.

Как обойти

Резать markdown под безопасный размер и ставить маркер «полный текст на сайте». Эмпирическая зелёная зона - 18 КБ исходного markdown:

const TELEGRAPH_SAFE_MD_BYTES = 18 * 1024;

function truncateMarkdownAtBoundary(md, maxBytes, lang) {

if (Buffer.byteLength(md, 'utf8') <= maxBytes) {

return { md, truncated: false };

}

let cut = md;

while (Buffer.byteLength(cut, 'utf8') > maxBytes) {

cut = cut.slice(0, Math.floor(cut.length * 0.9));

}

// По возможности режем по границе абзаца, а не посреди слова

const lastBreak = cut.lastIndexOf('\n\n');

if (lastBreak > cut.length * 0.5) cut = cut.slice(0, lastBreak);

const note = lang === 'en'

? '\n\n\_Article truncated for Telegraph. Read full version on the site.\_'

: '\n\n\_Статья сокращена для Telegraph. Полный текст на сайте.\_';

return { md: cut + note, truncated: true };

}

Две детали, на которых легко обжечься.

Цикл режет по длине строки, а не по байтовому индексу. Если бы я сразу резал по целевому байт-индексу, мог бы попасть в середину кириллического двухбайтового символа. Получилась бы невалидная UTF-8 последовательность, и Telegraph такой payload отбрасывает без объяснений. Срез через "String.prototype.slice" по длине гарантирует валидную границу для BMP-символов, куда попадает кириллица.

Шаг 10% на первый взгляд жадноват, но на реальных входах цикл обычно укладывается в 1-2 итерации. На очень коротких текстах работает ранний return.

Поверх этого ставлю retry: если 18 КБ всё равно не прошло (бывает, если в тексте много ссылок - каждая "[text](url)" это плюс 30-50 байт после сериализации), уменьшаю лимит на 40% и пробую ещё раз. Максимум три попытки. Если всё ушло в потолок, публикую без Telegraph-ссылки, в канале остаётся ссылка только на сайт.

Что забрать с собой

Документированные 64 КБ у Telegraph - не верхняя граница, не нижняя, вообще не граница. Эмпирическое число на апрель 2026 - 17-18 КБ исходного markdown. Если они подкрутят свой внутренний лимит, придётся подкручивать и у вас. Сервис существует с 2016, документация обновлялась, кажется, тогда же.

Если у вас пайплайн «генерируем контент - публикуем в Telegraph для OG-карточки - потом ссылка в TG-канал», и в логах хоть раз мелькало "CONTENT_TOO_BIG", - скорее всего, RU-публикация у вас молча отваливается на длинных постах. Загляните в логи, там может быть приятный сюрприз.

И наконец общее. UTF-8 байты и UTF-16 code units - разные вещи. Если интеграция живёт на байтовом лимите, считать надо через "Buffer.byteLength", не через "length". Касается и баз, и транспортного слоя, и rate-лимитов. С токенами LLM ситуация ещё веселее, там для кириллицы вообще другие правила, чем для латиницы, и единственный способ не промахнуться - прогонять корпус на всех языках, которые у вас в проде.

Лига программистов

2.4K постов12K подписчиков

Правила сообщества

- Будьте взаимовежливы, аргументируйте критику

- Приветствуются любые посты по тематике программирования

- Если ваш пост содержит ссылки на внешние ресурсы - он должен быть самодостаточным. Вариации на тему "далее читайте в моей телеге" будут удаляться из сообщества

2
Автор поста оценил этот комментарий

да тут всем похуй

не оценят

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества

Недвижимость и ремонт

Теги

Популярные авторы

Сообщества