Download all docs
boards

Board

A configurable kanban board you shape to your own domain — declare your columns, give cards a schema, and wire each column or card to invoke other elements, all without baking workflow logic into the element type.

Working with it

Opening a Board launches a kanban board — its dedicated working surface.

How it appears

The same element type rendered as a definition, a circle instance, and a live workspace card.

Bd
type

Board

Generic kanban board — columns, cards, configurable card schema and actions

boardsatomdefinition

When to use / not

When to use

  • Tracking a process as columns and cards when your domain doesn't fit sales, planning, or recruitment — month-end close, audit-request management, reconciliation variance, covenant compliance, and the like.
  • Carrying arbitrary per-card data: drop any domain fields into a card's free-form metadata, optionally validated against your own JSON Schema.
  • Gating moves and automating side effects from the board itself — column preconditions, WIP limits, and entry/exit actions that fire other elements when a card lands or leaves.
  • Putting action buttons on cards that call out to a function, an HTTP endpoint, or a human-in-the-loop approval without writing glue code.

When not to use

  • A sales pipeline, sprint plan, or hiring funnel — reach for sales-board, planning-board, or recruitment-board, which bake that domain's logic in rather than asking you to configure it.
  • Orchestrating a sequence of steps with branching and waits — that is what an automation or app is for; a board is a tracking surface, not a flow engine.
  • Storing structured records you query rather than move through stages — use a data element like entity or sql instead.

Topology

Created from the library and placed inside an app or circle. It is a top-level building block you compose with other elements.

Properties

card_schemaobject
Optional JSON Schema (Draft 2020-12) that every card's `metadata` object is validated against on add-card and update-card. Leave unset for free-form metadata (Phase 1 loose-mode behaviour). Accepts an inline JSON Schema object. In later phases this will also accept `{$ref: "data/schema/<slug>"}` to reuse a schema declared elsewhere in the circle library.
card_schema_modestring
`strict` (default): metadata is rejected when it fails card_schema. `loose`: validation runs but violations are recorded as warnings on the op response and the card is stored anyway. Useful during schema migrations where some existing cards no longer match the declared shape.
card_actionsarray
Phase 4: declarative card-level actions. Each entry surfaces as a button on the card face (rendered by portal from the `_actions` HATEOAS list on get-card) and as a callable operation via the `run-card-action` op. Each action declares: - `id` — stable identifier used by run-card-action - `label` — button text - `icon` — Material icon name (optional) - `invokes` — what to call when the action fires: element_ref: "<element_type>/<slug>" — library path to the target element (e.g. "io/phone-number/my-phone" or "actions/hitl/approve-signature") op: target operation name on that element input_template: JSON-shaped template. String leaves are rendered with {{var}} substitution against `{card}` context; non-string values pass through. - `enabled_when` — optional CEL expression returning a boolean. Evaluates against `{card}`. When false, the action is returned on _actions with `enabled: false` and a `reason` (the expression) so the UI can grey it out. Example (close-board — "Attach evidence" + "Request sign-off"): card_actions: - id: attach_evidence label: Attach Evidence icon: attach_file invokes: element_ref: io/http/evidence-uploader op: upload input_template: card_id: "{{card.id}}" account_code: "{{card.metadata.account_code}}" enabled_when: "card.metadata.evidence_url == ''" - id: request_signoff label: Request Sign-off icon: approval invokes: element_ref: actions/hitl/close-signoff op: invoke input_template: card: "{{card.id}}" enabled_when: "card.metadata.evidence_url != ''"
columnsarray
Kanban columns for this board. Each column has a stable id, display name, and phase (todo | in_progress | closed). Optional: position, color, wip_limit, weight (for aggregation), stage (free-form string seeded onto cards on entry).

Operations

  • activityGET
  • add-cardPOST
  • add-columnPOST
  • add-filePOST
  • add-refPOST
  • archive-cardPOST
  • attachmentsGET
  • batch_statsGET
  • board-summaryGET
  • composePOST
  • contextGET
  • createPOST
  • deleteDELETE
  • delete-cardDELETE
  • delete-fileDELETE
  • disablePOST
  • enablePOST
  • export_bundleGET
  • getGET
  • get-cardGET
  • get-fileGET
  • import_bundlePOST
  • intentionGET
  • list-cardsGET
  • list-filesGET
  • move-cardPOST
  • move-historyGET
  • promotePOST
  • readmeGET
  • readme_updatePOST
  • remove-columnDELETE
  • remove-modifierPOST
  • remove-refDELETE
  • reorder-cardPOST
  • restorePOST
  • run-card-actionPOST
  • schemaGET
  • seed-cardsPOST
  • sourceGET
  • source_branchesGET
  • source_promotePOST
  • source_repairPOST
  • source_statusGET
  • source_validatePOST
  • statsGET
  • treeGET
  • unarchive-cardPOST
  • updatePATCH
  • update-cardPUT
  • update-columnPUT
  • update_metaPATCH
  • versionGET

Composition

Errors / when it fails

Duplicate column IDs detected — each column must have a unique id
Fails unless: columns.size() <= 1 || columns.all(c, columns.filter(c2, c2.id == c.id).size() == 1)
Duplicate card_action IDs detected — each action must have a unique id
Fails unless: card_actions.size() <= 1 || card_actions.all(a, card_actions.filter(a2, a2.id == a.id).size() == 1)

Validation rules

  • card_schema_mode=loose has no effect without card_schema — set card_schema first or remove card_schema_mode
  • Board has no columns — add at least one column before creating cards

Board (board)

Category: boards | Form: | Symbol: Bd

Generic kanban board — columns, cards, configurable card schema and actions

Generic kanban board for any domain. Cards store arbitrary metadata; columns are declared in spec with phase mapping, optional WIP limits, and (in later phases) entry/exit actions and preconditions. Unlike sales-board/planning- board/recruitment-board which bake domain logic into the element type, a generic board achieves specialization via configuration: card_schema (field shape), card_display (render hints), card_actions (button list), and bonded_elements (siblings that provide callable ops). Use this when your domain doesn’t fit one of the existing board types — e.g. month-end close tracking, audit-request management, reconciliation variance tracking, covenant compliance, or any workflow where you want kanban shape without the sales/planning/recruitment opinions. Phase 1 scope: basic kanban with user-declared columns and free-form card metadata. No schema validation, no declarative column hooks yet — those layer on in later phases.

Guide

Generic kanban board — columns, cards, configurable card schema and actions

What It Does

Board is a generic kanban element: columns hold cards, cards carry free-form metadata, and you organise work by moving cards between columns. Unlike sales-board, planning-board, and recruitment-board — which bake domain logic into the element type — a generic board specialises through configuration: columns (with phases and WIP limits), card_schema (the shape of each card’s metadata), card_display (render hints), and card_actions (per-card buttons that invoke other elements).

Each card stores arbitrary, user-declared data in a metadata object. With no card_schema set (the free-form default) there is no validation — whatever you put in metadata is stored verbatim and echoed back. When you set card_schema, every card’s metadata is validated against that JSON Schema (Draft 2020-12) on add-card and update-card; card_schema_mode (default strict) decides whether a violation is rejected (strict) or stored with a warning (loose). Use a generic board when your domain doesn’t fit one of the existing board types — month-end close tracking, audit-request management, reconciliation variance tracking, covenant compliance, or any workflow that wants kanban shape without the sales/planning/recruitment opinions.

A board is a data element (activity_type: data), not a flow element: it exposes an HTTP card/column API rather than wire ports. It accepts rate-limit and auth-policy modifiers. Its runtime states are provisioned (initial), active, and error.

Element Definition

PropertyValue
Typeboard
Categoryboards
Formatom
SymbolBd
Iconview_kanban / #6B7280
Workbenchworkbench-board-panel

Properties

FieldTypeDefaultDescription
columnsarray[]Kanban columns. Each requires id, name, phase (todo/in_progress/closed); optional position, color, wip_limit, weight, stage, entry_actions, exit_actions, preconditions
card_schemaobjectnullOptional JSON Schema (Draft 2020-12) validating each card’s metadata. Unset = free-form metadata
card_schema_modestringstrictstrict: reject metadata that fails card_schema. loose: record violations as warnings and store anyway
card_displayobjectnullDeclarative card-face rendering; {{var}} templates over the card (metadata.* for metadata fields)
card_actionsarray[]Per-card action buttons. Each requires id, label, invokes (element_ref + op + optional input_template); optional icon, enabled_when (CEL)
sample_cardsarray[]Reusable starter cards seeded idempotently by seed-cards (keyed on metadata.sample_id)

Column sub-fields of note: wip_limit (0 = no limit), weight (0–1, for weighted aggregates), stage (seeded onto metadata.stage on entry), and the per-column hooks entry_actions / exit_actions (fire-and-forget element invocations) and preconditions (CEL expressions that must hold before a card enters the column).

Capabilities

CapabilityDescription
kanbanColumn-based card organisation with drag-and-drop and WIP limits
card-schemaOptional JSON Schema validation of card metadata via card_schema + card_schema_mode
card-displayDeclarative card-face rendering via card_display templates
card-actionsPer-card action buttons that invoke other elements via run-card-action
column-hooksentry_actions, exit_actions, and preconditions declared per column
seed-cardsIdempotent sample-card seeding via the seed-cards op and spec.sample_cards
move-historyAppend-only audit trail of every card move with timestamps and actor

Error Codes

CodeClassRetryableDescription
BOARD_WIP_EXCEEDEDlimitNoCard move or create would exceed the target column’s wip_limit
BOARD_COLUMN_NOT_FOUNDvalidationNocolumn_id does not match any column on this board
BOARD_COLUMN_NOT_EMPTYvalidationNoCannot remove a column that still contains non-archived cards
BOARD_SELF_REFvalidationNoA card cannot reference itself
BOARD_CARD_NOT_FOUNDvalidationNoCard does not exist or belongs to a different board
BOARD_PRECONDITION_FAILEDvalidationNoColumn precondition expression returned false — card cannot enter
BOARD_ACTION_NOT_FOUNDvalidationNorun-card-action: action_id matches no declared card_action
BOARD_ACTION_DISABLEDvalidationNorun-card-action: the action’s enabled_when evaluated false for this card
BOARD_SCHEMA_VALIDATION_FAILEDvalidationNoCard metadata failed JSON Schema validation against card_schema (strict mode)
BOARD_TARGET_ELEMENT_NOT_FOUNDvalidationNorun-card-action: target element from invokes.element_ref does not exist in this circle

Operations

Operations are invoked at POST /api/{circle}/{slug}/ops/{op}. Board overrides the card-shaped ops below to expose a free-form metadata field and adds seed-cards and run-card-action; it also inherits the shared kanban ops (move-card, reorder-card, archive-card, unarchive-card, delete-card, add-ref, remove-ref, add-column, update-column, remove-column, add-file, get-file, list-files, delete-file, move-history).

add-card

POST cards · auth: write Create a new card in a column with free-form metadata. Accepts the standard card fields (title required; description, column_id, phase, position, assignees, labels, priority, tags, due_date, story_points) plus an optional metadata object. In Phase 1 metadata is stored as-is; when card_schema is set it is validated before insert.

update-card

PUT cards/{card_id} · auth: write Update any combination of card fields (card_id required). When metadata is provided its keys are shallow-merged into the existing card metadata — set a key to null to delete it, omit it to leave it unchanged.

get-card

GET cards/{card_id} · auth: read Get a card with its full metadata blob.

list-cards

GET cards · auth: read List cards with optional filters: column_id, phase, assignee, label, archived (default false), limit (default 100), offset (default 0).

seed-cards

POST seed-cards · auth: write Create missing sample cards declared in spec.sample_cards. Idempotent: cards already present with the same metadata.sample_id are skipped; missing ones are created through the normal add-card path (so schema validation, WIP limits, and preconditions still apply). Returns {created, skipped, total, cards}.

run-card-action

POST cards/{card_id}/actions/{action_id} · auth: write Fire a declared card action. Resolves spec.card_actions[] by id, checks enabled_when against the card, renders the action’s input_template with {{var}} substitution ({card} context), resolves the target via invokes.element_ref (<element_type>/<slug>), and dispatches invokes.op on it. Returns the downstream result wrapped as {action_id, target_element_type, target_slug, target_op, result}. Optional override_input is merged over the rendered template (top-level keys) before dispatch.

board-summary

GET summary · auth: read Per-column card counts and phase breakdown for this board. Lightweight — no money/weight aggregation.

Quick Start

Create a board

POST /api/{circle}/{project}/
Content-Type: application/json

{
  "element_type": "board",
  "slug": "close-board",
  "name": "Close Board · Month-End",
  "spec": {
    "columns": [
      { "id": "open",     "name": "Open",      "phase": "todo" },
      { "id": "review",   "name": "In Review", "phase": "in_progress", "wip_limit": 5 },
      { "id": "signed",   "name": "Signed Off","phase": "closed" }
    ]
  }
}

Add a card

POST /api/{circle}/{project}/close-board/ops/add-card
Content-Type: application/json

{
  "title": "Reconcile GL 1020",
  "column_id": "open",
  "priority": "high",
  "metadata": { "account_code": "1020", "risk_level": "medium" }
}

Move it and summarise

POST /api/{circle}/{project}/close-board/ops/move-card
Content-Type: application/json

{ "column_id": "review" }
GET /api/{circle}/{project}/close-board/ops/board-summary

Common Mistakes

Duplicate column IDs. Every column in spec.columns must have a unique id — duplicates corrupt card storage and fail spec validation (column_ids_unique). The same uniqueness rule applies to card_actions[].id (card_action_ids_unique).

Setting card_schema_mode: loose without a schema. loose only takes effect when card_schema is also set. Without a schema it has no effect and validation warns you (card_schema_mode_requires_schema).

Creating cards before adding columns. A board with no columns has nowhere to put cards — declare at least one column (via spec.columns or add-column) first. The empty-columns case is flagged as a spec warning.

Removing a non-empty column. remove-column fails with BOARD_COLUMN_NOT_EMPTY if the column still holds non-archived cards. Archive or move them out first.

Expecting a move into a precondition-gated column to always succeed. When a target column declares preconditions, the card must satisfy every CEL expression or the move/add fails with BOARD_PRECONDITION_FAILED and the collected messages; the card is not moved.

Relationships

  • Attaches to: rate-limit, auth-policy

Capabilities

  • kanban: Column-based card organisation with drag-and-drop and WIP limits
  • card-schema: Optional JSON Schema (Draft 2020-12) validation of card metadata via card_schema + card_schema_mode
  • card-display: Declarative card-face rendering via card_display Mustache templates (Phase 4b)
  • card-actions: Declarative per-card action buttons that invoke other elements via run-card-action
  • column-hooks: entry_actions, exit_actions, and preconditions declared per column (Phase 3)
  • seed-cards: Idempotent sample-card seeding via seed-cards op and spec.sample_cards
  • move-history: Append-only audit trail of every card move with timestamps and actor

Properties

PropertyTypeDefaultDescription
card_schemaobjectnullOptional JSON Schema (Draft 2020-12) that every card’s metadata object is validated against on add-card and update-card. Leave unset for free-form metadata (Phase 1 loose-mode behaviour).
Accepts an inline JSON Schema object. In later phases this will also accept {$ref: "data/schema/<slug>"} to reuse a schema declared elsewhere in the circle library.
card_schema_modestring"strict"strict (default): metadata is rejected when it fails card_schema. loose: validation runs but violations are recorded as warnings on the op response and the card is stored anyway. Useful during schema migrations where some existing cards no longer match the declared shape.
card_displayobjectnullPhase 4b: declarative card-face rendering. Template strings use {{var}} substitution against the card (title/column_id/phase are top-level, metadata fields are under metadata.*). Missing paths render as empty strings.
Without this, generic-board cards fall back to showing the card title on one line and the first line of description on a second line — fine for quick notes but useless for CFO workflows where the card’s state at a glance matters.
Example (close-board — “Reconcile 1020 · CFO · Risk: medium”):
card_display:
title: “{{title}}”
subtitle: “{{metadata.gl_owner}} · Risk: {{metadata.risk_level}}”
badges:
- field: metadata.risk_level
color_map:
low: “#10B981”
medium: “#F59E0B”
high: “#EF4444”
- field: metadata.evidence_url
label: “Evidence attached”
card_actionsarray[]Phase 4: declarative card-level actions. Each entry surfaces as a button on the card face (rendered by portal from the _actions HATEOAS list on get-card) and as a callable operation via the run-card-action op.
Each action declares:
- id — stable identifier used by run-card-action
- label — button text
- icon — Material icon name (optional)
- invokes — what to call when the action fires:
element_ref: “<element_type>/” — library path to the
target element (e.g. “io/phone-number/my-phone”
or “actions/hitl/approve-signature”)
op: target operation name on that element
input_template: JSON-shaped template. String leaves are
rendered with {{var}} substitution against
{card} context; non-string values pass through.
- enabled_when — optional CEL expression returning a boolean.
Evaluates against {card}. When false, the
action is returned on _actions with
enabled: false and a reason (the
expression) so the UI can grey it out.

Example (close-board — “Attach evidence” + “Request sign-off”):
card_actions:
- id: attach_evidence
label: Attach Evidence
icon: attach_file
invokes:
element_ref: io/http/evidence-uploader
op: upload
input_template:
card_id: “{{card.id}}”
account_code: “{{card.metadata.account_code}}”
enabled_when: “card.metadata.evidence_url == ‘’”
- id: request_signoff
label: Request Sign-off
icon: approval
invokes:
element_ref: actions/hitl/close-signoff
op: invoke
input_template:
card: “{{card.id}}”
enabled_when: “card.metadata.evidence_url != ‘’”
sample_cardsarray[]Optional reusable starter cards for a board scaffold. The seed-cards operation creates missing cards idempotently, using metadata.sample_id as the stable dedupe key.
columnsarray[]Kanban columns for this board. Each column has a stable id, display name, and phase (todo | in_progress | closed). Optional: position, color, wip_limit, weight (for aggregation), stage (free-form string seeded onto cards on entry).

Operations

activity

Get /ops/activity | Auth: Read

Get activity events for this element

Scope depends on element capabilities: individual elements query by element_id, project-form elements with activity-scope-members include member activities, circle-level elements with activity-scope-all query the entire circle. Gracefully returns empty list if activities table is missing (old circles).

add-card

Post /ops/cards | Auth: Write

Create a new card in a column (free-form metadata)

Creates a card in the specified column. Accepts the standard card fields (title, description, phase, column_id, position, assignees, labels, priority, tags, due_date, story_points) plus an optional metadata object for any domain-specific fields your schema needs. Phase 1 is loose — metadata is stored as-is with no validation. When spec.card_schema lands in Phase 2, metadata will be validated against it before insert.

add-column

Post /ops/columns | Auth: Write

Add a new column to the board

Creates a column with id, name, and phase (required). The display label field is ‘name’ (not ‘title’). Phase accepts friendly names: Backlog/To Do→todo, Sprint/In Progress→in_progress, Done/Review→closed. Use position gaps (e.g., 15 between 10 and 20) to insert between existing columns without renumbering.

add-file

Post /ops/cards/{card_id}/files | Auth: Write

Attach a file to a card

Uploads a file to the card’s CAS folder at files/{card_id}/{filename}. Content is base64-encoded in the request body. Existing files with the same name are overwritten (upsert). Creates a DB index entry for fast listing. Maximum filename length is 255 characters; path separators are rejected.

add-ref

Post /ops/cards/{card_id}/refs | Auth: Write

Add a typed reference between cards

Creates a typed edge from this card to the target. relation=blocks automatically creates the inverse blocked_by on the target card. relation=parent makes this card a child of the target. Validates against circular references (BOARD_CIRCULAR_REF) and self-references (BOARD_SELF_REF).

archive-card

Post /ops/cards/{card_id}/archive | Auth: Write

Archive a card (soft-remove from board view)

Archived cards are hidden from default list_cards results but can be queried with filter=archived. Archive rather than delete to preserve history and references. Archived cards do not block other cards.

attachments

Get /ops/attachments | Auth: Read

List all modifiers and resources attached to this element

Returns both modifiers (policy enforcement) and resources (data injection) with is_modifier flag to distinguish. Items in the generated MODIFIER_TYPES list are modifiers; everything else is a resource. Includes cascade_policy and version pin info.

batch_stats

Get /ops/batch_stats | Auth: Read

Get per-element statistics for all children of this element

Returns per-child stats plus an aggregate. Most meaningful on compound or manifest form elements (repositories, circles, projects); atoms have no children so the result is an empty children array with a zeroed aggregate. Uses efficient GROUP BY SQL. Weighted averages for eval scores.

board-summary

Get /ops/summary | Auth: Read

Per-column counts and phase breakdown for this board

Lightweight summary — card counts per column + phase totals. No money/weight aggregation (that’s sales-board territory). When the board’s columns declare weight fields, future Phase 3 work will surface weighted counts here.

compose

Post /ops/compose | Auth: Execute

Batch add and remove modifiers on this element in a single call

Declarative composition: add modifiers by ref path (slug or path@version) and remove by attachment ID, all in one atomic call on the target element. Each ‘add’ entry resolves the source element, validates topology, attaches with optional priority and cascade policy. Each ‘remove’ entry deletes the attachment row. Returns a summary of what was added and removed. Example: compose({ add: [{ref: “my-prompt”}, {ref: “rate-limit/api@v2”, priority: 50}], remove: [{attachment_id: “uuid”}] })

context

Get /ops/context | Auth: Read

Get connected elements (graph traversal)

Graph traversal showing all connected elements with their relationship type (contains, contained_by, references, referenced_by, attaches, etc.). Use ?depth=N to control traversal depth (default 1) and ?types=actor,data to filter by element types.

create

Post /ops/create | Auth: Write

Create child element

POST to the parent path — element_type goes in the request body, NOT the URL. Both element_type and slug are required and must be non-empty. Name is derived from slug if omitted. Writes to both Git and PostgreSQL. All elements are stored flat under the circle — no intermediate library wrapper rows.

delete

Delete /ops/delete | Auth: Admin

Delete element (soft delete)

Soft delete — sets state to ‘deleted’ but retains the record. Cannot delete elements that have children (has_no_bond precondition) or active runs. Requires admin auth and confirmation.

delete-card

Delete /ops/cards/{card_id} | Auth: Write

Permanently delete a card

Hard deletes a card and all its references (blocked_by, blocks, related, parent). Subcards (children) are NOT deleted — they become orphans. Prefer archive-card to preserve history.

delete-file

Delete /ops/cards/{card_id}/files/{filename} | Auth: Write

Remove a file attachment from a card

Deletes the file from CAS and removes its DB index entry. The filename is extracted from the URL path. Returns not-found if the file doesn’t exist.

disable

Post /ops/disable | Auth: Admin

Disable element (hides and prevents use)

Idempotent — safe to call on already-disabled elements. Optionally pass a reason string. Disabled elements cannot be invoked or executed. Inverse of enable.

enable

Post /ops/enable | Auth: Admin

Enable element (makes usable and visible)

Idempotent — safe to call on already-enabled elements. Transitions element to ready/enabled state. Cannot enable deleted elements. Inverse of disable.

export_bundle

Get /ops/export/bundle | Auth: Read

Export element as downloadable git bundle

On non-root-namespace elements, returns a binary git bundle. On root-namespace (circle) elements, dispatch hands off to the circle’s own export_bundle op, which returns a multi-element JSON envelope with one base64 bundle per child element — this is intentional, not an error.

get

Get /ops/get | Auth: Read

Get element details

Element is already resolved by the routing layer — this returns the cached element, not a fresh DB query. Use the path /api/{circle}/{slug} to address elements.

get-card

Get /ops/cards/{card_id} | Auth: Read

Get a generic card with its full metadata blob

get-file

Get /ops/cards/{card_id}/files/{filename} | Auth: Read

Download a file attached to a card

Returns the file content as base64 along with metadata (size, content type, upload timestamp). The filename is extracted from the URL path.

import_bundle

Post /ops/import/bundle | Auth: Write

Import git bundle into element

Accepts a base64-encoded git bundle in the JSON bundle_base64 field. Use overwrite=true to replace existing elements with same slug (default skips duplicates). Imported elements get new UUIDs. Returns counts of imported/skipped elements and any errors.

intention

Get /ops/intention | Auth: Read

Get element intention with full inheritance chain

Returns three levels: direct (this element’s intention), inherited (from category and root), and resolved (final merged intention). Useful for understanding an element’s purpose in context of its hierarchy.

list-cards

Get /ops/cards | Auth: Read

List generic cards with optional filters

list-files

Get /ops/cards/{card_id}/files | Auth: Read

List files attached to a card

Returns metadata for all files attached to the card. Reads from the DB index table for fast listing without walking the CAS tree.

move-card

Post /ops/cards/{card_id}/move | Auth: Write

Move a card to a different column

Moves the card to the target column. Column can be specified by ID, name, or phase alias (Backlog, In Progress, Done, etc.). Position defaults to bottom of the target column. This is tracked in the move history audit trail. Enforces WIP limits on the target column — fails with BOARD_WIP_EXCEEDED if the limit would be breached. Emits board.card.moved NATS event.

move-history

Get /ops/cards/{card_id}/moves | Auth: Read

Get the move history for a card

Returns the audit trail of column transitions for this card. Each entry includes from_column, to_column, moved_by (actor slug), and timestamp. Ordered newest first.

promote

Post /ops/promote | Auth: Admin

Promote element configuration to a target environment

Only for manifest-form elements (projects). Environments advance: dev → demo → live. dev→demo requires member+ role, demo→live requires admin. Freezes member versions at promotion time (creates snapshot). Persists environment config to spec.environments.

readme

Get /ops/readme | Auth: Read

Get element README.md content

Reads README.md from the element’s git repository. Returns empty content (not an error) if no README exists. Always returns markdown format.

readme_update

Post /ops/readme_update | Auth: Write

Update element README.md content

Creates or overwrites README.md in the element’s git repo. Commits to the draft branch. Content must be provided as a markdown string.

remove-column

Delete /ops/columns/{column_id} | Auth: Admin

Remove a column from the board

Fails with BOARD_COLUMN_NOT_EMPTY if the column has non-archived cards. Archive or move all cards first. The column’s position gap remains available for reuse.

remove-modifier

Post /ops/remove-modifier | Auth: Execute

Remove an attached modifier from this element by attachment ID

Removes a modifier/resource attachment by its row ID. The ID comes from the attachments or context API. This is the reverse of attach — called on the target element, not the source.

remove-ref

Delete /ops/cards/{card_id}/refs/{target_card_id} | Auth: Write

Remove a reference between cards

Removes the reference from this card to the target. If the reference has an inverse (blocks/blocked_by), the inverse is also removed.

reorder-card

Post /ops/cards/{card_id}/reorder | Auth: Write

Change a card’s position within its current column

Changes vertical position (priority) without changing column. Position 0 is the top (highest priority). Other cards in the column are renumbered automatically to maintain order.

restore

Post /ops/restore | Auth: Admin

Restore element to a specific version

Automatically snapshots the current state before restoring (creates a ‘Before restore to vN’ version entry). Writes restored spec to git as .triform/spec.yaml. Git failures warn but don’t fail the operation — DB state is authoritative. Cannot restore deleted elements.

run-card-action

Post /ops/cards/{card_id}/actions/{action_id} | Auth: Write

Phase 4: fire a declared card action on the target element

Resolves spec.card_actions[] by id, checks enabled_when against the card, renders the action’s input_template with {{var}} substitution (context: {card}), looks up the target element via invokes.element_ref (shape <element_type>/<slug>), and dispatches invokes.op on it via OperationDispatcher. Returns the downstream operation’s result verbatim, wrapped with {action_id, target_element_type, target_slug, target_op, result} so callers can trace what ran. Fails 400 on: unknown action_id, card_schema mismatch on target input, enabled_when false (returns the expression as the reason), target element not found, or dispatch error.

schema

Get /ops/schema | Auth: Read

Get element input/output schema (MCP tools/list compatible)

Returns type-level port schemas from the TypeRegistry — not instance-specific overrides. Includes direction (input/output), required flag, and JSON schema per port. Useful for understanding what data an element accepts and produces.

seed-cards

Post /ops/seed-cards | Auth: Write

Create missing sample cards declared in spec.sample_cards

Seeds starter cards for schema-backed reusable boards. The operation is idempotent: cards already present with the same metadata.sample_id are skipped, while missing sample cards are created through the normal add-card path so schema validation, WIP limits, and preconditions still apply.

source

Get /ops/source | Auth: Read

Get any file’s content from the element’s git repository

Reads an arbitrary file from the element’s CAS-backed git tree by its relative path. Same store as readme, just generalized. Path safety: rejects .. traversal, leading /, and null bytes. Use this to view main.py for action elements, asset files for SPAs, etc. Returns empty content (not an error) if the file doesn’t exist.

source_branches

Get /ops/source/branches | Auth: Read

List Source branches for this element

Returns the standard draft/demo/live Source branches, their current commits, and promotion relationships. Use GET /api/{element_path}/ops/source/branches.

source_promote

Post /ops/source/promote | Auth: Write

Promote Source branch forward

Promotes draft to demo or demo to live through the generated element op path. Direct Git pushes to demo/live are blocked by Source policy.

source_repair

Post /ops/source/repair | Auth: Write

Inspect or repair the element Source index

Runs Source repair through the element operation path. Defaults to dry_run=true; set dry_run=false only after reviewing a dry-run report.

source_status

Get /ops/source/status | Auth: Read

Get Source control status for this element

Returns the branch-aware clone URL, checkout commands, current draft commit, child source-link count, portable export summary, Source health, warnings, and auth hints for the addressed element. Use the element-first path: GET /api/{element_path}/ops/source/status.

source_validate

Post /ops/source/validate | Auth: Read

Validate Source branch contents

Validates a Source branch before accepting local Git workflow changes or promotion. Defaults to branch=draft and rejects runtime data, generated output, secret material, and unreadable CAS refs.

stats

Get /ops/stats | Auth: Read

Get aggregate statistics for this element

Health status is computed: error if errors_per_day > 5 or success_rate < 0.8, warning if errors_per_day > 0 or success_rate < 0.95. Firing alerts escalate health to error/warning. Default period is ‘day’. Returns runs_per_day, success_rate, avg_duration_ms, and more.

tree

Get /ops/tree | Auth: Read

Get the element’s position in the graph — ancestors, children, references, and subtree statistics

Uses per-circle ElementGraph cache for O(1) lookups. Returns ancestors (containment chain), children (direct), members (references), referenced_by (reverse refs), attachments, and subtree stats. Default depth is 3, max is 10. Pass ?include_metadata=true for name/state on each node.

unarchive-card

Post /ops/cards/{card_id}/unarchive | Auth: Write

Restore an archived card to the board

Restores an archived card, making it visible again in default list-cards results. The card retains all its fields and references. Position defaults to the bottom of the card’s original column.

update

Patch /ops/update | Auth: Write

Update element

Partial update — send only the fields you want to change. spec, name, and intention are all independently optional. spec MUST be a JSON object when present; deep-merged into the existing spec by default. Empty {"spec":{}} preserves existing spec content but still records a new version (no-op for content, not for version state). To clear/replace the entire spec wholesale send {"spec":{...},"deep":false}. List-typed spec fields use replace semantics (the patch list replaces the existing list, no array merging). Coordinates Git + DB writes. Slug cannot be changed after creation.

update-card

Put /ops/cards/{card_id} | Auth: Write

Update card fields (metadata is shallow-merged)

Updates any combination of card fields. When metadata is provided, its keys are shallow-merged into the existing card metadata — set a key to null to delete it, omit a key to leave it unchanged. To replace metadata entirely, delete the card and recreate it.

update-column

Put /ops/columns/{column_id} | Auth: Write

Update column name, phase, WIP limit, or position

Updates column properties. Changing phase does NOT change the phase of cards in the column — card phase is independent. Only provided fields are updated. Set wip_limit to 0 to remove the limit.

update_meta

Patch /ops/update_meta | Auth: Write

Update element metadata (lightweight merge — does NOT bump version or snapshot spec)

Shallow JSONB merge into element.meta. Top-level keys in the provided value replace existing meta values; other keys are preserved. Used for UI metadata like canvas positions, panel state, viewer preferences. Wire-shape op_name is update_meta (distinct from update) so SSE subscribers + the cache auto-invalidator can distinguish lightweight metadata changes from spec edits without inspecting the payload. The MutatingElementStore wrapper stamps this op_name on the lifecycle event emitted by update_element_meta storage calls.

version

Get /ops/version | Auth: Read

Get current version or full history

Returns current version by default. Pass ?history=true for full version history (up to ?limit=N, default 50). Versions are backed by the element_versions table. Every spec update creates a new version entry.

Error Codes

CodeClassRetryableDescription
BOARD_WIP_EXCEEDEDlimitnoCard move or create would exceed the target column’s wip_limit
BOARD_COLUMN_NOT_FOUNDvalidationnocolumn_id does not match any column on this board
BOARD_COLUMN_NOT_EMPTYvalidationnoCannot remove a column that still contains non-archived cards
BOARD_SELF_REFvalidationnoA card cannot reference itself
BOARD_CARD_NOT_FOUNDvalidationnoCard does not exist or belongs to a different board
BOARD_PRECONDITION_FAILEDvalidationnoColumn precondition expression returned false — card cannot enter this column
BOARD_ACTION_NOT_FOUNDvalidationnorun-card-action: action_id does not match any declared card_action on this board
BOARD_ACTION_DISABLEDvalidationnorun-card-action: card_action.enabled_when evaluated to false for this card
BOARD_SCHEMA_VALIDATION_FAILEDvalidationnoCard metadata failed JSON Schema validation against card_schema (strict mode)
BOARD_TARGET_ELEMENT_NOT_FOUNDvalidationnorun-card-action: target element resolved from invokes.element_ref does not exist in this circle

Observability

Defined for this element

Metrics

  • board_card_created_count
  • board_card_moved_count
  • board_card_archived_count
  • board_card_deleted_count
  • board_card_time_in_phase_ms

Pricing / cost

Platform default

Operation costs

  • create: free
  • update: free
  • delete: free
  • get: free
  • list: free
  • invoke: 10000 micro-AU
  • tool_use: free

Set it up

Board Nametext
Descriptiontextarea