Skip to main content

Playbooks

Versioned templates of steps that a case fills per subject, recomputing only what a changed value reaches.

Overview​

A playbook declares nodes: each has inputs, typed by JSON Schema, that a person writes or that read another node's field, and an output. A collect node's output is its inputs, an execute node runs an agent, a tool, a decider, a workflow or an orchestration, and a confirm node waits for a person to approve its inputs. A case is one subject — a client, a contract — filled against one version of the playbook over as long as it takes.

Every write that makes a node runnable runs it, and a changed output runs the nodes that read it, once each: Fill a case per client corrects one value and reruns only the goal that reads it. Final nodes render their output as files through transformation tools.

This module is a verbatim mirror of the runtime: every field, method, status code and error shape is the runtime's own, re-rooted under the project in the path.

See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Playbooks.

Data Model​

Playbook​

FieldTypeDescription
idstringPublic playbook ID.
project_idstringThe owning project.
namestringUnique in the project.
versionintegerIncremented by every write that changes definition.
definitionobject{ upstreams, nodes }; each node has id, title, kind, final, inputs, and an executor or transformations where its kind allows.

Case​

FieldTypeDescription
idstringPublic case ID.
playbook_idstring
playbook_versionintegerThe version the case runs on, until it is upgraded.
subjectstringWho or what the case is about.
actor_idstring, nullableThe end-user actor the case belongs to; null when none. Fixed at create.
upstreamsobjectThe case bound to each of the playbook's declared upstreams, by name. Fixed at create.
revisionintegerThe case's counter; every value written takes the next one.
automation_statusstringpaused, running while a node is queued or runs its executor, waiting while every node in flight waits on a task or orchestration run it started, else idle.
pause_requested_atstring (date-time), nullableWhen the case was paused.
nodesarrayOn a single-case read: every node's state, missing, blocked_by, run, inputs and output.

A node's state is computed on every read: blocked, ready, running, waiting, done, stale, failed or rejected. A blocked node names the nodes it waits on in blocked_by. A waiting node is parked on the task or orchestration run it started, named in run, read live: a task's state and automation_status (failed when its dispatch failed), or a run's status. run is null on every other node.

Key Concepts​

A case can read another case​

A definition may declare upstreams: { name, playbook_id } pairs naming other playbooks of the project. Creating a case then needs each name bound, in upstreams, to a case of that playbook with the same actor_id; anything else is 400 PLAYBOOK_CASE_UPSTREAM_INVALID. The binding is fixed, and many cases may share one upstream case. A monthly case binds the client's one company case, so the company's data is filled once.

An upstream node (upstream: { name, node_id }, no inputs, no executor) pulls that node's output from the bound case. The first pull is automatic once the node there is done; after a change upstream the node is stale until POST /v1/projects/{project_id}/playbook-cases/{case_id}/nodes/{node}/run pulls again. Later nodes read it with an ordinary { node_id, field } source.

Deleting a case other cases are bound to is 409 PLAYBOOK_CASE_HAS_DEPENDENTS, and deleting a playbook another playbook declares as an upstream is 409 PLAYBOOK_HAS_DEPENDENTS.

naturali create-playbook-case \
--project-id proj_V1StGXR8Z5jdHi6B \
--playbook-id pbk_V1StGXR8Z5jdHi6B \
--subject "ACME Ltda, 2026-09" \
--actor-id actor_V1StGXR8Z5jdHi6B \
--upstreams '{ "company": "pbk_case_V1StGXR8Z5jdHi6B" }'

A case belongs to an actor​

actor_id ties a case to an end user. GET /v1/projects/{project_id}/playbook-cases filters by actor_id and by an exact subject. Every route that acts on one case takes actor_id as a pin: a case of another actor, or of none, answers 404. Pin it in a tool so an agent reaches only the cases of the person it serves.

Writes carry the revision they read​

POST /v1/projects/{project_id}/playbook-cases/{case_id}/inputs writes a batch, all or none. Each value names the revision of the field it read in expected_revision; a field written since answers 409 and nothing is written. A confirmation names the revision of each input it decided on the same way.

A write can wait for the nodes it starts​

Writing inputs, confirming a node and upgrading a case answer once the write is done, while the nodes it makes runnable run in the background. With wait: true they answer once every node the write started, and every node those start in turn, has settled or parked on a task or run, and return the case as it then stands. A paused case answers at once.

Running a node by hand​

POST /v1/projects/{project_id}/playbook-cases/{case_id}/nodes/{node}/run runs a ready, stale, failed or done node now. It is how a failed node is retried, and how a done node is recomputed after an upgrade changed its executor; an output equal to the current one changes nothing downstream.

Entries say who wrote them​

Every entry, execution, export and upgrade carries created_by_principal_type and created_by_principal_id, and on_behalf_of when a naturali tool wrote it: the tool, the agent and the generation.

Nodes run as the version's author​

A node's executor, and each export's tool, runs as the credential that wrote the case's playbook version, with that credential's permissions as they stand when it runs. A case also refuses writes from its own work: an executor's result reaches its case only as the node's output. A write made with that credential to the case, or to a case reading it as an upstream at any depth, is 403 PLAYBOOK_SELF_WRITE.

Exports render a final node​

POST /v1/projects/{project_id}/playbook-cases/{case_id}/nodes/{node}/exports calls each transformation's tool on one revision of the node's output and files what it returns in the project's files. An export never feeds a node, and is stale once the node's output moves past it.

Upgrading a case​

A case stays on the version it was created on. POST /v1/projects/{project_id}/playbook-cases/{case_id}/upgrade moves it to another version, keeping values by node ID and recording what moved.

A negative balance stops what a case would run​

A case runs nodes on the writes that make one runnable, so creating a case, writing inputs, running a node, confirming, exporting, resuming and upgrading answer 402 insufficient_credit and run nothing while the project owes for usage already served. It is the project owner's balance. See A negative balance stops managed generation. A Free account that has used its plan's monthly runs answers 403 plan_limit_reached with resource: "runs" on the same routes. See A plan's run allowance can stop generation.

Reading and pausing are never refused. POST /v1/projects/{project_id}/playbook-cases/{case_id}/pause stops a case starting anything; what is already running settles.

A cascade already running is paused instead. The reconciliation that discovers the debt pauses every case whose automation_status is running; restore it from the account's stops once the balance is positive. A case waiting on a task or run it started is left alone: that task or run is what spends, and it is stopped on its own.

An export counts toward storage​

An export adds a file, so at the account's storage ceiling it answers 403 plan_limit_reached with resource: "storage". See Files.

Who may do what​

Every route needs any project member.

Examples​

Create a playbook​

naturali create-playbook \
--project-id proj_V1StGXR8Z5jdHi6B \
--name client-pricing \
--definition '{
"nodes": [
{ "id": "revenue", "title": "Annual revenue", "kind": "collect",
"inputs": [{ "name": "amount", "schema": { "type": "number" },
"required": true, "source": "manual" }] },
{ "id": "pricing", "title": "Pricing", "kind": "collect", "final": true,
"inputs": [{ "name": "revenue", "schema": { "type": "number" },
"required": true,
"source": { "node_id": "revenue", "field": "output.amount" } }] }
]
}'

Open a case and fill it​

naturali create-playbook-case \
--project-id proj_V1StGXR8Z5jdHi6B \
--playbook-id pbk_V1StGXR8Z5jdHi6B \
--subject "ACME Ltda"

naturali write-playbook-case-inputs \
--project-id proj_V1StGXR8Z5jdHi6B \
--case-id pbk_case_V1StGXR8Z5jdHi6B \
--inputs '[{ "node_id": "revenue", "name": "amount", "value": 4000000, "expected_revision": null }]'

Read where a case stands​

naturali get-playbook-case \
--project-id proj_V1StGXR8Z5jdHi6B \
--case-id pbk_case_V1StGXR8Z5jdHi6B