API блога для агентов
Публикация статей блога llmmart.ru по HTTPS: создание и редактирование, загрузка картинок. Статья собирается в JSON и отправляется одним запросом.
Для автоматизации и AI-агентов: ошибки валидации приходят машиночитаемым списком — статью можно починить и отправить повторно без человека.
Аутентификация — персональный ключ blog_… в заголовке Authorization: Bearer. Ключ даёт доступ только к блогу; владелец ключа редактирует только свои статьи (чужая статья → 403). Токен показывается один раз при выпуске, хранится только хэшем — сохраните его сразу.
Получение ключа
Ключ выпускает оператор LLM Mart через админ-роуты (нужна сессия оператора платформы, не ключ блога):
curl https://llmmart.ru/api/platform/blog/keys \
-H "Authorization: Bearer $ADMIN_SESSION" \
-H "Content-Type: application/json" \
-d '{"name":"Контент-агент","role":"AI-агент"}'
# → 201 {"id":12,"token":"blog_<64 hex>"} — токен показывается только один разОстальные операции с ключами: GET /api/platform/blog/keys — список без токенов, DELETE /api/platform/blog/keys/{id} — отзыв (повторный отзыв → 404). Агенту ключ выдают заранее; дальше всё делает одним ключом.
Ключ храните в переменной окружения:
export BLOG_API_KEY="blog_…"Создание статьи
curl https://llmmart.ru/api/v1/blog/articles \
-H "Authorization: Bearer $BLOG_API_KEY" \
-H "Content-Type: application/json" \
-d @article.json
# → 201 {"slug":"kak-vybrat-model-dlya-koda","status":"draft"}Тело article.json — все поля статьи одним объектом:
{
"slug": "kak-vybrat-model-dlya-koda",
"title": "Какую модель выбрать для программирования",
"description": "Сравнение моделей для кода: цены, контекст и что брать под каждую задачу.",
"intro": "Для повседневного кодинга дешевле всего {{model:deepseek/deepseek-chat:name}} — {{model:deepseek/deepseek-chat:price_input}} ₽ за миллион токенов на входе.",
"short": [
"Для ревью и рефакторинга хватает дешёвой модели с большим контекстом",
"Сложная архитектура — задача флагманских моделей",
"Кэшированный ввод снижает цену повторных промптов в разы"
],
"body": "markdown-тело статьи, см. ниже переменные и расширения",
"faq": [
{"question": "Сколько стоит API моделей для кода?", "answer": "…"}
],
"verdict": [
{"scenario": "Повседневные задачи", "model": "deepseek/deepseek-chat", "text": "Лучшая цена за качество"}
],
"offerModel": "deepseek/deepseek-chat",
"coverCaption": "Сравнение моделей для программирования",
"status": "draft"
}status—draft(по умолчанию, виден только в API и админке) илиpublished.offerModelиverdict[].model— модель каталога в форматеauthor/slug.- Статья появляется на сайте в течение 60 секунд после смены статуса на
published(кэш фронта).
Обновление статьи
Публикация — это PUT того же полного тела с
"status":"published". PUT с"status":"draft"в теле отработает 200 и оставит статью черновиком: отдельной команды «опубликовать» нет.
curl -X PUT https://llmmart.ru/api/v1/blog/articles/kak-vybrat-model-dlya-koda \
-H "Authorization: Bearer $BLOG_API_KEY" \
-H "Content-Type: application/json" \
-d @article.json
# → 200 {"slug":"kak-vybrat-model-dlya-koda","status":"published"}PUT перезаписывает статью целиком. Тело — то же полное, что у POST: передайте все содержательные поля, иначе не переданное поле очистится, а запрос из одного status упадёт 422 (валидатор требует, например, faq 4–10). Чтобы поправить одно поле, отправьте полное тело с этой правкой; полную копию тела держите у себя — read-API отдаёт только published.
Единственное частичное поле — status: пустой сохраняет текущий. Также: slug в теле переименовывает статью (занятый слаг → 409); чужая статья → 403, несуществующая → 404.
Сменить статус может и оператор: POST /api/platform/blog/articles/{slug}/status {"status":"published"} (сессия оператора).
Картинки
curl https://llmmart.ru/api/v1/blog/images \
-H "Authorization: Bearer $BLOG_API_KEY" \
-F "file=@cover.png"
# → 201 {"url":"/api/v1/blog/images/blog/<uuid>.png"}PNG, JPEG или WebP до 2 МБ; тип проверяется по содержимому файла, а не по расширению. Возвращённый URL публичный, без авторизации — вставляйте его в body как обычный markdown .
Переменные каталога
Цены и характеристики моделей не вписываются в текст вручную — вместо них плейсхолдер, значение подставляется при рендере из каталога:
{{model:<author>/<слаг модели>:<поле>}}- Поля:
name,id,context,price_input,price_output,price_cache(цены — за 1 млн токенов в рублях, контекст — как «128K»). - Слаг модели может содержать
:(OpenRouter-суффиксы видаfoo:free): полем считается сегмент после последнего:. - В тексте статьи хранится сам плейсхолдер — при делистинге модели значение просто перестаёт выводиться.
- Все плейсхолдеры проверяются при сохранении: несуществующая модель или опечатка →
422с указанием переменной.
Markdown-расширения
Два блочных расширения в body — JSON внутри code fence. Невалидный JSON не ломает рендер: блок показывается как обычный код.
Карточки шагов (нумерация появляется автоматически):
```steps
[{"title": "Получите ключ", "text": "Зарегистрируйтесь и создайте ключ в кабинете"},
{"title": "Выберите модель", "text": "Включите нужные модели в настройках организации"}]
```Выноска:
```callout
{"kind": "info", "text": "Кэширование снижает цену входных токенов в несколько раз"}
```kind — info (бирюзовая «i») или warn (жёлтая «!»). Расширения — только fenced-блоки: :::callout-директивы (pandoc/MDX-стиль) не поддерживаются и уедут в текст как есть. Закрывающий ``` — строго на отдельной строке: фенс, прикрытый в конце строки с JSON, не закрывается и «поглощает» следующий блок. Якоря заголовков генерируются автоматически из текста (github-slugger) — синтаксис {#my-id} не поддерживается. Остальное — обычный GFM: заголовки, списки, таблицы, картинки, ссылки.
Стилистика текста
Статья должна выглядеть написанной редактором, а не сгенерированной. Правила:
- Никаких вводных-маркеров нейротекста: «кроме того», «однако стоит отметить», «важно понимать», «таким образом», «подводя итог», «как видим» — ни одного в статье.
- Никаких клише: «в современном мире», «играет важную роль», «открывает новые возможности», «инновационный», «уникальный», «давайте разберёмся», «не секрет, что».
- Длинное тире
—— не чаще одного на абзац; не заменять им запятые и двоеточия; цепочки «слово — слово — слово» запрещены. - Конструкция «не X, а Y» — максимум один раз на статью. Восклицательные знаки и эмодзи — никогда.
- Предложения: средняя длина 12–18 слов (потолок 25), чередовать короткие с длинными; в инструкциях глагол в начале; каждый абзац кончается фактом, а не обобщением.
- Без канцелярита («осуществлять», «данный», «производить подключение»); конкретика вместо оценок: цифры, названия, версии — вместо «быстро», «удобно», «гибко».
Лимиты и ошибки
| Поле | Лимит |
|---|---|
slug | 1–80 символов: a-z, 0-9, дефис; уникален |
title | ≤ 120 символов |
description | ≤ 300 символов |
intro | ≤ 600 символов |
short | ≤ 5 пунктов |
faq | 4–10 вопросов |
body | ≤ 100 000 символов |
verdict | 2–3 сценария |
Это лимиты API-валидации (нарушение → 422). Редакционные требования частично строже и API не проверяются: faq берём 4–6 (не 4–10), short — 3–5 пунктов, а число сценариев verdict валидатор не считает вовсе — за редакционными нормами следит редактура.
Ошибки валидации — машиночитаемым списком, HTTP 422:
{"errors": [{"field": "faq", "message": "faq: от 4 до 10 вопросов, получено 2"}]}Коды: 401 — нет/неверен/отозван ключ; 403 — чужая статья; 404 — статьи нет; 409 — slug занят; 400 — некорректный JSON. Невалидная статья не сохраняется вовсе — исправьте поля из errors и отправьте снова.
Отправка с первого раза
- Соберите статью по лимитам таблицы; цены — только плейсхолдерами
{{model:…}}из существующих моделей каталога. POST /api/v1/blog/articlesсоstatus: "draft".- При
422исправьте поля из спискаerrorsи повторите; при409смените slug. - Картинки —
POST /api/v1/blog/images, полученные URL — вbody. - Опубликуйте:
PUT /api/v1/blog/articles/{slug}с тем же полным телом и"status":"published"(или статус ставит оператор).