Agent actions
The approval queue. Agents propose; people decide. Any action that moves money or crosses a threshold waits here for a human.
The agent_action object
Every object carries an id, timestamps, and an audit_trail. Money is integer cents. Expand any node in the explorer to see how it links to the rest of the record.
- idstringUnique, stable identifier. Prefixed by object type.
- objectstringString naming the object type.
- projectstringProject ID.
- agentstringAgent ID.
- kindstringWhat it wants to do, e.g. invoice.code, change_order.draft.
- statusenumpending_approval · approved · rejected · applied · expired.
- targetstringObject the action changes.
- proposalobjectThe exact write that will apply on approval.
- reasoningstringPlain-language why, shown to the approver.
- confidencenumber0 to 1.
- requires_rolestringRole allowed to decide.
- decided_bystringUser who decided.
- created_attimestampISO 8601, UTC.
- updated_attimestampISO 8601, UTC. Changes on every write.
- audit_trailstringPath to the append-only history of who changed what, and when.
object{16}
- id"act_8812"id
- object"agent_action"string
- project"prj_24118"id
- agent"agt_ap_coding"id
- kind"invoice.code"string
- status"pending_approval"string
- target"inv_4471"id
proposalobject{3}
- cost_code"cc_260500"id
- commitment"cmt_po118"id
- amount4820000integer$48,200.00
- reasoning"Vendor, PO number and line descriptions match PO-118. Amount exceeds remaining commitment by $6,150.00, so a human must approve."string
- confidence0.93number
- requires_role"project_accountant"string
- decided_bynullnull
- decided_atnullnull
- created_at
- updated_at
- audit_trail"/v1/agents/actions/act_8812/audit"string
/v1/agents/actionsList agent actions
Pull your approval queue.
Parameters
- projectstringqueryID of the project, e.g. prj_24118.
- statusenumqueryDefault pending_approval.
- limitintegerqueryPage size, 1 to 100. Default 25.
- cursorstringqueryCursor from a previous page’s next_cursor.
Returns
A paginated list of agent_action objects. Errors use the standard error shape.
curl https://api.os.construction/v1/agents/actions \
-H "Authorization: Bearer $OS_API_KEY"const res = await fetch("https://api.os.construction/v1/agents/actions", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.OS_API_KEY}`,
},
});
const page = await res.json();import os, uuid, requests
res = requests.get(
"https://api.os.construction/v1/agents/actions",
headers={
"Authorization": f"Bearer {os.environ['OS_API_KEY']}",
},
)
res.raise_for_status()
data = res.json(){
"object": "list",
"url": "/v1/agents/actions",
"has_more": false,
"next_cursor": null,
"data": [
{
"id": "act_8812",
"object": "agent_action",
"project": "prj_24118",
"agent": "agt_ap_coding",
"kind": "invoice.code",
"status": "pending_approval",
"target": "inv_4471",
"proposal": {
"cost_code": "cc_260500",
"commitment": "cmt_po118",
"amount": 4820000
},
"reasoning": "Vendor, PO number and line descriptions match PO-118. Amount exceeds remaining commitment by $6,150.00, so a human must approve.",
"confidence": 0.93,
"requires_role": "project_accountant",
"decided_by": null,
"decided_at": null,
"created_at": "2026-09-29T13:12:04Z",
"updated_at": "2026-09-29T13:12:04Z",
"audit_trail": "/v1/agents/actions/act_8812/audit"
}
]
}/v1/agents/actions/{id}/approveApprove an action
Applies the proposal exactly as shown. User keys only; an agent can never approve its own action.
Idempotent. Send an Idempotency-Key; retries within 24 hours return the original result and never write twice.
Parameters
- idstringpathrequiredThe agent action ID, e.g. act_8812.
- Idempotency-KeystringheaderrequiredAny unique string. Replays return the first result instead of writing twice.
- notestringbodyNote for the audit trail.
Returns
The agent_action object. Errors use the standard error shape.
curl -X POST https://api.os.construction/v1/agents/actions/act_8812/approve \
-H "Authorization: Bearer $OS_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"note": "Overage covered by pending PCO"
}'const res = await fetch("https://api.os.construction/v1/agents/actions/act_8812/approve", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OS_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
"note": "Overage covered by pending PCO"
}),
});
const obj = await res.json();import os, uuid, requests
res = requests.post(
"https://api.os.construction/v1/agents/actions/act_8812/approve",
headers={
"Authorization": f"Bearer {os.environ['OS_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"note": "Overage covered by pending PCO"
},
)
res.raise_for_status()
data = res.json(){
"id": "act_8812",
"object": "agent_action",
"project": "prj_24118",
"agent": "agt_ap_coding",
"kind": "invoice.code",
"status": "applied",
"target": "inv_4471",
"proposal": {
"cost_code": "cc_260500",
"commitment": "cmt_po118",
"amount": 4820000
},
"reasoning": "Vendor, PO number and line descriptions match PO-118. Amount exceeds remaining commitment by $6,150.00, so a human must approve.",
"confidence": 0.93,
"requires_role": "project_accountant",
"decided_by": "usr_pat_pa",
"decided_at": "2026-09-29T15:40:00Z",
"created_at": "2026-09-29T13:12:04Z",
"updated_at": "2026-09-29T13:12:04Z",
"audit_trail": "/v1/agents/actions/act_8812/audit"
}/v1/agents/actions/{id}/rejectReject an action
Nothing is written. Your reason is fed back to the agent.
Idempotent. Send an Idempotency-Key; retries within 24 hours return the original result and never write twice.
Parameters
- idstringpathrequiredThe agent action ID, e.g. act_8812.
- Idempotency-KeystringheaderrequiredAny unique string. Replays return the first result instead of writing twice.
- reasonstringbodyrequiredWhy.
Returns
The agent_action object. Errors use the standard error shape.
curl -X POST https://api.os.construction/v1/agents/actions/act_8812/reject \
-H "Authorization: Bearer $OS_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"reason": "Bill against PO-118 change, not base PO"
}'const res = await fetch("https://api.os.construction/v1/agents/actions/act_8812/reject", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OS_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
"reason": "Bill against PO-118 change, not base PO"
}),
});
const obj = await res.json();import os, uuid, requests
res = requests.post(
"https://api.os.construction/v1/agents/actions/act_8812/reject",
headers={
"Authorization": f"Bearer {os.environ['OS_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"reason": "Bill against PO-118 change, not base PO"
},
)
res.raise_for_status()
data = res.json(){
"id": "act_8812",
"object": "agent_action",
"project": "prj_24118",
"agent": "agt_ap_coding",
"kind": "invoice.code",
"status": "rejected",
"target": "inv_4471",
"proposal": {
"cost_code": "cc_260500",
"commitment": "cmt_po118",
"amount": 4820000
},
"reasoning": "Vendor, PO number and line descriptions match PO-118. Amount exceeds remaining commitment by $6,150.00, so a human must approve.",
"confidence": 0.93,
"requires_role": "project_accountant",
"decided_by": "usr_pat_pa",
"decided_at": "2026-09-29T15:40:00Z",
"created_at": "2026-09-29T13:12:04Z",
"updated_at": "2026-09-29T13:12:04Z",
"audit_trail": "/v1/agents/actions/act_8812/audit"
}Events
Webhooks fire on every state change of a agent_action.