# Content model

Site, pages told apart by kind, blocks, entities, theme tokens and media.

This page explains the data a clinic frontend renders: the site, its pages, the blocks inside them, the clinic's treatments, team, case studies and posts, the theme and the media. It is the same data the clinic's own site is rendered from, so everything the clinic publishes in its panel is in the API at once.

## Site

`site` is the frame of every page: the clinic's name, its domain, the main menu, the theme tokens and the clinic profile (contact, address, map point, opening hours, NFZ contract, payments, social links, logos, registry numbers). Every profile field is optional: show what is there and hide the rest.

Get it on its own with [GET /site](https://pozyskajpacjenta.pl/docs/reference/content/get-site), or together with a page from [GET /content](https://pozyskajpacjenta.pl/docs/reference/content/get-content). When `site.noindex` is `true` the clinic keeps its site out of search engines: add meta robots `noindex` to every page (ideally the `X-Robots-Tag: noindex` header too), publish no sitemap, and keep robots.txt open (`Allow: /`) so crawlers can see the noindex.

## Pages and kinds

[GET /content](https://pozyskajpacjenta.pl/docs/reference/content/get-content)`?path=/…` returns `{ site, page }`. `page` is a union told apart by `kind`: a page the clinic built from blocks in its page editor, or one of eight pages the platform builds from the clinic's data.

| kind | Page | Main field |
| --- | --- | --- |
| `page` | A page built from blocks in the clinic's page editor. | `blocks` |
| `services` | The treatments index (/zabiegi). | `services` |
| `service` | A treatment page (/zabiegi/{slug}). | `service` |
| `team` | The team page (/zespol). | `doctors` |
| `doctor` | A team member's profile page (/zespol/{slug}). | `doctor` |
| `case-studies` | The case studies index (/realizacje). | `caseStudies` |
| `case-study` | A case study page (/realizacje/{slug}). | `caseStudy` |
| `posts` | The posts index (/aktualnosci). | `posts` |
| `post` | A post page (/aktualnosci/{slug}). | `post` |

[GET /pages](https://pozyskajpacjenta.pl/docs/reference/content/list-pages) lists every published path with its `type` (the same values as `kind`), so you know what to expect before you fetch a page. When a block page and an entity page share a path, the block page wins, as on the clinic's site.

```ts TypeScript
import type { ContentPage } from "@pozyskajpacjenta/sdk";

function render(page: ContentPage) {
  switch (page.kind) {
    case "page":
      return page.blocks; // render by block.type
    case "service":
      return page.service.bodyBlocks;
    case "doctor":
      return page.doctor.bio;
    default:
      return page.title;
  }
}
```

Entity pages share a frame: `title`, `breadcrumbs` and exactly one call to action, `cta` (the clinic's phone or its contact page).

## Blocks

Pages built in the editor, and the `bodyBlocks` of treatments, case studies and posts, are lists of validated blocks. Each block has an `id`, a `type` with its version (`hero.v1`, `faq.v1`) and `props` whose shape depends on the type.

Render the types you support and skip the rest: new block types appear over time and must never break your frontend. The block types in use today:

`before-after.v1`, `booking.v1`, `booksy-embed.v1`, `breadcrumbs.v1`, `case-study-teaser.v1`, `cta-band.v1`, `faq.v1`, `feature-cards.v1`, `footer.v1`, `gallery.v1`, `hero.v1`, `lead-form.v1`, `map.v1`, `media-split.v1`, `nav.v1`, `post-list.v1`, `pricing.v1`, `procedure-summary.v1`, `process-steps.v1`, `related-services.v1`, `rich-text.v1`, `services-grid.v1`, `stats.v1`, `sticky-booking-cta.v1`, `team-grid.v1`, `team.v1`, `testimonials.v1`, `trust-bar.v1`, `znanylekarz-embed.v1`.

Price list rows (`pricing.v1`, field `items`) can carry a `serviceSlug`: the treatment the row is about, so you can link the row to that treatment's page.

## Entities

Treatments, team members, case studies and posts are resources of their own, each with a list and a detail endpoint (tag [Entities](https://pozyskajpacjenta.pl/docs/reference/entities)). They carry the path of their page on the clinic's site, and treatments and team members link to booking: `bookingServiceId` on a treatment and `bookingResourceId` on a team member are the ids [GET /booking/slots](https://pozyskajpacjenta.pl/docs/reference/booking/get-slots) takes. `null` means the item cannot be booked online.

## Theme

`site.theme` holds the clinic's design tokens: colours (light and dark), fonts, type scale, radii, shadows and motion. Map them onto CSS variables and style your components with the variables: a theme change in the panel then reaches your frontend without a code change.

```ts TypeScript
const { site } = await client.getSite();
const c = site.theme.colors;

const css = `:root {
  --background: ${c.background};
  --foreground: ${c.foreground};
  --primary: ${c.primary};
  --primary-foreground: ${c.primaryForeground};
}`;
```

## Media

Images come as `MediaRef` objects with an absolute `url`, the original's `width` and `height`, `alt` and a caption. Put `width` and `height` on the `<img>` so the layout does not shift. `focalX` and `focalY` (percent) tell you which part to keep when you crop, e.g. as `object-position`.

Files from the media library (`/api/media-file/…`) have smaller variants: `?w=` takes one width of 320, 480, 640, 960, 1280, 1600, 2048 px (never upscaled; any other width is a 400) and `f=` takes `auto` (AVIF or WebP by the `Accept` header), `avif` or `webp`. Files are served with `Cache-Control: public, max-age=31536000, immutable`: the address names one version of the file, so cache freely. The SDK builds variant URLs and a `srcset` for you (`mediaVariantUrl`, `mediaSrcSet`).

GET responses of the API itself are private to the key (`Cache-Control: private, max-age=60`): cache them on your server for up to a minute.
