Справочник
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.
# 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"const { data, meta, nextCursor } = await citron.collection('products').list({
locale: 'bg',
populate: ['related'],
richtext: 'html',
});
for (const product of data) console.log(product.slug, product.data.name);
console.log(`page ${meta.page} of ${meta.pageCount}`, nextCursor);{
"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"// Throws CitronError (status 404) when there is no such published entry
const product = await citron.collection('products').get('bio-6', { locale: 'en', richtext: 'json' });
// Resolves null instead of throwing on 404
const maybe = await citron.collection('products').find('bio-6', { locale: 'en' });
console.log(product.id, maybe?.slug);{
"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"const settings = await citron.collection('site-settings').single({ locale: 'bg' });
console.log(settings.data);Параметри на заявката#
| Параметър | Стойности | Бележки |
|---|---|---|
locale | Език от работното пространство, например bg | Преведените полета идват само на този език. Непознат език връща 400. |
filter[field], filter[field][op] | Вижте филтри | Най-много 10. |
sort | field или -field | Едно поле. publishedAt, createdAt, updatedAt, sortOrder, slug или поле, което не е обект или списък. |
page | 1, 2, … | По подразбиране 1. |
pageSize | От 1 до 100 | По подразбиране: размерът на списъка, зададен на колекцията. |
populate | Полета за връзка, разделени със запетая | До 10 полета от първо ниво. |
fields | Ключове от data, разделени със запетая | id, slug, publishedAt, updatedAt и sortOrder идват винаги. |
richtext | html, json, both | По подразбиране both. |
status | draft | Само с ключ ck_prev_; иначе 403. |
preview | Токен за преглед | Същото като заглавката x-citron-preview, вижте преглед на чернови. |
Езици и форматиран текст#
Без locale всяко преведено поле е карта с всички ваши езици, а форматираният текст идва и в двата формата. С locale=bg получавате българската стойност, а където тя е празна, стойността на езика по подразбиране.
{
"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 notIn | contains търси подниз без значение от главни и малки букви. in и notIn приемат стойности, разделени със запетая. Сравнението е текстово, така че датите в ISO формат се подреждат правилно. |
| Число | eq ne lt lte gt gte in notIn | Стойностите трябва да са числа. |
| Да/не | eq ne | true или 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"const { data, meta } = await citron.collection('products').list({
locale: 'en',
filter: {
category: 'station', // filter[category]=station
capacity: { gte: 5 }, // filter[capacity][gte]=5
code: { in: ['BIO-6', 'BIO-10'] }, // filter[code][in]=BIO-6,BIO-10
},
sort: '-publishedAt', // or { field: 'publishedAt', order: 'desc' }
page: 1,
limit: 20, // sent as pageSize
});
console.log(meta.total, data.length);curl тълкува [ и ] като шаблон. Подайте -g или ги кодирайте като %5B и %5D.
Страници#
Страниците се номерират от 1 и съдържат най-много 100 записа. Спрете, когато page стигне meta.pageCount. SDK добавя непрозрачен nextCursor и итератор all() върху същите номера на страници.
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.
# 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 -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-storeimport { citronTags } from '@citron/sdk/next';
/** previewToken: the ?preview= value the console put in your preview URL. */
export async function getProduct(slug: string, previewToken?: string) {
return citron.collection('products').find(
slug,
previewToken
? { headers: { 'x-citron-preview': previewToken }, fetchOptions: { cache: 'no-store' } }
: { fetchOptions: { next: { revalidate: 3600, tags: citronTags('products', slug) } } },
);
}Ключове за преглед#
Ключ ck_prev_ със status=draft (в SDK: preview: true) чете черновите на всички колекции. Ползвайте го за преглед при build на сървър, който контролирате, никога в браузъра.
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.