REST API reference

Read-only, token-authenticated, cached at the edge

Your frontend reads published content from kura over HTTP. Base URL: https://kuracms.com/api/v1/{project}, where {project} is your project slug.

Authentication

Send your project token (prefixed kr_live_) as a Bearer token on every request:

Authorization: Bearer kr_live_your_token

The project token is full-access: the same kr_live_ token authenticates both reads and writes (the public API has create/update endpoints, and a POST auto-publishes). Keep it server-side, never in client-side code. Create one from your project page (/app/{slug}, the Developer section); it's shown once, so copy it on creation.

Status codes

  • 200 - content returned. Envelope: { "data": [...], "meta": { "count", "limit", "offset" } }. meta.count is the number of entries in this response (up to limit), not a grand total - there is no total-count field; page with limit/offset until you get back fewer than limit.
  • 401 - missing or invalid bearer token.
  • 402 Subscription inactive - the project is not billing-active, so content is not served. A brand-new project is on the free tier (not active); start the free trial to flip it to active. A lapsed or paused project also returns 402.
  • 404 - unknown project or content type.

(Write endpoints, used by the admin and MCP, also return 400 for a malformed body and 409 for a slug conflict on create - you won't hit these on read.)

Endpoints

  • GET /api/v1/{project}/{type} - list published entries of a content type.
  • GET /api/v1/{project}/{type}/{entry} - one entry by its slug.
  • GET /api/v1/{project}/types - every content type with its full field schema.
  • GET /api/v1/{project}/types/{type} - one content type's schema.

Don't know your type slugs? Call /types first - it lists every content type and field.

Query parameters (list endpoint)

  • Sorting: sort=-field is descending, sort=field is ascending (the - prefix is the canonical form). Sort on several: comma-separated, e.g. sort=-published_at,title. (sort=field:desc still works but is legacy - use the -field form.)
  • Filtering: field=value (equals), plus operators in brackets: field[gt|lt|gte|lte|ne]=value, field[contains]=str (case-insensitive), field[startswith]=str, field[in]=a,b,c (comma list, max 50), and field[isnull]=true|false. Example: ?bedrooms[gte]=3&status=Available.
  • Field selection: fields=a,b,c returns only those fields (id and slug are always returned); exclude=a,b drops those.
  • Pagination: limit (default 20, max 100) and offset (default 0). There is no page param.
  • Drafts/preview: add ?preview (same token) to return unpublished draft content; preview reads are never cached. Default returns published only. Wire ?preview to a /preview route so editors can check changes before publishing.

Note: object-valued fields (rich_text, coordinates, gallery) cannot be filtered or sorted on.

What you get back - and four things to know

The response is clean JSON. Four field types behave in ways worth knowing before you build against them:

Relations return a bare slug

A relation field (a Listing linked to an Agent, say) comes back as the linked entry's slug string, not the embedded object. There is no ?expand. To get the linked entry's content, make a second request for that slug.

// a Listing entry
{ "title": "...", "agent": "jane-smith" }   // <- slug, not the agent object
// fetch it: GET /api/v1/{project}/agent/jane-smith

Images return a relative media path

An image field comes back as a relative path, /media/{key} (e.g. /media/abc123.jpg), not a full URL. Your frontend must prefix it with the base URL to load it: ${KURA_BASE_URL}/media/abc123.jpg -> https://kuracms.com/media/abc123.jpg. (The example frontends do exactly this prefix on any /media/ path.) A gallery field is a list of these paths.

Coordinates return {lat, lon}

A coordinates field comes back as an object: { "lat": 35.68, "lon": 139.76 } (the keys are lat and lon, not lng).

Rich text returns rendered HTML

A rich_text field comes back as a rendered HTML string, ready to drop into your page (the same shape a markdown field gives).

The schema is additive (v0.1)

You can add content types and fields, but not rename or delete them yet. Treat the shapes you read here as append-only for now.


Setting kura up for the first time? Start with Getting started. Managing the schema from your AI tool? See MCP setup.