PRISM CAFE — read API

A read-only JSON view of the PRISM CAFE base: the chart of accounts, the dimensions, the rules, and any single record with its texts.

Getting in

Every path under /api/v1/ needs a shared key, sent as a header. Ask Rasmus for it.

curl -H "X-API-Key: YOUR_KEY" https://data.prismcafe.dk/api/v1/overview

Without a valid key the answer is 401. This page and /health are the only paths that need no key, and neither of them reads the base.

The paths

/api/v1/ lists them and is the one to build against: it is generated from the same list the server routes on, so it cannot fall behind.

PathWhat it answers
/api/v1/overview How much the base holds, per database and per category, plus the current version stamp of each database and the languages in use.
/api/v1/accounts The chart of accounts. Accounts, their subaccounts (datatype, whether they can be posted to, which levels they sit on) and every value with its properties. Texts included throughout.
/api/v1/dimensions The axes and their levels (position, type, predecessor), the relations between levels (kind, inheritance, what a conditional one depends on), and the properties.
/api/v1/rules The rules the base holds itself to: severity, reach, what each one checks, and the worked example it carries.
/api/v1/records/<id> A single record: the row it lives in, and every text it has.

Two things worth knowing about the shape

Field names are the base's own. Nothing is renamed and nothing is translated. subaccount_id is subaccount_id, can_post is can_post. If you have seen the schema, you have seen these names.

Two kinds of missing are kept apart. A text carries a state. "filled" means there is one; "n/a" means somebody has decided there will not be one. A language that simply has not been written yet is absent altogether. The three are different facts, and the API does not flatten them into one.

"texts": {
  "name": {
    "en": { "state": "filled", "value": "Volume" },
    "da": { "state": "n/a",    "value": null }
  }
}

Freshness

The API never reads the base where it lies, because that file is written while this serves. It reads a copy, and it takes a new copy at intervals. Every answer carries a source block saying when the copy was taken, how old it is, and its checksum. A copy that fails its check is discarded and the previous one keeps serving, so an answer can be stale but never half-written.

"source": {
  "copy_taken_at": "2026-08-05T14:31:02Z",
  "copy_age_seconds": 12.4,
  "copy_md5": "...",
  "refresh_interval_seconds": 30,
  "last_refusal": null
}

This API only reads. There is no path that writes, and the connection to the base is opened read-only, so a write cannot be made even by mistake. A POST, PUT, PATCH or DELETE is answered 405.