Named Procedures
Store a named, parameterized transaction template on the server and call it by name. The ops run through the standard atomic transact path — all succeed or none do. Parameters are substituted at call time using $param string placeholders in the ops JSON.
When to use procedures
For one-off writes, direct writes or transact are simpler. Procedures shine when:
- Multiple callers execute the same business-logic pattern (transfer funds, transition a workflow state, record an event with a side-effect write).
- You want the ops validated and stored once rather than assembled client-side on every call.
- You are calling SapixDB from an environment where sending a full ops array is cumbersome (edge workers, mobile clients, scripts).
Defining a procedure
Send a POST to /v1/procedures with a name and an ops array. The ops use the same shape as a transact request, with "$param_name" string values as placeholders for call-time substitution.
POST /v1/procedures
Authorization: Bearer spx_root_YOUR_ROOT_KEY
Content-Type: application/json
{
"name": "transfer_funds",
"ops": [
{
"op": "write",
"agent": "ledger",
"data": {
"type": "debit",
"account": "$from_account",
"amount": "$amount",
"reference": "$reference"
}
},
{
"op": "write",
"agent": "ledger",
"data": {
"type": "credit",
"account": "$to_account",
"amount": "$amount",
"reference": "$reference"
}
}
]
}{
"name": "transfer_funds",
"ops": [ ... ],
"created_at_ms": 1756224000000,
"updated_at_ms": 1756224000000
}Re-posting with the same name replaces the procedure (200 OK). The created_at_ms is preserved.
Parameter substitution
At call time, every string value in the ops tree that starts with $ is replaced with the corresponding value from the params map. Substitution is recursive — it works inside nested objects and arrays. Non-string values (numbers, booleans, null) are passed through unchanged; objects and arrays are traversed but not replaced themselves.
| Placeholder type | Example ops value | params value | Result |
|---|---|---|---|
| string param | "$status" | "params": {"status": "active"} | "active" |
| numeric param | "$amount" | "params": {"amount": 49.99} | 49.99 (number) |
| boolean param | "$enabled" | "params": {"enabled": true} | true (boolean) |
| missing param | "$unknown" | (not in params) | "$unknown" (unchanged) |
Calling a procedure
POST /v1/procedures/transfer_funds/call
Authorization: Bearer spx_root_YOUR_ROOT_KEY
Content-Type: application/json
{
"params": {
"from_account": "acc_alice",
"to_account": "acc_bob",
"amount": 250.00,
"reference": "inv_9981"
}
}{
"committed": true,
"results": [
{
"index": 0,
"agent": "ledger",
"record_id": "018f3c2a-4b1d-...",
"content_hash": "3a7bd3f1c2e9...",
"chain_head": "9f1a2b3c4d5e..."
},
{
"index": 1,
"agent": "ledger",
"record_id": "018f3c2a-4b1e-...",
"content_hash": "7c8d9e0f1a2b...",
"chain_head": "2b3c4d5e6f7a..."
}
]
}POST /v1/transact. All ops succeed or none do. If a precondition fails, the response has committed: false and a failed_op_index.HTTP API Reference
| Method | Path | Description |
|---|---|---|
| POST | /v1/procedures | Define or replace a procedure. Returns 201 (new) or 200 (updated). |
| GET | /v1/procedures | List all procedures sorted by name. |
| GET | /v1/procedures/:name | Get one procedure definition. 404 if not found. |
| DELETE | /v1/procedures/:name | Delete a procedure. Returns 204. |
| POST | /v1/procedures/:name/call | Execute with params. Returns TransactResponse. |
Define request body
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | yes | Unique procedure name. Must not be empty. |
| ops | array | yes | Transact ops with $param placeholders. Must not be empty. |
Call request body
| Field | Type | Required | Description |
|---|---|---|---|
| params | object | no | Key-value map substituted into the ops. Defaults to empty object. |
Storage
Procedure definitions are stored in the CF_META column family of the _crons system agent at key proc_def:{name}. They persist across restarts. There is no upper limit on the number of procedures, but each ops array must deserialize into a valid transact request — invalid ops are rejected at definition time.
Authentication and Scopes
Creating, replacing, and deleting procedures requires a root key or a key with write:agents/_crons. Calling a procedure goes through the transact auth check — the caller needs write:agents/:id for each agent the ops touch. Listing and reading procedure definitions requires read:agents/_crons.