Landing Service API
Сервис принимает текст в простом блочном формате и возвращает ссылку на опубликованную страницу.
- База:
https://lp.somnarium.ru - Авторизация: заголовок
Authorization: Bearer <api-key> - Формат тела:
application/json(кроме загрузки файлов) - Эта страница статична и одинакова для всех клиентов. Если что-то не сходится — верь ей, а не своей памяти.
Машиночитаемые версии: /llms.txt · /docs.md · /openapi.json
Готовый скилл для Claude Code: /skill — положить в .claude/skills/landing-pages/SKILL.md
1. Быстрый старт
curl -X POST https://lp.somnarium.ru/api/v1/sites \
-H "Authorization: Bearer $LP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Отчёт по тренировке",
"content": ":::hero\n# Пётр Иванов\nВольная борьба · 6 августа\n:::\n\n:::text\nОтличное занятие: отработали проходы в ноги.\n:::"
}'
Ответ:
{
"id": "k3f9dl2m8x1q",
"slug": "otchet-po-trenirovke",
"url": "https://lp.somnarium.ru/s/demo/otchet-po-trenirovke",
"status": "published",
"warnings": []
}
Поле url — готовая ссылка, её можно сразу отдавать человеку.
2. Формат контента
Контент — это plain text. Блоки размечаются «заборчиками» :::, внутри блока — обычный Markdown.
:::тип атрибут=значение другой="значение с пробелами"
markdown-содержимое
:::
Правила, на которые можно положиться:
| Правило | Поведение |
|---|---|
| Незнакомый тип блока | Отрисуется как обычный текст, запрос не упадёт |
Забыл закрыть ::: |
Блок закроется сам на следующем открывающем или в конце |
| Текст вне блоков | Станет блоком text |
| Ошибка внутри блока | Блок деградирует до текста, остальная страница не пострадает |
Всё, что не отрисовалось как ожидалось, попадает в массив warnings ответа — но запрос всё равно успешен.
Атрибуты
Пишутся в открывающей строке: key=value, key="значение с пробелами" или просто key (равно true).
Картинки в атрибутах
Где ожидается изображение, принимается три формы:
image=k7f2m9x1— id загруженного ассета (см. раздел 5)image=/i/k7f2m9x1— путьimage=https://example.com/photo.jpg— внешний URL
3. Справочник блоков
hero — шапка страницы
Атрибуты: image, alt, eyebrow, align (left | center | right, по умолчанию center).
:::hero image=k7f2m9x1 eyebrow="Отчёт за 6 августа"
# Пётр Иванов
Вольная борьба · группа 2
:::
text — обычный текст
Атрибуты: align. Внутри — любой markdown: заголовки, списки, ссылки, жирный, курсив.
:::text
## Что получилось
- Уверенный **тайминг** на проходах
- Хорошая работа в партере
:::
stats — числовые показатели
Строки вида - значение | подпись | пояснение. Третья колонка необязательна.
:::stats
- 90 мин | на ковре
- 12 | схваток | из них 8 выиграно
- 85% | посещаемость
:::
cards — карточки сеткой
Секции ## Заголовок + текст под ними. Альтернативно строки - Заголовок | Описание.
:::cards
## Техника
Проходы в ноги стали заметно чище.
## Дисциплина
Не пропустил ни одного занятия за месяц.
:::
gallery — сетка фотографий
По одной картинке на строку: id ассета, URL,  или url | подпись.
:::gallery
k7f2m9x1
k7f2m9x2
https://example.com/photo.jpg | На разминке
:::
image — одна картинка
Атрибуты: src, alt, caption. Если атрибутов нет, ссылка берётся из тела блока.
:::image caption="Финальная схватка"
k7f2m9x1
:::
quote — цитата или отзыв
Атрибуты: author, role, avatar.
:::quote author="тренер Алексей" role="Вольная борьба" avatar=k7f2m9x3
Пётр заметно прибавил в дисциплине за последний месяц.
:::
list — список с галочками
Строки - Пункт | Пояснение. Вторая колонка необязательна.
:::list
- Разминка | 15 минут, полный комплекс
- Отработка проходов
- Схватки
:::
steps — пронумерованные шаги
Секции ## Заголовок + текст, либо строки - Заголовок | Описание. Нумерация проставляется сама.
:::steps
## Разминка
Суставная гимнастика, бег.
## Техника
Проходы в одну и две ноги.
## Схватки
Шесть схваток по две минуты.
:::
progress — шкалы и оценки
Строки - Название | значение | максимум. Максимум необязателен: если его нет и значение ≤ 5, шкала считается пятибалльной, иначе процентной.
:::progress
- Проходы в ноги | 4 | 5
- Защита | 3 | 5
- Посещаемость | 85 | 100
:::
table — таблица
Обычная markdown-таблица. На узком экране получает горизонтальную прокрутку внутри себя.
:::table
| Упражнение | Подходы | Результат |
|---|---|---|
| Приседания | 3×15 | выполнено |
| Мост | 3×30 сек | выполнено |
:::
faq — раскрывающиеся вопросы
Секции ## Вопрос + ответ.
:::faq
## Что взять на следующее занятие?
Форму, борцовки и бутылку воды.
## Когда следующий турнир?
14 сентября, подробности позже.
:::
cta — кнопка действия
Атрибуты: href, label, style (outline для менее заметной кнопки).
:::cta href="https://t.me/coach" label="Написать тренеру"
Остались вопросы?
:::
contacts — строки контактов
Строки - Подпись | Значение | ссылка. Ссылка необязательна.
:::contacts
- Тренер | Алексей Смирнов
- Telegram | @coach | https://t.me/coach
- Зал | ул. Спортивная, 4
:::
badges — теги
Через запятую или по одному на строку.
:::badges
вольная борьба, группа 2, 12 лет
:::
divider — разделитель
:::divider
Итоги месяца
:::
footer — подвал
:::footer
Отчёт сформирован автоматически · club-nika.ru
:::
Синонимы
Эти имена работают как псевдонимы, чтобы формат прощал промахи:
section, paragraph, markdown, md, heading → text ·
kpi, stat, metrics → stats ·
card, features, feature, grid → cards ·
images, photos → gallery ·
img, photo, picture → image ·
testimonial, blockquote → quote ·
checklist, bullets → list ·
timeline, process → steps ·
rating, skills, bars → progress ·
accordion, questions → faq ·
button, action → cta ·
contact → contacts ·
tags, chips → badges ·
hr, separator → divider
4. Методы работы с сайтами
Все требуют Authorization: Bearer <api-key>. Каждый ключ видит только сайты своего клиента.
POST /api/v1/sites — создать
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
title |
string | да | Заголовок страницы и <title> |
content |
string | да | Блочный контент |
slug |
string | нет | Часть URL. По умолчанию строится из title |
description |
string | нет | Описание для превью в мессенджере |
status |
string | нет | published (по умолчанию) или draft |
ttl |
string | нет | Срок жизни, см. раздел 6. По умолчанию бессрочно |
Ответ 201 с полями id, slug, url, warnings.
GET /api/v1/sites — список
Параметры: limit (1–100, по умолчанию 50), offset, status.
curl https://lp.somnarium.ru/api/v1/sites \
-H "Authorization: Bearer $LP_API_KEY"
GET /api/v1/sites/{id|slug} — получить один
Возвращает объект вместе с полем content — исходным текстом блоков.
GET /api/v1/sites/{id|slug}/html — предпросмотр
Возвращает готовую HTML-страницу, не публикуя её. Работает и для черновиков, поэтому вёрстку можно проверить до того, как ссылка уйдёт человеку.
curl https://lp.somnarium.ru/api/v1/sites/otchet/html \
-H "Authorization: Bearer $LP_API_KEY" > preview.html
PATCH /api/v1/sites/{id|slug} — изменить
Принимает те же поля, что и создание; передавать нужно только изменяемые. PUT работает так же.
curl -X PATCH https://lp.somnarium.ru/api/v1/sites/otchet-po-trenirovke \
-H "Authorization: Bearer $LP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": ":::hero\n# Обновлённый отчёт\n:::"}'
DELETE /api/v1/sites/{id|slug} — удалить
curl -X DELETE https://lp.somnarium.ru/api/v1/sites/otchet-po-trenirovke \
-H "Authorization: Bearer $LP_API_KEY"
Удаление безвозвратно; привязанные к странице картинки удаляются вместе с ней.
5. Картинки
Загрузка файла
curl -X POST https://lp.somnarium.ru/api/v1/assets \
-H "Authorization: Bearer $LP_API_KEY" \
-F "file=@photo.jpg" \
-F "ttl=3w"
Загрузка по ссылке
curl -X POST https://lp.somnarium.ru/api/v1/assets \
-H "Authorization: Bearer $LP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/photo.jpg", "ttl": "1y"}'
Ответ содержит id и url. id подставляется в блоки: image=<id>.
Что происходит с файлом при загрузке:
- EXIF стирается — в фотографиях с телефона лежат GPS-координаты места съёмки
- HEIC, PNG, JPEG и прочее приводится к WebP, чтобы открывалось на любом устройстве
- Длинная сторона ужимается до 2000 px
- Одинаковые файлы одного клиента хранятся один раз
Другие методы: GET /api/v1/assets (список), DELETE /api/v1/assets/{id}.
6. Сроки хранения
ttl принимает: 30m, 12h, 30d, 3w, 6M, 1y, forever, page.
Регистр важен только у месяцев: M — месяцы, m — минуты.
Главное правило: картинка без явного ttl живёт столько же, сколько страница, к которой она привязана (-F "site=<slug>"). Логотип, который переиспользуется, заливается один раз с ttl=forever.
# фотка с тренировки — умрёт вместе с отчётом
curl ... -F "file=@training.jpg" -F "site=otchet-6-avgusta"
# логотип клуба — живёт всегда
curl ... -F "file=@logo.png" -F "ttl=forever"
Страница с истёкшим сроком отдаёт 410 и нейтральную заглушку, а не ошибку. Картинка с истёкшим сроком просто исчезает — вёрстка вокруг остаётся целой.
7. Приватность
- Страницы закрыты от индексации (
noindexв мета-теге и в заголовке ответа) - Списка опубликованных страниц наружу нет
- Черновики (
status: draft) отвечают тем же404, что и несуществующие адреса - Ключ даёт доступ только к сайтам своего клиента
Ссылка — это и есть доступ: у кого она есть, тот и видит страницу. Для чувствительного содержимого ставьте ttl.
8. Ошибки
Единый формат:
{ "error": "Сайт \"report-42\" не найден", "status": 404 }
| Код | Когда |
|---|---|
400 |
Некорректное тело запроса, неверный ttl или slug |
401 |
Ключ отсутствует, неизвестен или отозван |
404 |
Объект не найден или принадлежит другому клиенту |
409 |
Запрошенный slug уже занят |
413 |
Файл больше 12 МБ |
9. Полный пример
API=https://lp.somnarium.ru
KEY=$LP_API_KEY
# 1. Логотип клуба — заливаем один раз, храним всегда
LOGO=$(curl -s -X POST $API/api/v1/assets -H "Authorization: Bearer $KEY" \
-F "file=@logo.png" -F "ttl=forever" | jq -r .id)
# 2. Собираем отчёт
curl -s -X POST $API/api/v1/sites \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d @- <<JSON | jq -r .url
{
"title": "Отчёт · Пётр Иванов · 6 августа",
"description": "Итоги занятия по вольной борьбе",
"ttl": "90d",
"content": ":::hero image=$LOGO eyebrow=\"Занятие 6 августа\"\n# Пётр Иванов\nВольная борьба · группа 2\n:::\n\n:::stats\n- 90 мин | на ковре\n- 12 | схваток\n- 85% | посещаемость\n:::\n\n:::progress\n- Проходы в ноги | 4 | 5\n- Защита | 3 | 5\n:::\n\n:::text\n## Что получилось\n- Уверенный **тайминг** на проходах\n- Хорошая работа в партере\n:::\n\n:::quote author=\"тренер Алексей\"\nПётр заметно прибавил в дисциплине.\n:::\n\n:::cta href=\"https://t.me/coach\" label=\"Написать тренеру\"\nОстались вопросы?\n:::"
}
JSON
Скрипт печатает готовую ссылку, которую тренер отправляет родителю.