Docs
Dashboard

API reference

Content

Reading and writing your own content types with the /v1/content and /v1/schema endpoints.

The endpoints for the types you define. For the blog, knowledge base and authors, see Built-in modules.

Fetch a type

GET /v1/content/{type}

{type} is the type slug, exactly as content_types[].slug reports it in introspection.

A singleton answers with its values object. A collection answers with a paginated array.

Parameter
limitCollections only. 1–100, default 20. Refused outside that, not clamped.
cursorCollections in their default order. Opaque.
offsetCollections when sort is used.
where[…]Filter by the type’s own fields. See filtering.
sortA field key, or created_at / published_at / updated_at. - prefixes descending.
idsComma-separated ent_… ids, at most 50. Returns exactly those, in the sort order.
releaseA rel_… id. Omit for the default release.
media_urlspermanent to refuse expiring image URLs.
curl "https://api.zevcontent.net/v1/content/team-member\
?where[role]=engineer&sort=-joined&limit=10" \
  -H "Authorization: Bearer $ZEVCONTENT_KEY"
{
  "data": [
    {
      "id": "ent_2c8RkXqGn4wVtYbMhJpDs",
      "values": { "name": "Ada Lovelace", "role": "engineer", "joined": "2024-03-01" },
      "published_at": "2026-08-14T09:00:00.000Z",
      "updated_at": "2026-08-20T11:12:00.000Z"
    }
  ],
  "meta": {
    "type": "team-member",
    "kind": "collection",
    "release": { "id": "rel_…", "name": "Launch", "default": true },
    "limit": 10,
    "has_more": false,
    "next_cursor": null,
    "next_offset": null
  }
}

Fetch one entry

GET /v1/content/{type}/{entryId}

By its ent_… id. Use this for a detail page when you already hold the id; filter the listing instead when you hold a slug-like field value.

Takes release and media_urls.

Create an entry

POST /v1/content/{type}/entries

Secret key only. Collections only.

{
  "values": { "name": "Ada Lovelace", "role": "engineer" },
  "publish": true
}

values is keyed by field key and validated against the type: a missing required field is missing_required_field, a wrong type is invalid_field_value, an unknown key is unknown_field naming the ones that exist.

Omitting publish creates a draft. That is the right default for a migration script or an agent, because content generated by a machine should usually be reviewed before it is public.

Save a singleton

PUT /v1/content/{type}

Secret key only. Singletons only.

Replaces the singleton’s draft with values, creating its one entry on first save, and publishes it in the same call with "publish": true.

It is a replace, not a merge: values is the whole object. A key you omit is cleared, not left alone.

The schema itself

GET /v1/schema/content-types

Secret key only. Every type definition with its fields. These are the same definitions introspection embeds, on their own endpoint for a client that wants the schema without the rest of the map.

PUT /v1/schema/content-types/{type}

Secret key only. Create or update a type and its fields. This is how a project is modelled from code rather than in the dashboard, and it is what lets an agent set one up end to end.

Two things are immutable once set, because changing them breaks every frontend already reading:

  • A type’s kind (content_type_kind_immutable). A singleton becoming a collection changes the response from an object to an array.
  • A field’s type (field_type_immutable). A string becoming a number changes what every reader receives.

Everything else is editable: labels, options, order, and adding or removing fields.

Only published content is served

/v1/content serves published entries. A draft is invisible here and visible in the dashboard, which is what lets somebody work on a page for a week without it appearing on your site.

An entry that exists but is not published answers entry_not_published rather than pretending it does not exist.

Updated at, Wednesday, September 2, 2026