Справочник

REST API за доставка

Два адреса само за четене обслужват всяка колекция в работното ви пространство. Сайтът ви ги вика с API ключ; редакторите не е нужно да знаят за тях.

Адреси#

Базовият адрес е https://api.console.citronos.com. {collection} е идентификаторът (slug) на колекцията, нейното API име в конзолата. Смяната му променя адреса, затова конзолата предупреждава, преди да го позволи.

GET/v1/content/{collection}

Публикуваните записи на колекция, по една страница. За единична колекция връща самия документ.

GET/v1/content/{collection}/{slugOrId}

Един публикуван запис по slug или по идентификатор.

Удостоверяване#

Изпращайте ключ за доставка в Authorization: Bearer … или в x-api-key. Ключовете се създават в конзолата от Настройки → Разработчици → API ключове. Пълният ключ се показва веднъж; Citron пази само неговия хеш.

КлючЧетеКъде се ползва
ck_pub_…Публикуваното съдържаниеНа сървъра, в edge среда или при build. С ограничение на заявките за всеки ключ.
ck_prev_…Публикуваното съдържание и черновите (status=draft)Само на сървър, който контролирате. Никога не го изпращайте към браузъра.

Липсващ, непознат или отменен ключ връща 401.

curl
# Either header works
curl https://api.console.citronos.com/v1/content/products \
  -H "Authorization: Bearer $CITRON_API_KEY"

curl https://api.console.citronos.com/v1/content/products \
  -H "x-api-key: $CITRON_API_KEY"

Списък със записи#

curl "https://api.console.citronos.com/v1/content/products?locale=bg&populate=related&richtext=html" \
  -H "Authorization: Bearer $CITRON_API_KEY"
Отговор
{
  "data": [
    {
      "id": "cmf3k9x2p0004s601q8w7e2rt",
      "slug": "bio-6",
      "publishedAt": "2026-09-21T09:30:00.000Z",
      "updatedAt": "2026-09-21T09:30:00.000Z",
      "sortOrder": 0,
      "data": {
        "code": "BIO-6",
        "category": "station",
        "inCapacityTable": true,
        "name": "Пречиствателна станция BIO 6",
        "body": "<p>Компактна станция за една къща.</p>",
        "capacity": 6,
        "specRows": [{ "label": "Капацитет", "value": "6 ЕЖ" }],
        "cover": {
          "id": "cmf3k7a1b0001s601m2n3b4v5",
          "url": "https://cdn.example.com/media/bio-6.webp",
          "mimeType": "image/webp",
          "width": 1600,
          "height": 900,
          "alt": "Станция BIO 6",
          "variants": { "thumb": "https://cdn.example.com/media/bio-6-thumb.webp" },
          "focal": { "x": 0.5, "y": 0.4 }
        },
        "related": [
          {
            "id": "cmf3k9x2p0007s601z5x4c3v2",
            "slug": "bio-10",
            "publishedAt": "2026-09-18T12:00:00.000Z",
            "updatedAt": "2026-09-18T12:00:00.000Z",
            "sortOrder": 1,
            "collection": "products",
            "data": {
              "code": "BIO-10",
              "name": "Пречиствателна станция BIO 10",
              "capacity": 10,
              "related": ["cmf3k9x2p0004s601q8w7e2rt"]
            }
          }
        ]
      }
    }
  ],
  "meta": { "total": 2, "page": 1, "pageSize": 20, "pageCount": 1 }
}

Всеки запис има id, slug, publishedAt, updatedAt, sortOrder и data, където са полетата на колекцията. meta съдържа total, page, pageSize и pageCount. Без sort важи подредбата, зададена на колекцията в конзолата.

  • Полетата за изображения и медия идват като файлове от медийната библиотека: id, url, mimeType, width, height, alt и variants (именувани размери, например thumb). Полетата за изображения добавят focal, точката на фокус, зададена от редактора (от 0 до 1).
  • Поле за връзка съдържа идентификатора на свързания запис. Ако го посочите в populate, вместо това съдържа самия запис заедно с collection. Заредените така записи са плитки (собствените им връзки остават идентификатори) и винаги са публикуваните версии.
  • Живите колекции, които четат от външна система, са само за конзолата; API за доставка отговаря с 404 за тях.

Един запис#

curl "https://api.console.citronos.com/v1/content/products/bio-6?locale=en&richtext=json" \
  -H "Authorization: Bearer $CITRON_API_KEY"
Отговор
{
  "data": {
    "id": "cmf3k9x2p0004s601q8w7e2rt",
    "slug": "bio-6",
    "publishedAt": "2026-09-21T09:30:00.000Z",
    "updatedAt": "2026-09-21T09:30:00.000Z",
    "sortOrder": 0,
    "data": {
      "code": "BIO-6",
      "category": "station",
      "name": "Treatment plant BIO 6",
      "body": {
        "type": "doc",
        "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "A compact plant for one house." }] }]
      },
      "capacity": 6,
      "datasheet": {
        "id": "cmf3k7a1b0002s601p9o8i7u6",
        "url": "https://cdn.example.com/media/bio-6.pdf",
        "mimeType": "application/pdf",
        "width": 0,
        "height": 0,
        "alt": "Datasheet",
        "variants": {}
      },
      "related": ["cmf3k9x2p0007s601z5x4c3v2"]
    }
  }
}

Последната част от адреса се сравнява със slug или с идентификатора. Запис, който не съществува или никога не е публикуван, връща 404.

Единични колекции#

Единичната колекция съдържа един документ, например настройките на сайта или началната страница. Адресът на списъка ѝ връща { "data": { … } } с документа вместо списък и 404, докато документът не е публикуван.

curl "https://api.console.citronos.com/v1/content/site-settings?locale=bg" \
  -H "Authorization: Bearer $CITRON_API_KEY"

Параметри на заявката#

ПараметърСтойностиБележки
localeЕзик от работното пространство, например bgПреведените полета идват само на този език. Непознат език връща 400.
filter[field], filter[field][op]Вижте филтриНай-много 10.
sortfield или -fieldЕдно поле. publishedAt, createdAt, updatedAt, sortOrder, slug или поле, което не е обект или списък.
page1, 2, …По подразбиране 1.
pageSizeОт 1 до 100По подразбиране: размерът на списъка, зададен на колекцията.
populateПолета за връзка, разделени със запетаяДо 10 полета от първо ниво.
fieldsКлючове от data, разделени със запетаяid, slug, publishedAt, updatedAt и sortOrder идват винаги.
richtexthtml, json, bothПо подразбиране both.
statusdraftСамо с ключ ck_prev_; иначе 403.
previewТокен за прегледСъщото като заглавката x-citron-preview, вижте преглед на чернови.

Езици и форматиран текст#

Без locale всяко преведено поле е карта с всички ваши езици, а форматираният текст идва и в двата формата. С locale=bg получавате българската стойност, а където тя е празна, стойността на езика по подразбиране.

Отговор без locale (съкратен)
{
  "data": [
    {
      "id": "cmf3k9x2p0004s601q8w7e2rt",
      "slug": "bio-6",
      "publishedAt": "2026-09-21T09:30:00.000Z",
      "updatedAt": "2026-09-21T09:30:00.000Z",
      "sortOrder": 0,
      "data": {
        "code": "BIO-6",
        "name": { "bg": "Пречиствателна станция BIO 6", "en": "Treatment plant BIO 6" },
        "body": {
          "bg": { "html": "<p>Компактна станция.</p>", "json": { "type": "doc", "content": [] } },
          "en": { "html": "<p>A compact plant.</p>", "json": { "type": "doc", "content": [] } }
        }
      }
    }
  ],
  "meta": { "total": 2, "page": 1, "pageSize": 20, "pageCount": 1 }
}

richtext=html връща форматирания текст като HTML низ, richtext=json като документа на редактора (ProseMirror JSON), а both, което е по подразбиране, като { "html", "json" }. По-стари записи може да съдържат обикновен HTML низ, каквото и да поискате.

Филтри#

filter[field]=value означава „равно на“; filter[field][op]=value избира оператор. Можете да филтрирате по id, slug и всяко поле освен обекти, географски координати и JSON. Филтрите се комбинират с „и“. Непознато поле или оператор, който типът на полето не поддържа, връща 400.

Тип полеОператориБележки
Текст, избор, дата, slug, форматиран текст, медия, връзкаeq ne lt lte gt gte contains in notIncontains търси подниз без значение от главни и малки букви. in и notIn приемат стойности, разделени със запетая. Сравнението е текстово, така че датите в ISO формат се подреждат правилно.
Числоeq ne lt lte gt gte in notInСтойностите трябва да са числа.
Да/неeq netrue или false.
Множествен избор, множествена връзка, списъкeq containsИ двата намират записите, чийто списък съдържа стойността.

Преведено поле се сравнява на поискания locale, а без него на езика по подразбиране. Тук няма резервен език: запис без стойност на този език не съвпада.

# -g stops curl from treating [ ] as a glob
curl -g "https://api.console.citronos.com/v1/content/products?locale=en\
&filter[category]=station\
&filter[capacity][gte]=5\
&filter[code][in]=BIO-6,BIO-10\
&sort=-publishedAt&page=1&pageSize=20" \
  -H "Authorization: Bearer $CITRON_API_KEY"

curl тълкува [ и ] като шаблон. Подайте -g или ги кодирайте като %5B и %5D.

Страници#

Страниците се номерират от 1 и съдържат най-много 100 записа. Спрете, когато page стигне meta.pageCount. SDK добавя непрозрачен nextCursor и итератор all() върху същите номера на страници.

TypeScript SDK
import type { ListResult } from '@citron/sdk';

// Page by page with the opaque cursor
let cursor: string | null = null;
do {
  const page: ListResult<Record<string, unknown>> = await citron.collection('products').list({ limit: 100, cursor });
  console.log(page.data.length);
  cursor = page.nextCursor;
} while (cursor);

// Or let the SDK walk every page
for await (const product of citron.collection('products').all({ fields: ['code', 'name'] })) {
  console.log(product.slug);
}

Избор на полета#

fields оставя само изброените ключове от data, така че страниците със списъци стават по-малки. Комбинира се с populate и locale.

curl
# Only these data keys (id, slug, publishedAt, updatedAt, sortOrder always come)
curl "https://api.console.citronos.com/v1/content/products?locale=en&fields=code,name,cover" \
  -H "Authorization: Bearer $CITRON_API_KEY"

Кеширане и ETag#

Публикуваните отговори идват с Cache-Control: public, max-age=60, stale-while-revalidate=300 и ETag. Върнете ETag в If-None-Match и непроменен отговор става празен 304. Отговорите с чернови и за преглед са no-store. За да показвате промените в мига на публикуване, кеширайте по-дълго при себе си и обновявайте с уебкуки.

curl
curl -i "https://api.console.citronos.com/v1/content/products?locale=en" \
  -H "Authorization: Bearer $CITRON_API_KEY" \
  -H 'If-None-Match: "3f1c9e0a8b7d6c5e4f3a2b1c0d9e8f7a6b5c4d3e"'
# HTTP/1.1 304 Not Modified   (when nothing changed)

Преглед на чернови#

Задайте на колекцията адрес за преглед в конзолата с някои от заместителите {slug}, {id}, {locale}, {token} и {collection}, например https://example.com/{locale}/products/{slug}?preview={token}. Отвори преглед на запис отваря този адрес с подписан токен (cpt1.…). Ако шаблонът няма {token}, токенът се добавя като ?preview=.

Страницата ви връща токена в заглавката x-citron-preview (или в ?preview=) заедно с обичайния си ключ ck_pub_. Тогава API връща черновите само на тази колекция, без кеш. Токените изтичат след 30 минути по подразбиране (най-много 24 часа) и работят само за вашето работно пространство и за колекцията, за която са издадени.

Заглавка X-Citron-Preview в отговораЗначение
draftТокенът е валиден; върнати са черновите.
invalidТокенът е повреден, изтекъл или от друго работно пространство; върнато е публикуваното съдържание.
other-collectionТокенът е издаден за друга колекция; върнато е публикуваното съдържание.

Лош токен никога не води до грешка: отговорът е точно публикуваният, така че стара връзка за преглед просто показва живата страница.

# The token arrives in your preview URL: https://example.com/products/bio-6?preview=cpt1.…
curl -i "https://api.console.citronos.com/v1/content/products/bio-6?locale=en" \
  -H "Authorization: Bearer $CITRON_API_KEY" \
  -H "x-citron-preview: cpt1.eyJvIjoi….k1Qm…"
# X-Citron-Preview: draft
# Cache-Control: no-store

Ключове за преглед#

Ключ ck_prev_ със status=draft (в SDK: preview: true) чете черновите на всички колекции. Ползвайте го за преглед при build на сървър, който контролирате, никога в браузъра.

TypeScript SDK
import { createClient } from '@citron/sdk';

// Server only: a ck_prev_ key reads every draft of the workspace
const drafts = createClient({
  baseUrl: 'https://api.console.citronos.com',
  apiKey: process.env.CITRON_PREVIEW_KEY, // ck_prev_…
  preview: true, // sends status=draft
});

const draft = await drafts.collection('products').find('bio-6');
console.log(draft?.updatedAt);

Ограничения#

Всеки ключ може да прави по 300 заявки в минута по подразбиране; REST и GraphQL делят този лимит. Как изглежда отговор 429, вижте в грешки и ограничения.

Справочникът описва само публичното API и следва договора му за съвместимост: публичните отговори само получават нови полета. Описанието на тези адреси във формат OpenAPI е на /developers/openapi.json.