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.
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
| Property | Value |
|---|---|
| Type | board |
| Category | boards |
| Form | atom |
| Symbol | Bd |
| Icon | view_kanban / #6B7280 |
| Workbench | workbench-board-panel |
Properties
| Field | Type | Default | Description |
|---|---|---|---|
columns | array | [] | Kanban columns. Each requires id, name, phase (todo/in_progress/closed); optional position, color, wip_limit, weight, stage, entry_actions, exit_actions, preconditions |
card_schema | object | null | Optional JSON Schema (Draft 2020-12) validating each card’s metadata. Unset = free-form metadata |
card_schema_mode | string | strict | strict: reject metadata that fails card_schema. loose: record violations as warnings and store anyway |
card_display | object | null | Declarative card-face rendering; {{var}} templates over the card (metadata.* for metadata fields) |
card_actions | array | [] | Per-card action buttons. Each requires id, label, invokes (element_ref + op + optional input_template); optional icon, enabled_when (CEL) |
sample_cards | array | [] | 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
| Capability | Description |
|---|---|
kanban | Column-based card organisation with drag-and-drop and WIP limits |
card-schema | Optional JSON Schema validation of card metadata via card_schema + card_schema_mode |
card-display | Declarative card-face rendering via card_display templates |
card-actions | Per-card action buttons that invoke other elements via run-card-action |
column-hooks | entry_actions, exit_actions, and preconditions declared per column |
seed-cards | Idempotent sample-card seeding via the seed-cards op and spec.sample_cards |
move-history | Append-only audit trail of every card move with timestamps and actor |
Error Codes
| Code | Class | Retryable | Description |
|---|---|---|---|
BOARD_WIP_EXCEEDED | limit | No | Card move or create would exceed the target column’s wip_limit |
BOARD_COLUMN_NOT_FOUND | validation | No | column_id does not match any column on this board |
BOARD_COLUMN_NOT_EMPTY | validation | No | Cannot remove a column that still contains non-archived cards |
BOARD_SELF_REF | validation | No | A card cannot reference itself |
BOARD_CARD_NOT_FOUND | validation | No | Card does not exist or belongs to a different board |
BOARD_PRECONDITION_FAILED | validation | No | Column precondition expression returned false — card cannot enter |
BOARD_ACTION_NOT_FOUND | validation | No | run-card-action: action_id matches no declared card_action |
BOARD_ACTION_DISABLED | validation | No | run-card-action: the action’s enabled_when evaluated false for this card |
BOARD_SCHEMA_VALIDATION_FAILED | validation | No | Card metadata failed JSON Schema validation against card_schema (strict mode) |
BOARD_TARGET_ELEMENT_NOT_FOUND | validation | No | run-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
| Property | Type | Default | Description |
|---|---|---|---|
card_schema | object | null | 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_mode | string | "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_display | object | null | Phase 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_actions | array | [] | 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>/ 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, theaction is returned on _actions with enabled: false and a reason (theexpression) 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_cards | array | [] | 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. |
columns | array | [] | 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
metadataobject 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
weightfields, 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, checksenabled_whenagainst the card, renders the action’sinput_templatewith{{var}}substitution (context:{card}), looks up the target element viainvokes.element_ref(shape<element_type>/<slug>), and dispatchesinvokes.opon 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 viewmain.pyfor 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, andintentionare all independently optional.specMUST 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
metadatais 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 fromupdate) 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 byupdate_element_metastorage 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
| Code | Class | Retryable | Description |
|---|---|---|---|
BOARD_WIP_EXCEEDED | limit | no | Card move or create would exceed the target column’s wip_limit |
BOARD_COLUMN_NOT_FOUND | validation | no | column_id does not match any column on this board |
BOARD_COLUMN_NOT_EMPTY | validation | no | Cannot remove a column that still contains non-archived cards |
BOARD_SELF_REF | validation | no | A card cannot reference itself |
BOARD_CARD_NOT_FOUND | validation | no | Card does not exist or belongs to a different board |
BOARD_PRECONDITION_FAILED | validation | no | Column precondition expression returned false — card cannot enter this column |
BOARD_ACTION_NOT_FOUND | validation | no | run-card-action: action_id does not match any declared card_action on this board |
BOARD_ACTION_DISABLED | validation | no | run-card-action: card_action.enabled_when evaluated to false for this card |
BOARD_SCHEMA_VALIDATION_FAILED | validation | no | Card metadata failed JSON Schema validation against card_schema (strict mode) |
BOARD_TARGET_ELEMENT_NOT_FOUND | validation | no | run-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