rercon · api · v1

REST API

Programmatic-доступ к данным магазина и автоматизация публикаций: заказы, товары, покупатели, блог, загрузка изображений. Bearer-токены, cursor-пагинация. База URL — домен вашего магазина:

https://<your-shop>.rercon.tech/api/v1

На своём домене API живёт там же: https://mystore.com/api/v1.

Быстрый старт

  1. Создайте токен в админке магазина: /admin/settings/api-tokens. Для чтения хватит scope read; для публикаций в блог и загрузки картинок нужен write.
  2. Токен показывается один раз и выглядит как rcn_a1b2c3… — сохраните его в секретах вашей интеграции.
  3. Проверьте, что токен работает:
curl -H "Authorization: Bearer rcn_..." \
     https://<your-shop>.rercon.tech/api/v1/me

В ответе придёт ваш магазин и метаданные токена — значит всё настроено. Дальше любой endpoint из таблицы ниже.

Аутентификация

Каждый запрос несёт токен в заголовке. Поддерживаются два равнозначных варианта:

ЗаголовокПример
AuthorizationBearer rcn_a1b2c3…
X-Rercon-Tokenrcn_a1b2c3…

Права токена задаются при создании и определяют доступное:

ScopeЧто разрешеноПерсональные данные
readВсе GET-endpointsМаскируются: email → a***@…, tg_username и ФИО скрыты
writeВсё из read + POST (посты блога, загрузка картинок)Полные данные покупателей

Write-токены — только для доверенных server-side интеграций. Никогда не вшивайте токен в браузерный код или мобильное приложение: его увидит любой пользователь.

Доступ и лимиты

ОграничениеЗначение
Тариф«Автопилот» и «Флагман» (на «Старте» запросы вернут 403)
Запросы60 в минуту на токен → 429 при превышении
Загрузки картинок30 в минуту, файл до 20 MB
Размер тела запросадо 2 MB (тело статьи — до 200 000 символов HTML)

Остаток лимита приходит в заголовках каждого ответа:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1716210000   # unix-время сброса окна

При 429 подождите до времени из X-RateLimit-Reset и повторите запрос. CORS открыт (Access-Control-Allow-Origin: *) — авторизация токеном, не cookie. Версия API — в заголовке X-Rercon-API-Version: v1.

Формат ответов и пагинация

Все ответы — один конверт. Успех: data заполнен, error: null. Ошибка: data: null и объект error с машинным code и человекочитаемым message.

{
  "data": [ ... ],
  "pagination": { "limit": 50, "next_cursor": 12345, "has_more": true },
  "error": null
}

Списки отдаются страницами от новых к старым (по убыванию id). Чтобы получить следующую страницу, передайте next_cursor из ответа параметром ?cursor=:

GET /api/v1/orders?limit=50            # первая страница
GET /api/v1/orders?limit=50&cursor=12345   # следующая
# повторяйте, пока has_more == true (next_cursor == null в конце)

Все даты — ISO 8601 в UTC (2026-06-12T10:00:00.000Z). Все суммы — в копейках (total_kopecks: 990000 = 9 900 ₽): целые числа, без проблем с плавающей точкой.

Ошибки

HTTPcodeКогда
400invalid_query / invalid_bodyНевалидные параметры или тело запроса; в message — какое поле
400invalid_published_atДата отложенной публикации не в будущем / нечитаема
400invalid_categorycategory_id не из вашего магазина
401unauthorizedТокен не передан, не найден или истёк
403unauthorizedТариф без API или у токена не хватает scope
409slug_takenПост с таким slug уже существует
413file_too_large / storage_quota_exceededФайл больше 20 MB или закончилась квота хранилища тарифа
415unsupported_typeФайл не jpeg/png/webp/avif/gif (тип определяется по содержимому)
429rate_limitedПревышен лимит запросов или загрузок
5xxНаша сторона; безопасно повторить с экспоненциальной задержкой
{
  "data": null,
  "pagination": null,
  "error": { "code": "slug_taken", "message": "a post with this slug already exists" }
}

Все endpoints

МетодПутьScopeНазначение
GET/api/v1/mereadПроверка токена, инфо о магазине
GET/api/v1/ordersreadЗаказы
GET/api/v1/productsreadКаталог товаров
GET/api/v1/customersreadПокупатели
GET/api/v1/blog/postsreadПосты блога (вкл. черновики)
POST/api/v1/blog/postswriteСоздать пост: черновик / сразу / по расписанию
POST/api/v1/uploads/imageswriteЗагрузить картинку, получить CDN-URL

GET/api/v1/me

Информация о магазине и токене. Идеален как health-check интеграции: дешёвый, без побочных эффектов.

curl -H "Authorization: Bearer rcn_..." \
     https://<your-shop>.rercon.tech/api/v1/me

ответ · 200

{
  "data": {
    "tenant": { "id": 7, "slug": "alice", "name": "Alice School", "plan": "pro", "status": "active" },
    "token": {
      "name": "n8n integration", "prefix": "rcn_a1b2",
      "scope": "write", "last_used_at": "2026-06-11T18:02:11.000Z", "expires_at": null
    }
  },
  "error": null
}

GET/api/v1/orders

ПараметрТипDefaultОписание
limitinteger50Размер страницы, максимум 200
cursorintegerid заказа из next_cursor предыдущей страницы
statusstringdraft · pending_payment · paid · refunded · cancelled
curl -H "Authorization: Bearer rcn_..." \
     "https://<your-shop>.rercon.tech/api/v1/orders?limit=50&status=paid"

ответ · 200

{
  "data": [{
    "id": 1042,
    "total_kopecks": 990000,
    "status": "paid",
    "email": "a***@gmail.com",          // полный — только для scope write
    "customer_name": null,               // только для scope write
    "created_at": "2026-06-10T09:31:00.000Z",
    "paid_at": "2026-06-10T09:33:12.000Z",
    "refunded_at": null,
    "utm": { "source": "tg_channel", "campaign": "june_launch" },
    "payment_provider": "prodamus"
  }],
  "pagination": { "limit": 50, "next_cursor": 1042, "has_more": true },
  "error": null
}

GET/api/v1/products

ПараметрТипDefaultОписание
limitinteger50Размер страницы, максимум 200
cursorintegerid товара из next_cursor
activebooleantrue — только активные, false — только выключенные
curl -H "Authorization: Bearer rcn_..." \
     "https://<your-shop>.rercon.tech/api/v1/products?active=true"

ответ · 200

{
  "data": [{
    "id": 3, "slug": "python-course", "name": "Курс по Python",
    "description": "…", "cover_url": "https://cdn.rercon.tech/i/…",
    "is_active": true, "is_featured": false, "is_listed": true,
    "delivery_mode": "course", "scope": "tenant",
    "created_at": "2026-03-02T12:00:00.000Z", "updated_at": "2026-06-01T08:15:00.000Z"
  }],
  "pagination": { "limit": 50, "next_cursor": null, "has_more": false },
  "error": null
}

id товаров из этого списка подставляются в recommended_product_ids при создании поста блога — блок рекламы ваших товаров под статьёй.

GET/api/v1/customers

ПараметрТипDefaultОписание
limitinteger50Размер страницы, максимум 200
cursorintegerid покупателя из next_cursor
opted_inbooleanФильтр по согласию на email-рассылки
curl -H "Authorization: Bearer rcn_..." \
     "https://<your-shop>.rercon.tech/api/v1/customers?opted_in=true"

ответ · 200

{
  "data": [{
    "id": 215,
    "email": "i***@yandex.ru",          // полный — только для scope write
    "created_at": "2026-05-20T14:00:00.000Z",
    "last_login_at": "2026-06-11T20:44:00.000Z",
    "bonus_balance": 1500,
    "email_opt_in": true,
    "email_opted_in_at": "2026-05-20T14:01:00.000Z",
    "tg_username": null,                 // только для scope write
    "tg_linked_at": "2026-05-21T10:00:00.000Z"
  }],
  "pagination": { "limit": 50, "next_cursor": 215, "has_more": true },
  "error": null
}

GET/api/v1/blog/posts

Все посты блога, включая черновики и запланированные — удобно для синхронизации с внешней CMS и проверки «не публиковали ли мы уже такое».

ПараметрТипDefaultОписание
limitinteger50Размер страницы, максимум 200
cursorintegerid поста из next_cursor
statusstringdraft · scheduled · published
curl -H "Authorization: Bearer rcn_..." \
     "https://<your-shop>.rercon.tech/api/v1/blog/posts?status=published"

ответ · 200

{
  "data": [{
    "id": 18, "slug": "kak-vybrat-kurs", "title": "Как выбрать курс",
    "excerpt": "Короткий анонс", "cover_url": "https://cdn.rercon.tech/i/…",
    "status": "published", "published_at": "2026-06-10T07:00:00.000Z",
    "author_name": "Артём", "category_id": 2,
    "url": "/blog/kak-vybrat-kurs",
    "created_at": "2026-06-09T16:20:00.000Z", "updated_at": "2026-06-10T07:00:00.000Z"
  }],
  "pagination": { "limit": 50, "next_cursor": 18, "has_more": false },
  "error": null
}

POST/api/v1/blog/posts

Создать пост блога — ядро автоматизации публикаций. Тело можно слать как HTML, как Markdown (body_format: "markdown"), или целым .md-документом в поле markdown — для AI-генерации это самый удобный путь: модель пишет markdown с frontmatter, вы шлёте его как есть. Категорию можно указывать по имени (category) — если такой ещё нет, она создастся сама. Управление судьбой поста — поле status:

statusЧто произойдётpublished_at
draftПост ляжет в черновики — на проверку перед публикацией (default)игнорируется
publishСразу на сайт. С датой в прошлом — публикация «задним числом»опционально
scheduleОтложенная публикация: сам появится на сайте в назначенное время, cron не нуженобязательно, в будущем

Поля тела запроса:

ПолеТипОбяз.Описание
titlestring ≤300да*Заголовок (*или придёт из frontmatter, если передан markdown)
bodystring ≤200kнетТело статьи. По умолчанию HTML (санитайзится как в редакторе). Если body_format=markdown — трактуется как Markdown и конвертится
body_formatenumнетhtml (default) · markdown. markdown → безопасный HTML тем же санитайзером
markdownstring ≤200kнетЦелый .md-документ (frontmatter + тело) вместо отдельных полей. Распарсится в title/slug/excerpt/seo/category/author/body; явные поля приоритетнее frontmatter
slugstring ≤160нетАдрес /blog/<slug> (латиница, цифры, дефис). Не указан — сгенерируется из title с транслитерацией; конфликт решится суффиксом -2
excerptstring ≤1000нетАнонс в карточке и description по умолчанию
cover_urlstringнетОбложка (URL от /uploads/images); только http(s) или относительный путь
statusenumнетdraft · publish · schedule (default draft)
published_atISO 8601см. вышеДата публикации, с таймзоной: 2026-07-01T10:00:00+03:00
author_namestring ≤200нетАвтор под статьёй (author — алиас)
category_idintegerнетКатегория блога по id (из админки)
categorystring ≤200нетКатегория по ИМЕНИ. Если такой ещё нет — создастся автоматически (удобно для AI: не нужно знать id)
seo_titlestring ≤300нетПереопределяет <title> (default — title)
seo_descriptionstring ≤1000нетMeta description (default — excerpt)
og_image_urlstringнетКартинка для соцсетей (default — обложка)
recommended_product_idsint[] ≤12нетid товаров для блока рекламы под статьёй
show_view_countbooleanнетПубличный счётчик просмотров (default true)
curl -X POST \
     -H "Authorization: Bearer rcn_..." \
     -H "Content-Type: application/json" \
     -d '{
       "title": "Как выбрать курс",
       "body": "<p>Текст статьи…</p>",
       "excerpt": "Разбираем критерии выбора онлайн-курса.",
       "cover_url": "https://cdn.rercon.tech/i/abc123.webp",
       "status": "schedule",
       "published_at": "2026-07-01T10:00:00+03:00"
     }' \
     https://<your-shop>.rercon.tech/api/v1/blog/posts

ответ · 201

{
  "data": {
    "id": 19,
    "slug": "kak-vybrat-kurs",
    "url": "/blog/kak-vybrat-kurs",
    "status": "scheduled",
    "published_at": "2026-07-01T07:00:00.000Z",
    "created_at": "2026-06-12T09:00:00.000Z"
  },
  "pagination": null,
  "error": null
}

Вариант для AI-генерации — целый .md-файл одним полем markdown (frontmatter + тело). Заголовок, slug, excerpt, SEO, категория и автор берутся из frontmatter:

curl -X POST \
     -H "Authorization: Bearer rcn_..." \
     -H "Content-Type: application/json" \
     -d '{
       "status": "draft",
       "markdown": "---\ntitle: Как выбрать курс\nslug: kak-vybrat-kurs\nexcerpt: Разбираем критерии выбора.\ncategory: Гайды\nauthor: Артём\n---\n\n## С чего начать\n\nТекст в **markdown**. Категория «Гайды» создастся, если её ещё нет."
     }' \
     https://<your-shop>.rercon.tech/api/v1/blog/posts

Эквивалент без markdown: те же поля по отдельности + "body_format": "markdown" и body с markdown-телом. h1 в теле понижается до h2 — заголовок статьи живёт в title.

Частые ошибки: 409 slug_taken — передан занятый slug (уберите поле, и адрес сгенерируется свободный); 400 invalid_published_at — для schedule дата обязана быть в будущем; 400 invalid_category — передан category_id не из вашего магазина (а вот category по имени просто создаётся).

POST/api/v1/uploads/images

Загрузка картинки на CDN — multipart/form-data с одним полем file. Вернувшийся url подставляется в cover_url, og_image_url или в <img src=…> внутри тела поста.

ТребованиеЗначение
Форматыjpeg, png, webp, avif, gif (тип проверяется по байтам файла, svg не принимается)
Размердо 20 MB; заголовок Content-Length обязателен
Квотаобъём считается в квоту хранилища тарифа (как загрузки из админки)
Частотадо 30 загрузок в минуту
curl -X POST -H "Authorization: Bearer rcn_..." \
     -F "file=@cover.webp" \
     https://<your-shop>.rercon.tech/api/v1/uploads/images

ответ · 201

{
  "data": {
    "url": "https://cdn.rercon.tech/i/abc123.webp",
    "thumb_url": "https://cdn.rercon.tech/i/abc123_t.webp",
    "slug": "abc123",
    "size_bytes": 184220,
    "width": 1600,
    "height": 900
  },
  "pagination": null,
  "error": null
}

Сценарий: полная автопубликация статьи

Связка двух endpoint'ов — генератор контента сам выкладывает статью с обложкой по расписанию. Один write-токен, два запроса:

#!/usr/bin/env bash
TOKEN="rcn_..."
SHOP="https://<your-shop>.rercon.tech"

# 1. Загружаем обложку, забираем CDN-URL из ответа
COVER=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" \
  -F "file=@cover.webp" "$SHOP/api/v1/uploads/images" \
  | jq -r '.data.url')

# 2. Создаём пост: в черновики на проверку…
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"title\": \"Заголовок статьи\",
    \"body\": \"<p>Текст…</p>\",
    \"cover_url\": \"$COVER\",
    \"status\": \"draft\"
  }" "$SHOP/api/v1/blog/posts"

# …или сразу в публикацию: "status": "publish"
# …или по расписанию:      "status": "schedule",
#                          "published_at": "2026-07-01T10:00:00+03:00"

Паттерн «на проверку»: автоматика постит со status: draft, вы просматриваете в админке (/admin/blog) и публикуете в один клик. Паттерн «полный автопилот»: schedule с разнесёнными датами — контент-план на месяц одним скриптом.

Рекомендации по интеграции

  • Храните токен в секретах (env-переменная, vault) — не в коде и не в репозитории. Утёк — отзовите в админке и создайте новый.
  • Для чтения создавайте отдельный read-токен: меньше прав — меньше последствий при утечке.
  • Обрабатывайте 429: подождите до X-RateLimit-Reset и повторите. На 5xx — повтор с экспоненциальной задержкой.
  • Идемпотентность публикаций: задавайте slug явно — при повторном запуске скрипта дубль вернёт 409 вместо второго поста.
  • Пагинация: останавливайтесь по has_more: false, а не по пустой странице.

Токены создаются в админке магазина: /admin/settings/api-tokens.

v1 · 2026-06-12