Referință
API REST de livrare
Două rute doar pentru citire servesc fiecare colecție din spațiul dumneavoastră de lucru. Site-ul le apelează cu o cheie API; editorii nu trebuie să știe că există.
Endpointuri#
URL-ul de bază este https://api.console.citronos.com. {collection} este slug-ul colecției, adică numele ei API din consolă. Redenumirea slug-ului schimbă calea, de aceea consola vă avertizează înainte de a vă permite acest lucru.
GET/v1/content/{collection}
Înregistrările publicate ale unei colecții, câte o pagină odată. Pentru o colecție singleton răspunde cu documentul însuși.
GET/v1/content/{collection}/{slugOrId}
O singură înregistrare publicată, după slug sau după id.
Autentificare#
Trimiteți o cheie de livrare în Authorization: Bearer … sau în x-api-key. Creați cheile în consolă, în Setări → Dezvoltatori → Chei API. Cheia completă este afișată o singură dată; Citron păstrează doar hash-ul ei.
| Cheie | Citește | Unde se folosește |
|---|---|---|
ck_pub_… | Conținutul publicat | Pe server, la edge sau la build. Cu limită de cereri pentru fiecare cheie. |
ck_prev_… | Conținutul publicat și ciornele (status=draft) | Doar pe un server pe care îl controlați. Nu o trimiteți niciodată în browser. |
O cheie lipsă, necunoscută sau revocată primește răspunsul 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"Listarea înregistrărilor#
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 }
}Fiecare înregistrare are id, slug, publishedAt, updatedAt, sortOrder și data, care conține câmpurile colecției. meta conține total, page, pageSize și pageCount. Fără sort se aplică ordinea stabilită pentru colecție în consolă.
- Câmpurile de imagine și media sosesc ca fișiere media:
id,url,mimeType,width,height,altșivariants(variante denumite, de exempluthumb). Câmpurile de imagine adaugăfocal, punctul de focalizare ales de editor pentru decupare, între 0 și 1. - Un câmp de relație conține id-ul înregistrării asociate. Dacă îl indicați în
populate, conține în schimb înregistrarea însăși, împreună cu slug-ul eicollection. Înregistrările încărcate astfel sunt superficiale (propriile relații rămân id-uri) și sunt întotdeauna versiunile publicate. - Colecțiile live, care citesc dintr-un sistem extern, sunt disponibile doar în consolă; API-ul de livrare răspunde cu
404pentru ele.
Obținerea unei înregistrări#
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"]
}
}
}Ultimul segment al căii se compară cu slug-ul sau cu id-ul. O înregistrare care nu există sau care nu a fost publicată niciodată primește răspunsul 404.
Colecții singleton#
O colecție singleton conține un singur document, de exemplu setările site-ului sau pagina principală. URL-ul ei de listare răspunde cu { "data": { … } }, adică documentul în locul unei liste, și cu 404 până când documentul este publicat.
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);Parametri de interogare#
| Parametru | Valori | Observații |
|---|---|---|
locale | O limbă a spațiului de lucru, de exemplu bg | Câmpurile traduse sunt returnate doar în acea limbă. O limbă necunoscută primește răspunsul 400. |
filter[field], filter[field][op] | Consultați filtrele | Cel mult 10. |
sort | field sau -field | Un singur câmp. publishedAt, createdAt, updatedAt, sortOrder, slug sau orice câmp care nu este obiect ori listă. |
page | 1, 2, … | Implicit 1. |
pageSize | Între 1 și 100 | Implicit: dimensiunea listei stabilită pentru colecție. |
populate | Chei ale câmpurilor de relație, separate prin virgulă | Până la 10 câmpuri de relație de pe primul nivel. |
fields | Chei din data, separate prin virgulă | id, slug, publishedAt, updatedAt și sortOrder sunt returnate întotdeauna. |
richtext | html, json, both | Implicit both. |
status | draft | Doar cu o cheie ck_prev_; altfel 403. |
preview | Un token de previzualizare | Echivalent cu antetul x-citron-preview; consultați previzualizarea ciornelor. |
Limbi și text formatat#
Fără locale, fiecare câmp tradus este un obiect cu toate limbile dumneavoastră, iar textul formatat vine în ambele formate. Cu locale=bg primiți valoarea în bulgară; acolo unde aceasta lipsește, valoarea din limba implicită.
{
"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 returnează textul formatat ca șir HTML, richtext=json ca document al editorului (ProseMirror JSON), iar both, valoarea implicită, ca { "html", "json" }. Înregistrările mai vechi pot conține un simplu șir HTML, indiferent de ce cereți.
Filtre#
filter[field]=value înseamnă „egal cu”; filter[field][op]=value alege un operator. Puteți filtra după id, slug și orice câmp, cu excepția câmpurilor de tip obiect, geo și JSON. Filtrele se combină prin ȘI. Un câmp necunoscut sau un operator pe care tipul câmpului nu îl acceptă primește răspunsul 400.
| Tip de câmp | Operatori | Observații |
|---|---|---|
| Text, selecție, dată, slug, text formatat, media, relație | eq ne lt lte gt gte contains in notIn | contains caută un subșir fără a ține cont de majuscule. in și notIn primesc valori separate prin virgulă. Comparația este textuală, deci datele ISO se ordonează corect. |
| Număr | eq ne lt lte gt gte in notIn | Valorile trebuie să fie numere. |
| Boolean | eq ne | true sau false. |
| Selecție multiplă, relație multiplă, listă | eq contains | Ambii găsesc înregistrările a căror listă conține valoarea. |
Un câmp tradus se compară în limba cerută prin locale sau, în lipsa ei, în limba implicită. Aici nu există limbă de rezervă: o înregistrare fără valoare în acea limbă nu se potrivește.
# -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 interpretează [ și ] ca model glob. Folosiți -g sau codificați-le ca %5B și %5D.
Paginare#
Paginile sunt numerotate de la 1 și conțin cel mult 100 de înregistrări. Opriți-vă când page ajunge la meta.pageCount. SDK-ul adaugă un nextCursor opac și un iterator all() peste aceleași numere de pagină.
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);
}Alegerea câmpurilor#
fields păstrează doar cheile din data pe care le enumerați, ceea ce micșorează paginile de listă. Se poate combina cu populate și 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"Cache și ETag#
Răspunsurile publicate includ Cache-Control: public, max-age=60, stale-while-revalidate=300 și un ETag. Trimiteți ETag-ul înapoi în If-None-Match, iar un răspuns neschimbat va fi un 304 gol. Răspunsurile cu ciorne și cele de previzualizare sunt no-store. Pentru ca modificările să apară imediat ce un editor publică, păstrați cache-ul mai mult timp la dumneavoastră și revalidați cu webhook-uri.
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)Previzualizarea ciornelor#
Linkuri de previzualizare semnate#
Setați în consolă un URL de previzualizare pentru colecție, cu oricare dintre substituenții {slug}, {id}, {locale}, {token} și {collection}, de exemplu https://example.com/{locale}/products/{slug}?preview={token}. Deschide previzualizarea pe o înregistrare deschide acea adresă cu un token semnat (cpt1.…). Dacă șablonul nu conține {token}, tokenul este adăugat ca ?preview=.
Pagina dumneavoastră trimite tokenul înapoi în antetul x-citron-preview (sau în ?preview=), împreună cu cheia obișnuită ck_pub_. API-ul servește atunci ciornele doar ale acelei colecții, fără cache. Tokenurile expiră implicit după 30 de minute (cel mult 24 de ore) și funcționează doar pentru spațiul dumneavoastră de lucru și pentru colecția pentru care au fost emise.
Antetul de răspuns X-Citron-Preview | Semnificație |
|---|---|
draft | Tokenul este valid; au fost servite ciornele. |
invalid | Tokenul este malformat, expirat sau provine din alt spațiu de lucru; a fost servit conținutul publicat. |
other-collection | Tokenul a fost emis pentru altă colecție; a fost servit conținutul publicat. |
Un token greșit nu provoacă niciodată o eroare: răspunsul este exact cel publicat, așa că un link vechi de previzualizare afișează pur și simplu pagina live.
# 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) } } },
);
}Chei de previzualizare#
O cheie ck_prev_ cu status=draft (în SDK, preview: true) citește ciornele tuturor colecțiilor. Folosiți-o pentru previzualizări la build, pe un server pe care îl controlați, niciodată în browser.
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);Limite#
Implicit, fiecare cheie poate face 300 de cereri pe minut; REST și GraphQL împart această limită. Consultați erori și limite de rată pentru a vedea cum arată un răspuns 429.
Această referință acoperă doar API-ul public și respectă contractul de compatibilitate al API-ului: răspunsurile publice pot doar să primească câmpuri noi. Descrierea OpenAPI a acestor rute se află la /developers/openapi.json.