Docs › Reference

For everyone

The semantic layer

The API calls behind the semantic layer.

Part of the HTTP API reference.

Route: #/semantic → drawSemantic.

The access decision for this screen:

Reading it is open, and deliberately so: the last time a screen was gated on the permission that changes it, nobody could find the screen and it may as well not have existed. Saving is behind SEMANTIC_EDIT, and the button says so rather than the page disappearing.

The order of calls

  1. GET /api/semantic/yaml
  2. On pressing Check: POST /api/semantic/yaml/plan
  3. On pressing Save: POST /api/semantic/yaml
  4. On unlocking a field: DELETE /api/semantic/lock/{ref}/{code}

GET /api/semantic/model, POST /api/semantic/compile, GET /api/semantic/history and GET /api/semantic/history/{save_id} exist and no screen calls them; see the final section.

GET /api/semantic/yaml — the layer as YAML

No parameters, no permission check.

Cached by _derived under semantic_yaml — 4.52s on ten million assets.

Refusal: 503 "Reading a semantic model needs the pyyaml package. pip install pyyaml".

Docstring: "The layer as Cube-shaped YAML, plus the fields a paste would not touch."

Response

{
  "yaml": "cubes:\n  - name: orders\n …",
  "locks": [                   // the fields a paste will NOT overwrite
    {"ref": "orders", "code": "revenue",
     "field": "Description",   // the attribute's name
     "by": "r.iyer",
     "when": "3 Feb 2026"}     // strftime("%-d %b %Y")
  ],
  "saved_at": "2026-02-03T…",  // the last save, or null
  "saved_by": "r.iyer",        // or null
  "note": "Added the churn metric."  // or null
}

A lock is created by PUT /api/assets/{uid}/attributes/{code} on every write (see Screen 4). "A lock is not a refusal. It decides who wins a disagreement, and the screen that would lose says so before saving rather than afterwards."

POST /api/semantic/yaml/plan — what saving it would do

No permission check. "Writes nothing." Always rolled back.

Body: {"yaml": "<the whole document>"}.

Refusal: 400 with semantic_io.CannotRead's own sentence.

Response: the plan shape (p.as_dict), as described in Conventions.

POST /api/semantic/yaml — apply it

Permission: SEMANTIC_EDIT — "save the semantic model".

Body

Field Type Notes
yaml string the whole document
note string stripped, null when empty; shown as note on the next read

Docstring: "Apply the YAML. Refused whole if anything in the plan blocks it."

Refusals: 400 from semantic_io.CannotRead (and the transaction is rolled back first); 409 with " ".join(p.blockers) when the plan is not safe.

Response: the plan shape, committed.

DELETE /api/semantic/lock/{ref}/{code}

Permission: SEMANTIC_EDIT — "unlock a field", naming "<ref>.<code>" in the refusal.

Docstring: "Let the YAML win this field from now on."

Response: {"unlocked": 1} — how many lock rows went.