Skip to content

Content

Each tenant has a small headless CMS in Quikstik so that product copy, policies, FAQs and pages live beside the catalog they describe, and the booking widget, the confirmation email and the marketing site all read one source.

All content endpoints live under /merchants/{merchant_slug}/… and take a content key in x-api-key: a pk_ key for reads, an sk_ key for writes. See Authentication → content keys.

A content type is a schema stored as data (PUT /merchants/{slug}/content-types/{type_slug}): its fields, their kinds (text, rich text, number, reference, …) and validation. Schema versions are monotonic; an attempt to write a lower version is 409. GET /merchants/{slug}/content-types returns exactly the schema documents the write endpoints accept, so a tool can discover and author with one vocabulary.

An entry is one document of a type, addressed by slug. POST /merchants/{slug}/content/{type_slug} creates an entry and its first revision atomically; PUT …/{entry_slug} appends a revision.

Revisions are protected by optimistic concurrency: send If-Match with the sequence you last read (428 without it). A lost race is 409 carrying currentSequence, so a retry is a deliberate read-merge-write, never a blind overwrite.

Validation returns the complete list of issues in one 422, each with a JSON Pointer to the field. Pass ?validate_only=true to run full validation with no side effects — rehearse before you write.

The write envelope is {"slug": "…", "fields": {…}}. A rich-text field on the wire is a block tree, {"root_id": "<uuid>", "blocks": {id: {id, block_type, parent, children, properties}}} — a map, with a root block whose parent is null and symmetric parent/children edges. Reads return the same content as a nested tree for rendering.

Publication is a join, not a flag: an entry’s current revision is published to a surface — a (channel, locale) pair such as ("website", "en-US") or ("booking-widget", "en-US") — with POST …/{entry_slug}/publications and {"channel": …, "locale": …, "state": "live"}. Retract with state: "retracted". The same entry can be live on the website and not yet in the widget.

Public reads see only what is live on the surface they ask for: GET /merchants/{slug}/content/{type_slug} lists live entries newest-first; GET …/{entry_slug} returns one with its body. A retracted or never-published entry is 404 — the public surface does not reveal unpublished existence.

List responses use the Payload-style envelope (docs, totalDocs, totalPages, page, hasNextPage, hasPrevPage), so a site built against a Payload CMS can move to Quikstik without consumer changes.

Media binaries live on the CDN; GET /merchants/{slug}/media and …/media/{media_id} serve their metadata (dimensions, alt text, the tenant’s own string id preserved through import), and PUT …/media/{media_id} upserts it idempotently by wire id.

GET /merchants/{slug}/settings returns the singleton site-settings document (brand tokens, contact details, copy voice) or {} when unset. GET /merchants/{slug}/policy returns the tenant’s guest-facing policy text, and GET /merchants/{slug}/cancellation-schedule its T-minus schedule, for a storefront to render beside checkout.