Delivery API
Public and cached at the edge; read-only apart from form submissions. Base URL: https://cdn.blokcms.app/v1/<spaceId>.
Authentication
- Public tokens (
blok_pub_…): published content. SendAuthorization: Bearer …or?token=…(avoids CORS preflights). - Preview tokens (
blok_prv_…): also drafts (draft=1) and release previews (release=rel_…). Header only; keep them server-side. - Tokens can be restricted to origins. Requests are rate limited per token (1,200 per minute).
Endpoints
| GET | Returns |
|---|---|
/status | Space id, cache version (cv), locales. |
/entries/by-path/<path> | One entry by its (localized) path, e.g. /entries/by-path/blog/hello. |
/entries/<id> | One entry by id. |
/entries | A page of entries (see list parameters). |
/tree | Navigation tree: under, depth, locale. |
/lists/<key> | An option list (value and localized label). |
/globals/<key> | The single entry of a global (site settings, navigation, footer, …). Takes the common parameters. |
/menus/<key> | A navigation menu with links resolved for locale. |
/menus | Several menus at once: keys (comma-separated; all when omitted), locale. |
/sitemap | Published, indexable entries with their path in every locale: locale, types (comma-separated). |
/forms/<key> | A form's fields, to render it. |
/search?q=… | Published entries ranked by meaning (spaces with AI switched on): locale, type, limit (up to 20). |
Common parameters
locale | Language. Missing translations fall back along the locale's fallback chain, then the default. |
include | Comma-separated reference fields to embed (included.entries). |
fields | Only return these root fields. |
cv | Pin the cache version (from a webhook or /status); such responses are immutable. |
draft=1, release | Preview tokens only. Never cached. |
List parameters
type | Content type key. |
under | Folder path, e.g. /blog. |
tags | Comma-separated; all must match. |
search | Full-text search. |
sort | publishedAt, firstPublishedAt, name, path, position or fields.<key>; prefix - for descending; comma-separated. Default -publishedAt. |
filter[field][op] | Operators eq ne in nin contains gt gte lt lte exists; filter[featured]=true means eq. Up to 20. |
limit, cursor | Page size and the nextCursor of the previous page. |
Globals
A type with the kind Global has exactly one entry, outside the page tree: it never appears in /entries, /tree, /sitemap or by path. Use it for what every page shares.
const { entry, included } = await blok.getGlobal("settings");
const { siteName, footerText } = entry.content.fields;
Collections
A collection is a content type managed as a table in the admin (products, blog posts, companies, team members), with bulk publishing and CSV import and export. Its items are ordinary entries, so you read them with /entries: filter, sort and page as usual. Collections whose items aren't pages (switched off in the collection's settings) are left out of /tree and /sitemap.
const { items } = await blok.listEntries({ type: "company", sort: "name", limit: 100 });
Menus
Menus (edited under Site in the admin) are trees of up to three levels. Each item comes with a ready-to-use href (page path in the requested language, URL, mailto: or file URL), a target and its children. Items linking to pages that aren't published are left out, and so are headings left without children; deleting a page takes it out of the menus for good. Saving a menu publishes it and sends a menu.updated webhook.
const { menus } = await blok.getMenus({ keys: ["main", "footer"], locale: "nl" });
menus.main.items.map((item) => ({ ...item, active: isMenuItemActive(item, pathname) }));
SEO and sitemaps
Give a type a field of the custom kind seo (title, description, sharing image, noindex). /sitemap leaves out entries whose SEO field has noindex, and sitemapXml() in @blokcms/client turns the result into sitemap.xml with hreflang alternates.
const { items } = await blok.getSitemap({ types: ["page", "post"] });
return new Response(sitemapXml(items, "https://www.example.com"), { headers: { "Content-Type": "application/xml" } });
Forms
POST /forms/<key>/submissions with a public token accepts JSON or regular form data (application/x-www-form-urlencoded, multipart/form-data; files are ignored). Besides the form's fields:
cf-turnstile-response | Turnstile token, required when the form has a Turnstile secret (the widget adds it to the form for you). |
_page, _locale | Where the form was sent from (defaults to the Referer) and its language. |
Spam trap (default website) | Render it hidden and empty. Submissions that fill it in get a normal response but are dropped. |
A valid submission returns { "ok": true, "successMessage": … }, is stored in the admin's inbox, emailed to the form's notification addresses and sent as a form.submitted webhook. Invalid fields return 422 with details.fields, e.g. { "email": "must be an email address" }. Submissions are rate limited per visitor (10 a minute) and per form.
await blok.submitForm("contact", { firstName: "Jan", email: "jan@example.com", message: "Hello" });
Example responses
What a site gets back, here from the starter template. The admin shows the live response for any page, global, collection, menu or form in its API panels, published or as drafts.
GET /v1/<space>/entries/by-path/
{
"entry": {
"id": "ent_01M4R43N18PTAWZ63SFJDDT7PK",
"type": "page",
"name": "Home",
"slug": "home",
"path": "/",
"parentId": null,
"locale": "en",
"tags": [],
"position": 1,
"publishedAt": "2026-10-12T13:16:22.954Z",
"firstPublishedAt": "2026-10-12T13:16:22.954Z",
"content": {
"id": "blk_01M4R43N17EWVYWY143FHNJKJZ",
"type": "page",
"fields": {
"title": "Duwijckpark",
"body": [
{
"id": "blk_01M4R43N17TRPEB3Y8KH3CZPJ4",
"type": "hero",
"fields": {
"headline": "Welcome to Duwijckpark",
"cta": { "kind": "url", "url": "https://docs.blokcms.app/quickstart.html" },
"ctaLabel": "Read the quickstart"
}
},
{
"id": "blk_01M4R43N17D83JHKMVEYS7CQBY",
"type": "text",
"fields": {
"body": {
"type": "doc",
"content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Pages are built from blocks…" }] }]
}
}
}
]
}
}
},
"included": { "entries": {}, "links": {} },
"cv": 1791810982962
}
GET /v1/<space>/entries?type=company&limit=10
{
"items": [ { "id": "ent_…", "type": "company", "name": "…", "content": { … } } ],
"included": { "entries": {}, "links": {} },
"nextCursor": "eyJ…",
"total": 42,
"cv": 1791810982962
}
GET /v1/<space>/globals/settings
{
"entry": {
"id": "ent_01M4R43N15XWESCCGPMCVXNPKX",
"type": "settings",
"name": "Site settings",
"path": "@settings",
"content": {
"id": "blk_01M4R43N1CCHK0JXP92KWNMVQF",
"type": "settings",
"fields": { "siteName": "Duwijckpark", "footerText": "© Duwijckpark" }
},
…
},
"included": { "entries": {}, "links": {} },
"cv": 1791810982962
}
GET /v1/<space>/menus/main
{
"menu": {
"key": "main",
"name": "Main menu",
"items": [
{
"id": "mni_01M4R43N1G1X6YJJAJ0E424AWE",
"label": "Home",
"href": "/",
"target": null,
"entryId": "ent_01M4R43N18PTAWZ63SFJDDT7PK",
"children": []
}
]
},
"cv": 1791810982962
}
/menus returns { "menus": { "main": { … }, "footer": { … } }, "cv": … }; /sitemap returns { "locale": "en", "items": [{ "id", "type", "path", "updatedAt", "alternates": { "en": "/" } }], "cv": … }.
Responses and caching
Entries have content (the localized block tree, assets expanded with URLs and focal points) and included.links to turn entry links into URLs. Every publish bumps the space's cache version (cv): responses are cached per version at the edge, so a publish is visible worldwide within seconds without purges. X-Blok-Cache shows whether a response came from the edge (HIT-L1), the global cache (HIT-L2) or the origin (MISS).
Images
Asset fields are delivered as { "asset", "url", "filename", "mime", "size", "width", "height", "focal", "crop", "alt", "title", "credit", "placeholder", "color" }. Build other sizes from url with assetUrl(asset, { width }) or assetSrcSet(asset) from @blokcms/client (or <BlokImage>): widths round up to 160, 320, 480, 640, 768, 1024, 1280, 1600, 1920, 2560 or 3840; presets (thumb, card, hero, og and your own, under Settings → Media) crop at the focal point. AVIF or WebP is picked from the browser's Accept header.
When editors crop an image for one usage, crop holds the crop (fractions of the original), width/height and focal describe the cropped image, and url carries a signed crop query. Keep that query string when you build URLs (the helpers do): only crops saved in content render. color is the image's average color, a cheap background while it loads.
Errors
{ "error": { "code": "not_found", "message": "Entry not found" } }
Status codes: 400 invalid query or body, 401 bad token, 403 origin or draft not allowed, 404, 415 unsupported body type, 422 invalid submission, 429 rate limited.