BIO.RE
Content

Get CMS Page (by slug)

Public read of a single CMS page by slug. Optional locale selector (default 'en'). HTML body server-side sanitized (script / style / on* / iframe stripped). 404 on missing or DRAFT.

GET /api/v1/public/pages/:slug โ€” ๐ŸŒ Public ยท Rate limit: 60 req / minute

Returns a single CMS page by slug. PUBLISHED-only โ€” DRAFT / ARCHIVED slugs return 404. The HTML body is server-side sanitized before return: <script>, <style>, <iframe> tags + on* event handlers are stripped (defense-in-depth โ€” admin authors are trusted, but the sanitizer protects against operator mistakes).

Sanitization is defensive, not exhaustive. The server strips obvious XSS vectors (<script>, <style>, <iframe>, inline event handlers like onclick). Other dangerous patterns (CSS expressions, data URIs in <img src>, etc) are NOT stripped โ€” render the content with dangerouslySetInnerHTML only if you trust the admin authors. For belt-and-suspenders safety, run client-side sanitization (e.g. DOMPurify) on top.

Locale-keyed lookup: each (slug, locale) pair is a separate row. Default locale is en. Pass ?locale=tr to fetch the Turkish version. Missing locale โ†’ 404, NOT fallback to English โ€” you must list available locales via GET /public/pages and pick from there.

Request

Path parameters

ParamTypeNotes
slugstringPage slug (e.g. about-us, careers)

Query parameters

ParamTypeDefaultNotes
localestringenLocale code

No headers required.

Response headers

HeaderValue
Cache-Controlpublic, s-maxage=300, stale-while-revalidate=600

Response

200 OK โ€” ApiResponseOf<PublicCmsPageDto>

{
  "success": true,
  "data": {
    "slug": "about-us",
    "title": "About Us",
    "content": "<p>Sanitized HTML content</p>",
    "locale": "en",
    "publishedAt": "2026-04-29T20:00:00.000Z"
  }
}
FieldTypeNotes
slugstringEchoes the path param
titlestringPage title
contentstringServer-side sanitized HTML body
localestringLocale of the matched row (matches the ?locale filter)
publishedAtstring (ISO 8601) | nullWhen the page transitioned to PUBLISHED

Stripped fields

id, status, publishedBy, authorId, createdAt, updatedAt โ€” all internal-only, excluded from this response.

Errors

HTTPcode / i18nKeyReason
404error.content.page_not_foundNo CMSPage matches (slug, locale) AND status = PUBLISHED
429(throttle)Rate limit exceeded (60 req/min)

Side effects

  1. prisma.cMSPage.findFirst({ where: { slug, locale: locale ?? 'en', status: PUBLISHED } }).
  2. Missing โ†’ throw page_not_found (404).
  3. HTML sanitization โ€” sanitizeHtml(page.content) strips:
    • <script>...</script> blocks (regex with greedy minimum match).
    • <style>...</style> blocks (same pattern).
    • Inline event handlers โ€” any attribute starting with on followed by a word (e.g. onclick="...", onerror='...').
    • <iframe> opening AND closing tags (kept content between, but tags stripped โ€” effectively unwraps).
  4. Return { slug, title, content: <sanitized>, locale, publishedAt }. No mutations.

Code samples

# Default locale (en)
curl https://api.bio.re/api/v1/public/pages/about-us

# Specific locale
curl 'https://api.bio.re/api/v1/public/pages/about-us?locale=tr'
type PublicCmsPage = {
  slug: string;
  title: string;
  content: string;
  locale: string;
  publishedAt: string | null;
};

async function getCmsPage(slug: string, locale = 'en'): Promise<PublicCmsPage> {
  const url = new URL(`https://api.bio.re/api/v1/public/pages/${encodeURIComponent(slug)}`);
  if (locale !== 'en') url.searchParams.set('locale', locale);
  const res = await fetch(url);
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Page fetch failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}
import { useQuery } from '@tanstack/react-query';

export const contentKeys = {
  page: (slug: string, locale: string) => ['content', 'pages', slug, locale] as const,
};

export function useCmsPage(slug: string, locale = 'en') {
  return useQuery({
    queryKey: contentKeys.page(slug, locale),
    queryFn: async () => {
      const url = new URL(`/api/v1/public/pages/${encodeURIComponent(slug)}`, window.location.origin);
      if (locale !== 'en') url.searchParams.set('locale', locale);
      const res = await fetch(url);
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Page fetch failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
      return json.data as PublicCmsPage;
    },
    enabled: Boolean(slug),
    staleTime: 5 * 60_000,
  });
}

Try it

GET
/api/v1/public/pages/{slug}

Path Parameters

slug*string

Query Parameters

locale?string

Locale code (default: en)

Response Body

application/json

application/json

curl -X GET "https://loading/api/v1/public/pages/string"
{
  "success": true,
  "data": {
    "slug": "about-us",
    "title": "About Us",
    "content": "<p>Sanitized HTML content</p>",
    "locale": "en",
    "publishedAt": "2019-08-24T14:15:22Z"
  }
}
{
  "success": false,
  "error": {
    "code": "AUTH_UNAUTHORIZED",
    "message": "Invalid credentials",
    "i18nKey": "auth.login.invalid_credentials",
    "i18nVars": {
      "field": "email"
    },
    "details": [
      {
        "message": "email must be an email"
      }
    ],
    "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}

Source

SourcePathLines
Controllerapps/api-core/src/modules/content/public-content.controller.ts72โ€“95 (getPage), 29โ€“36 (sanitizeHtml)
DTO (response)apps/api-core/src/modules/content/dto/content-public-response.dto.ts17โ€“32 (PublicCmsPageDto)
Serviceapps/api-core/src/modules/content/content.service.ts73โ€“77 (getPage)
Prisma modelpackages/prisma/prisma/schema.prismaCMSPage (filter status = PUBLISHED, (slug, locale) lookup)

On this page