Building a theme
The Content API
Read the site's content over HTTP — tokens and scopes, the endpoints, filtering, sorting, pagination and previews.
Everything a theme renders can also be read over HTTP. You can use it to show Tidewater’s newest blog posts and help articles inside the invoicing product itself.
After reading this guide, you will know:
- How to create a token, and what each scope allows.
- Every endpoint, and which collections they serve.
- How to filter, sort, paginate and choose fields.
- How to read drafts for a preview.
1. What it is for
Use it when something outside the site needs its content: a product showing “What’s new”, a mobile app, a static build, a search index somewhere else.
Do not use it from your own theme. A theme asks for content through its query sidecar, which is faster, needs no token, and lets Nibble cache the page precisely.
The API is read-only. Nothing is written through it.
2. Turning it on
A collection or taxonomy is served only when it says so:
# site/schema/collections/posts.yml
api: true
Nibble’s pages and posts and the theme’s taxonomies have api: true already.
Warning
Every field of a served entry is in the response. A field option api: false exists, but it only leaves the field
out of the generated TypeScript types — the value is still sent. Keep anything that must stay private out of
collections the API serves.
Caution
A token is shown once, when it is created. Store it where the job that uses it can read it and nowhere else — never in a theme, which is sent to every visitor’s browser.
Send it as a bearer token:
curl -H "Authorization: Bearer $TIDEWATER_TOKEN" \
"https://tidewater.example/api/v1/collections/posts/entries?page[size]=3&sort=-published_at"
Each token may make 300 requests a minute.
4. Endpoints
| Endpoint | Returns |
|---|---|
GET /api/v1/collections/:collection/entries |
a collection’s entries, paginated |
GET /api/v1/entries/:uuid |
one entry |
GET /api/v1/taxonomies/:taxonomy/terms |
a taxonomy’s terms, paginated |
GET /api/v1/terms/:uuid |
one term |
GET /api/v1/globals/:handle |
a global’s fields |
GET /api/v1/navigation/:handle |
a menu |
GET /api/v1/assets/:id |
one asset |
GET /api/v1/routes |
every public URL the site serves |
GET /api/v1/schema |
the collections and taxonomies the API serves, and their fields |
GET /api/v1/health |
whether the site is healthy |
Content comes back in the same shape a view receives, so a field means the same thing in a template and in a JSON response.
Tip
/api/v1/routes is the one a static build wants: it lists every page that exists, so a generator knows what to
fetch.
Note
A collection written as files is listed by
/api/v1/collections/:collection/entries, but /api/v1/entries/:uuid finds only entries stored in the database.
5. Query parameters
The listing endpoints take the same options as a query sidecar, written as URL parameters:
| Parameter | Example | Means |
|---|---|---|
filter[field] |
filter[topics]=12,15 |
in for a relationship, eq for a column |
filter[field][op] |
filter[published_at][gt]=2026-09-01 |
any operator a query supports |
sort |
sort=-published_at,title |
- for descending |
fields |
fields=title,url,excerpt |
only these fields |
include |
include=authors,topics |
relationship fields in full |
page[number], page[size] |
page[size]=3 |
pagination |
locale |
locale=en |
another locale |
To show the three newest posts in the product:
GET /api/v1/collections/posts/entries?page[size]=3&sort=-published_at&fields=title,url,excerpt
6. Previews
Drafts are never served unless a request asks for them with preview=1 and its token has the preview scope:
GET /api/v1/entries/0f1c…?preview=1
Give the product’s staging build a separate preview token, so writers can see a draft post in the product before it goes live — and the production build’s token cannot read drafts at all.
Warning
A token with preview can read every unpublished entry in every collection the API serves. Give it only to
systems that need it.
