Diil Docs
  1. Docs
  2. API reference

GET /v1/blocks/:marker — one block

Updated:

Sometimes you don't need a page or even a section — you need exactly one thing. The phone number in the header. The promo banner on top of the shop. The price that marketing changes every Friday. This endpoint hands you a single block by its marker, and nothing else.

Typical candidates:

  • A phone number or email in the header — a text block that lives on the common service page and shows up everywhere.
  • A promo banner — an object block with a title, an image and a link, dropped into a layout you already render from code.
  • A price — a number block your checkout or landing page reads without loading the whole pricing page.

Get one block

GET/v1/blocks/:marker

https://back.sitecog.com/content/v1/blocks/:marker

One block with its value in content. Same shape as a block inside a page or section response — just without the wrapping.

Parameters

markerpathstringrequired
The block marker from the CRM, e.g. phone or promo_banner. Latin letters, digits and underscores, 2–40 characters, case-sensitive.
sectionquerystringoptionalDefault: any section
Marker of the section the block belongs to, e.g. ?section=header. Block markers are unique only within a section, so this is how you say exactly which title you mean. Without it, the first match wins — the oldest block with that marker. See below.
langquerystringoptionalDefault: all active languages
Limit translations to these languages: ?lang=en, ?lang=en,de or ?lang=en&lang=de. Every code must be an active language of the site, otherwise you get 400 unknown_lang. Has no effect on values that have no translations, like numbers or colours.
x-crm-keyheaderstringrequired
Your site key. Can also be passed as ?key= if headers are not an option. See Site keys.

There is no ?empty here: a block without its content would be a box with nothing in it. The parameter simply doesn't apply to this endpoint.

Example request

curl "https://back.sitecog.com/content/v1/blocks/phone?section=header&lang=en" \
  -H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"

Example responses

The wrapper is always the same; only content changes with the block type.

A text block

200 OK — GET /v1/blocks/phone?section=headerjson
{
  "id": 88,
  "marker": "phone",
  "name": "Phone in header",
  "type": "text",
  "multilang": true,
  "updatedAt": "2026-09-12T07:45:10.000Z",
  "content": { "en": "+66 2 123 4567", "de": "+66 2 123 4567" }
}

Text is a language map: one key per language, in CRM order. A language with no saved value at all is simply missing from the map; one that exists but is blank comes as "". There is no server-side fallback, so pick a backup language yourself — see the helper in Tips.

An object block

200 OK — GET /v1/blocks/promo_banner?section=promojson
{
  "id": 131,
  "marker": "promo_banner",
  "name": "Promo banner",
  "type": "object",
  "multilang": false,
  "updatedAt": "2026-09-28T10:05:00.000Z",
  "content": {
    "title": { "en": "Autumn sale: 20% off", "de": "Herbst-Sale: 20 % Rabatt" },
    "image": { "file": "https://cdn.example.com/storage/your-site/autumn.jpg" },
    "link": {
      "url": "https://example.com/sale",
      "target": "_self",
      "title": { "en": "Shop now", "de": "Jetzt kaufen" }
    },
    "ends": "2026-10-15",
    "active": true
  }
}

An object is a set of fields keyed by field marker, and each field follows the same rules as a standalone block: text fields are language maps, a link is { url, target, title }, an image is { file } (or a per-language map), numbers, dates and booleans come as stored. Only values are returned — the field definitions stay in the CRM.

And the price

200 OK — GET /v1/blocks/price?section=pricingjson
{
  "id": 140,
  "marker": "price",
  "name": "Base plan price",
  "type": "number",
  "multilang": false,
  "updatedAt": "2026-09-26T16:20:00.000Z",
  "content": 149
}

Numbers, colours, dates, date ranges and booleans come back as the raw stored value (or null if empty) — no language map, so ?lang doesn't touch them. Every shape is listed on the Block types page.

Response fields

idnumber
Internal block id. Stable, but prefer markers in your code — ids differ between environments.
markerstring
The block marker, the same one you put in the URL.
namestring
Human name from the CRM (“Phone in header”). Meant for editors — don't print it on the page.
typestring
One of text, html, image, video, link, number, color, date, date_range, boolean, object, array. Check it before reading content if the same component renders different blocks.
multilangboolean
For images and videos: true if every language has its own file, and content is then a map of URLs by language instead of a single file.
updatedAtstring (ISO 8601)
When the block was last edited. Nice for “prices updated on…” notes and for cache keys.
contentdepends on type | null
The value itself: a language map for text and html, { file } for a single image or video, { url, target, title } for a link, an object of fields for object, an array of such objects for array, a raw value (or null) for numbers, colours, dates and booleans. Details: Block types.

Markers are unique per section — use ?section

Block markers only have to be unique inside their section. That is what lets editors reuse sensible names: the hero section has a title, the faq section has a title, and nobody has to invent title_2_final.

The flip side: /v1/blocks/title on its own is ambiguous. Without ?section the API returns the first match — the oldest block with that marker — which may or may not be the one you meant, and may change if someone recreates a block. Add the section and the answer is exact:

# Ambiguous: whichever "title" was created first
curl "https://back.sitecog.com/content/v1/blocks/title" -H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"

# Exact: the title of the FAQ section
curl "https://back.sitecog.com/content/v1/blocks/title?section=faq" -H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"

Don't fetch blocks one by one

Don't: a waterfall of tiny requestsjs
const title = await getBlock('title', 'promo');
const text = await getBlock('text', 'promo');
const image = await getBlock('image', 'promo');
Do: one request, same datajs
const promo = await fetch('https://back.sitecog.com/content/v1/sections/promo?lang=en', {
  headers: { 'x-crm-key': 'pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40' },
}).then((r) => r.json());
const { title, text, image } = promo.content;

The single-block endpoint shines when you really need one value in a place that otherwise has nothing to do with that page — the phone in a global header, a banner in a layout, a price in a checkout widget.

Errors

StatusBodyWhat happened
400{"message":"invalid_marker"}The marker has characters outside A–Z a–z 0–9 _ or the wrong length.
400{"message":"invalid_lang", …}A code in lang is malformed.
400{"message":"unknown_lang", …}A language in lang is not active on the site. The body lists the available ones.
401{"message":"invalid_key"}Missing, malformed or revoked key.
404{"message":"block_not_found"}No block with this marker — or none in the section you named in ?section.
429{"message":"rate_limit_exceeded"}Too many requests this minute. See Rate limits.

The full list with fixes lives on the Errors page.

Tips from the trenches

// requested language → default language → first non-empty value
export function t(map: Record<string, string> | undefined, lang: string, fallback = 'en') {
  if (!map) return '';
  return map[lang] || map[fallback] || Object.values(map).find(Boolean) || '';
}

t(phone.content, 'de'); // "+66 2 123 4567"