Pending Actions

Approve, reject, or batch-approve AI-proposed writes queued by @gated tools.

Overview

Every write-classified tool call from the Praxiom agent is intercepted by the @gated decorator and persisted as a pending action with status = "pending" instead of executing. This endpoint group lets workspace members list, inspect, approve, reject, or batch-resolve those pending rows.

All routes are mounted under /api/pending-actions and require authentication. Every route verifies that the caller is a member of the owning workspace — non-members get 403 FORBIDDEN.

See the Pending Actions guide for the conceptual model.

Base URL: https://api.praxiomai.xyz

Authentication: Authorization: Bearer <token>

Content type: Request and response bodies are camelCase on the wire; the server accepts snake_case on input as well.


Resource Shape

{
  "id": "uuid",
  "workspaceId": "uuid",
  "missionId": "uuid | null",
  "sessionId": "string | null",
  "batchId": "{missionId}:{toolName}",
  "toolName": "save_recommendations",
  "toolInput": { "...": "raw kwargs the agent passed" },
  "preview": { "...": "render-ready preview for the approval UI" },
  "status": "pending | approved | rejected | executed | failed",
  "userEdits": { "...": "optional edits to merge into toolInput" } ,
  "result": { "...": "populated when status=executed" },
  "error": "string | null (populated when status=failed)",
  "createdAt": "ISO-8601",
  "resolvedAt": "ISO-8601 | null",
  "executedAt": "ISO-8601 | null"
}

List Pending Actions

GET /api/pending-actions

List pending actions for a workspace, newest first. Optionally filter by status.

Query Params

NameTypeRequiredDescription
workspace_idUUIDYesWorkspace to scope results to. Caller must be a member.
statusstringNoFilter by status. One of pending, approved, rejected, executed, failed.

Response

{
  "items": [
    {
      "id": "fb93a0ea-2d1c-4f3d-8b3a-7e31d5f4bc11",
      "workspaceId": "550e8400-e29b-41d4-a716-446655440000",
      "missionId": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
      "sessionId": "ses_abc123",
      "batchId": "a1b2c3d4-e5f6-7890-abcd-ef0123456789:save_recommendations",
      "toolName": "save_recommendations",
      "toolInput": { "workspace_id": "550e...", "recommendations": [] },
      "preview": { "count": 1, "items": [] },
      "status": "pending",
      "userEdits": null,
      "result": null,
      "error": null,
      "createdAt": "2026-04-21T18:02:11.420Z",
      "resolvedAt": null,
      "executedAt": null
    }
  ],
  "total": 1
}

Curl

curl -H "Authorization: Bearer $TOKEN" \
  "https://api.praxiomai.xyz/api/pending-actions?workspace_id=550e8400-e29b-41d4-a716-446655440000&status=pending"

Get Pending Action

GET /api/pending-actions/{pa_id}

Fetch a single pending action by id.

Path Params

NameTypeDescription
pa_idUUIDPending action id.

Responses

  • 200 — the full pending action resource (see shape above).
  • 404 NOT_FOUND — no such id.
  • 403 FORBIDDEN — caller is not a member of the owning workspace.

Curl

curl -H "Authorization: Bearer $TOKEN" \
  "https://api.praxiomai.xyz/api/pending-actions/fb93a0ea-2d1c-4f3d-8b3a-7e31d5f4bc11"

Approve Pending Action

POST /api/pending-actions/{pa_id}/approve

Approve a pending action and schedule its execution. The DB row is committed as approved before execution is scheduled; the HTTP response returns immediately without waiting for the downstream API call.

Path Params

NameTypeDescription
pa_idUUIDPending action id. Must currently be in pending status.

Request Body

NameTypeRequiredDescription
userEditsobjectNoOptional dict of fields to shallow-merge into toolInput before execution. Use to correct titles, trim text, or swap metadata.
{
  "userEdits": {
    "recommendations": [
      { "title": "Streamline onboarding", "effort_weeks": 2 }
    ]
  }
}

Responses

  • 200 — the updated pending action with status = "approved", resolvedAt set, and userEdits persisted. Execution runs asynchronously; poll the row to observe the transition to executed or failed.
  • 404 NOT_FOUND — no such id.
  • 403 FORBIDDEN — caller is not a member of the owning workspace.
  • 409 INVALID_STATE — the row is not in pending status (already approved, rejected, or resolved).

Response Example

{
  "id": "fb93a0ea-2d1c-4f3d-8b3a-7e31d5f4bc11",
  "workspaceId": "550e8400-e29b-41d4-a716-446655440000",
  "toolName": "save_recommendations",
  "status": "approved",
  "userEdits": {
    "recommendations": [
      { "title": "Streamline onboarding", "effort_weeks": 2 }
    ]
  },
  "resolvedAt": "2026-04-21T18:04:55.011Z",
  "executedAt": null,
  "result": null,
  "error": null
}

Curl

curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"userEdits": {"recommendations": [{"title": "Streamline onboarding", "effort_weeks": 2}]}}' \
  "https://api.praxiomai.xyz/api/pending-actions/fb93a0ea-2d1c-4f3d-8b3a-7e31d5f4bc11/approve"

Approval is commit-then-schedule. If the process restarts between the commit and the executor task firing, the row is orphaned in approved state with no result. A reconciler will re-enqueue such rows in a future iteration.


Reject Pending Action

POST /api/pending-actions/{pa_id}/reject

Reject a pending action. The row transitions to rejected and the underlying handler is never invoked. Rejected rows stay in the database as an audit trail.

Path Params

NameTypeDescription
pa_idUUIDPending action id. Must currently be in pending status.

Request Body

None.

Responses

  • 200 — the updated pending action with status = "rejected" and resolvedAt set.
  • 404 NOT_FOUND — no such id.
  • 403 FORBIDDEN — caller is not a member of the owning workspace.
  • 409 INVALID_STATE — the row is not in pending status.

Response Example

{
  "id": "fb93a0ea-2d1c-4f3d-8b3a-7e31d5f4bc11",
  "toolName": "save_recommendations",
  "status": "rejected",
  "resolvedAt": "2026-04-21T18:05:22.142Z",
  "executedAt": null,
  "result": null,
  "error": null
}

Curl

curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  "https://api.praxiomai.xyz/api/pending-actions/fb93a0ea-2d1c-4f3d-8b3a-7e31d5f4bc11/reject"

Batch Approve

POST /api/pending-actions/batch/{batch_id}/approve

Resolve multiple pending actions in a single batch. A batch_id has the form {mission_id}:{tool_name} — for example, a1b2c3d4-e5f6-7890-abcd-ef0123456789:save_recommendations. Every row in a batch shares the same workspace_id, so authorization is checked once against the first row.

Path Params

NameTypeDescription
batch_idstringBatch identifier in the form {mission_id}:{tool_name}.

Request Body

NameTypeRequiredDescription
itemsarray of objectYesPer-row instructions. Rows not listed here are left pending (default-skip).
items[].pendingActionIdUUIDYesThe target row id (must belong to this batch).
items[].userEditsobjectNoOptional shallow-merge edits applied on approve.
items[].excludebooleanNoWhen true, reject this row instead of approving. Default false.
{
  "items": [
    { "pendingActionId": "pa-1", "userEdits": { "priority": 2 } },
    { "pendingActionId": "pa-2" },
    { "pendingActionId": "pa-3", "exclude": true }
  ]
}

Rows that belong to the batch but are not listed in items are left pending (default-skip). This prevents silent data loss if the frontend omits an item — but it also means callers must explicitly list every row they want resolved.

Responses

  • 200 — a summary of what was resolved:
{
  "batchId": "a1b2c3d4-e5f6-7890-abcd-ef0123456789:save_recommendations",
  "approved": 2,
  "rejected": 1,
  "skipped": 0
}
  • skipped counts rows that were resolved by another session between the list and the mutate. Partial progress is preserved instead of 500ing.

  • Rows already in a non-pending status when the batch runs are silently left alone — they do not count toward approved, rejected, or skipped.

  • 404 NOT_FOUND — batch is empty or does not exist.

  • 403 FORBIDDEN — caller is not a member of the owning workspace.

  • 500 INVARIANT_VIOLATION — rows in the batch span multiple workspaces (defense-in-depth; should never happen in practice).

After a successful batch, approved rows are scheduled for execution fire-and-forget. Rejected rows are final.

Curl

curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {"pendingActionId": "pa-1", "userEdits": {"priority": 2}},
      {"pendingActionId": "pa-2"},
      {"pendingActionId": "pa-3", "exclude": true}
    ]
  }' \
  "https://api.praxiomai.xyz/api/pending-actions/batch/a1b2c3d4-e5f6-7890-abcd-ef0123456789:save_recommendations/approve"

Error Codes

HTTPCodeWhen
403FORBIDDENCaller is not a member of the owning workspace.
404NOT_FOUNDPending action id or batch id does not exist.
409INVALID_STATEAttempted to approve or reject a row not in pending status.
422validationBad status filter or malformed request body.
500INVARIANT_VIOLATIONBatch unexpectedly spans multiple workspaces.

  • Pending Actions guide — conceptual model, @gated decorator, gated tool list, lifecycle.
  • Streaming (SSE)briefing events surface outstanding pending-action counts on conversation resumption.
  • Missions — pending actions queued during a mission subtask are grouped by batch_id = {mission_id}:{tool_name} for bulk approval.

Was this helpful?