Skip to main content

Public delivery

Public delivery is the surface your frontend hits. It serves published content only, authenticates with a project-scoped API key (no JWT, no user session), and is safe to call from browsers and mobile apps.

GET https://api.sandbox.ovok.com/v1/public/cms/{typeSlug}/items?environment=dev&projectId={medplumProjectId}
GET https://api.sandbox.ovok.com/v1/public/cms/{typeSlug}/items/{idOrSlug}?environment=dev&projectId={medplumProjectId}

Required query parameters​

ParamRequiredDescription
environmentYesdev, staging, or prod — selects the content partition
projectIdYesMedplum project UUID; resolves the Payload tenant

Use projectId, not projectSlug, on this surface.

Authentication​

Send the API key as Authorization: Bearer …, x-api-key: …, or ?apiKey=…. Each key is bound to exactly one project when PAYLOAD_CMS_PUBLIC_API_KEY is configured on ovok-core (alpha may allow unauthenticated access when that env var is unset).

Mint keys from the Console at Settings → API keys → CMS:

Console Settings API keys tab with CMS API keys card and a New API key button

GET /v1/public/cms/pages/items?environment=dev&projectId=4897826b-4ff5-4ce4-a96a-849a40f51f60 HTTP/1.1
Host: api.sandbox.ovok.com
Authorization: Bearer cmspub_a4f3b21c...
Accept: application/json

How to mint a key →

:::tip Key safety API keys identify your project. They're meant to be shipped in your frontend bundle — they only grant read access to published content. But: rotate them on schedule, scope them per app, and never check them into a public repo. :::

List items in a collection​

curl "https://api.sandbox.ovok.com/v1/public/cms/pages/items?environment=dev&projectId=$MEDPLUM_PROJECT_ID" \
-H "Authorization: Bearer $OVOK_CMS_KEY"

Additional query params:

ParamTypeDescription
limitintegerMax items per page (default 10, max 100)
pageinteger1-indexed page number
sortstringSort field, prefix - to reverse (-publishedAt)
where[field][op]JSONPayload's standard filter syntax

Example — recent published blog posts, newest first:

curl "https://api.sandbox.ovok.com/v1/public/cms/posts/items?environment=dev&projectId=$MEDPLUM_PROJECT_ID&sort=-publishedAt&limit=5" \
-H "Authorization: Bearer $OVOK_CMS_KEY"

Get one item​

By ID:

curl "https://api.sandbox.ovok.com/v1/public/cms/pages/items/abc123?environment=dev&projectId=$MEDPLUM_PROJECT_ID" \
-H "Authorization: Bearer $OVOK_CMS_KEY"

By slug (Payload's slug field, if your collection defines one):

curl "https://api.sandbox.ovok.com/v1/public/cms/pages/items/pricing?environment=dev&projectId=$MEDPLUM_PROJECT_ID" \
-H "Authorization: Bearer $OVOK_CMS_KEY"

The endpoint resolves slug vs ID automatically. Drafts and unpublished items return 404 here regardless of whether you ask by ID or slug.

What's not returned​

Public delivery hides the bits authors don't want public:

  • Items with _status: draft or _status: published but publishedAt in the future
  • Soft-deleted items
  • Internal-only fields (anything marked admin.hidden in the Payload collection config)
  • Items not in the key's project or wrong environment

If you can read an item via /v1/content/api/... but not via /v1/public/cms/.../items/..., check draft status, environment, and projectId first.

CORS​

Public delivery is the only CMS surface with browser-friendly CORS. The proxy returns Access-Control-Allow-Origin: * for GET and HEAD. If you need a stricter origin set, terminate at your own edge (Vercel, Cloudflare) and lock CORS there.

Caching​

Responses include Cache-Control: public, max-age=60, s-maxage=300 by default — a minute on the client, five minutes on shared caches. Publishing an item invalidates the shared cache for that item's collection via a fan-out hook. Custom cache headers per collection are on the roadmap.

Errors​

StatusMeaning
401API key missing or revoked
403API key belongs to a project where CMS is disabled
404Item doesn't exist, isn't published, or belongs to another project
429Rate-limited — back off and retry
502Tenant unreachable — retry

Next​