Theme

Definitions API

The whole authoring surface as files, and the safety-railed way to apply an edited copy of it back. The three endpoints the CLI is a thin client of.

Method & pathWhat it does
GET /api/v1/definitionsThe whole surface as { files: { path: contents } }.
POST /api/v1/definitions/diffWhat applying these files would change. Changes nothing.
POST /api/v1/definitions/applyDiff, validate, rehearse, apply — all or nothing.

The file formats are documented at definitions repo layout. Authoring artifacts only: people, events, runs, sends and money are runtime and never appear, and neither do credentials.

Pull

GET /api/v1/definitions

{ "files": {
    "flows/trial_journey.json": "{ … }",
    "emails/0042-welcome-aboard.md": "---\nid: 42\n…",
    "settings.yml": "tunables:\n  …" } }

Canonically formatted — one key order, one indent, one trailing newline — so a pull straight after a pull is byte-identical, and an unrelated push doesn't churn every file in the repo with reordered keys.

Diff and apply

POST /api/v1/definitions/apply
{ "files": { "flows/trial_journey.json": "…", … },
  "prune": false,
  "force": false }

Send the whole managed tree, not a subset — absences are how removals are expressed, and a partial tree would read as a mass deletion. (With prune: false, the default, absences are only reported.)

The response

{ "changes": [
    { "path": "flows/trial_journey.json", "kind": "update", "type": "flow",
      "label": "trial_journey",
      "summary": ["added 12-send-recap (id 12 minted)"],
      "errors": [], "warnings": [] } ],
  "changed": 1,
  "dry_runs": [ { "flow": "trial_journey", "triggered": true, "steps": 6, "problems": [] } ],
  "notices": [],
  "parse_errors": [],
  "blocked": false,
  "ok": true,
  "applied": 1 }
FieldMeaning
kindcreate, update, unchanged, or absent (on the instance, no file in the tree).
summaryHuman-readable lines. Steps are named by handle; an email re-point is described in the chooser's terms.
dry_runsOne per changed flow, rehearsed against a real person with details that satisfy that flow's own trigger.
noticesDefinitions already on the instance that don't validate. Not caused by this push, and not blocking it.
parse_errorsFiles that couldn't be read at all. Always blocking, never forcible.
blockedWhether apply would refuse.

Matching, not filenames

Records match by identity — a key, or the id in a file's frontmatter — never by filename. Renaming an email changes its filename, and a rename is not a delete.

What blocks

A definition already broken on the instance appears in notices and doesn't block — you can't fix it by not pushing, and one broken record shouldn't wall off every unrelated change from then on.

Order of operations

Templates, fields and products first; then emails; then segments, guards and sequences; then flows; then settings. So a commit that adds an email and the step using it lands in one push. Everything happens in one transaction — a push that half-landed would leave the instance in a state no file describes.

Pruning

prune: true also removes what's on the instance but not in the tree. Emails are archived rather than destroyed — an email that has been sent is part of the record of what went out — and a flow people have run through refuses regardless, because their history is theirs.

Live updates

A successful apply pings every surface an admin page watches, so changes appear on an open page without a refresh.

See also: CLI · File layout · MCP server