API · conventions
Everything you do in the dashboard can be done by API. This page explains what applies to every endpoint.
Base
https://pluma.so/api/v1
Start at the root. It tells you what it is, what your key can do and where the docs are:
GET /api/v1
curl https://pluma.so/api/v1 -H "Authorization: Bearer $PLUMA_KEY"
{
"name": "Pluma",
"docs": "https://pluma.so/llms.txt",
"key": { "kind": "delivery", "space": "acton-estero", "can": ["read_published"] },
"next": "GET /api/v1/spaces/acton-estero"
}
The machine contract (OpenAPI 3.1) is at /openapi.json: use it to generate clients or to let your agent see every endpoint with its parameters.
Authentication
Every request carries a key in the header:
Authorization: Bearer pluma_dlv_…
Keys are created in the site dashboard, under Keys. The full value is shown only once; keep it in a secrets manager. You can revoke a key in one click.
| Kind | Prefix | What it can do | What for |
|---|---|---|---|
delivery |
pluma_dlv_ |
Read what is published | Your site's build. Safe to use on the build server |
preview |
pluma_prv_ |
Also read drafts | Preview |
management |
pluma_mgt_ |
Read everything, write, publish and change the model | Scripts, migrations. Never in the browser |
An invited agent has its own token (pluma_agt_) with the same permissions as management, plus creating delivery and preview keys. See Agents.
Delivery and Preview are the same API: same routes, same responses. The only thing that changes is the key: with a preview key you see the latest version of each entry; with a delivery key, the published one.
Responses
- JSON, UTF-8,
snake_casein the API's names; your content's fields use their API ID as is. - Every object has
sys(metadata:id,type, dates, version) andfields(your content). - The
sysdates (created_at,updated_at,published_at) are ISO 8601, UTC. Date fields in your content come back exactly as you saved them, with their time zone. ids are opaque and unique across all of Pluma, not per site: don't assume they start at 1 or are consecutive.
Collections and pagination
{ "sys": { "type": "Array" }, "total": 57, "skip": 0, "limit": 100, "items": [ … ] }
limit: up to 1000. Default 100.skip: how many to skip.- For the next page:
skip = skip + limitwhileskip < total.
Locales
- Without
locale, fields come in the site's default language. locale=en: in that language; if a field has no value, it falls back to the fallback language. Fields that aren't localized always come with the default language's value.locale=*: every language, as{ "title": { "es": "…", "en": "…" } }.- Filters (
fields.slug=…), sorting andsys.urluse the language you asked for. - Writing takes the same
locale. See Languages.
Errors
Every error says what happened, how to fix it and links to its page:
{
"error": {
"code": "invalid_key",
"message": "The key does not exist or was revoked.",
"fix": "Check that you copied all of it (it starts with pluma_). If it was revoked, create a new one in Keys.",
"doc_url": "https://pluma.so/docs/errors/invalid_key"
}
}
The full list is in Errors.
Cache
- With a
deliverykey:Cache-Control: public, max-age=60, s-maxage=300andETag. SendIf-None-Matchand you get304if nothing changed. - With
previewormanagement:Cache-Control: private, no-store.
Limits
- Per token: up to 600 requests per minute (API and MCP together). Every response has
X-RateLimit-LimitandX-RateLimit-Remaining. If you go over, you getrate_limitedwithRetry-Afterin seconds. - Per site: 1 million API calls per month. Today they are counted; the warning and the limit arrive with billing.