Posts
GET /external/posts and GET /external/posts/:slug
Two endpoints — paginated list and single-post-by-slug.
List
GET /external/posts?type=services&locale=en&page=1&limit=50
X-Api-Key: bk_live_xxx| Query | Type | Default | Notes |
|---|---|---|---|
type | string | blog | Post type slug. * for every type. |
locale | string | (no filter) | BCP47 code. Omit to fetch every locale. |
page | int | 1 | 1-indexed. |
limit | int | 50 | Max page size. |
force_limit | int | — | Opt past the 50-cap. Up to 1000. |
include | string | (none) | Pass content to inline contentHtml / contentMarkdown. |
q | string | — | Case-insensitive substring on title + slug. |
Response
{
"items": [
{
"postId": "p_2N4r",
"slug": "schengen-visa",
"canonicalSlug": "schengen-visa",
"title": "Schengen Visa",
"metaDescription": "...",
"imageUrl": "...",
"tags": ["europe"],
"category": { "id": "c_1", "slug": "visas", "name": "Visas" },
"authorName": "...",
"customConfig": { /* free-form */ },
"publishedAt": "2026-03-12T08:00:00.000Z",
"contentHtml": null,
"contentMarkdown": null,
"jsonLd": null
}
],
"pageInfo": {
"page": 1,
"limit": 50,
"total": 54,
"totalPages": 2,
"hasNext": true,
"hasPrev": false
}
}SDK
const posts = await bf.posts.list({ type: 'services', locale: 'en' })
// Pagination handled internally; caller gets a flat array.Get by slug
GET /external/posts/schengen-visa?locale=en
X-Api-Key: bk_live_xxxReturns the single post + a translations array listing every
published locale variant. Each entry includes publishedAt so
consumers can compute a cross-locale sitemap <lastmod>
(max(publishedAt) across siblings) when one URL represents the
whole article family.
{
"postId": "...",
"slug": "schengen-visa",
"title": "Schengen Visa",
"...": "...",
"translations": [
{ "locale": "en", "slug": "schengen-visa", "publishedAt": "2026-03-12T08:00:00.000Z" },
{ "locale": "pt", "slug": "visto-schengen", "publishedAt": "2026-04-02T10:15:00.000Z" }
]
}404 when the slug doesn't exist in the requested locale.
SDK
const post = await bf.posts.getBySlug('schengen-visa')
// → BrandfinePost | null (null on 404)publishedAt semantics — read before building a sitemap
publishedAt is the time of the last publish that actually
changed content, not the time of first publish. Brandfine
re-stamps it whenever an editor republishes a post with a different
contentHash, and skips the bump on no-op republishes (clicking
Publish without edits). That makes it the right value to feed into
a sitemap <lastmod>:
<url>
<loc>https://example.com/services/schengen-visa</loc>
<lastmod>{post.publishedAt}</lastmod>
</url>Two corollaries worth noting:
- Draft edits don't move
publishedAt. Saving a typo fix to the draft does nothing to the public URL or to this timestamp — the change only becomes visible (andpublishedAtonly bumps) when the editor hits Publish. - Unpublish → republish bumps it. Even with identical content,
re-publishing after an unpublish bumps
publishedAtsince consumers may have dropped the URL during the gap.