Topolo Docs Topolo Platform
Open workspace

List messages awaiting approval

Inspect the exact rendered messages currently waiting for approval.

Outcome

Inspect the exact rendered messages currently waiting for approval.

Before you start

  • Verify the environment, credential, authorized organization and the published action permission workspace:read.
  • Discover an accessible Engage workspace and bind resourceType=workspace and its resourceId separately from action input. Fixture identifiers grant no access.

Discover the target

  • Discover and select an authorized Engage workspace. The read requires no message identifier.
  • Optionally filter with campaignId returned by campaigns.list. The queue is limited to 500 messages and has no declared continuation cursor.
topolo whoami --json
topolo actions get app_topolo_engage.approvals.list --json
topolo workspaces list topolo-engage --json

Input fields

FieldRequiredMeaning and constraints
campaignIdNoOptional campaign identifier discovered in this workspace. Omit to read the workspace queue, up to 500 pending messages.

Validate, plan and execute

Set ENGAGE_WORKSPACE_ID to the identifier of your selected workspace. Prepare payload.json from the current input schema and discovered identifiers. Use the same payload for validation, planning and execution.

: "${ENGAGE_WORKSPACE_ID:?Set ENGAGE_WORKSPACE_ID to your selected workspace identifier}"
topolo actions validate app_topolo_engage.approvals.list --data @payload.json --json
topolo actions plan app_topolo_engage.approvals.list --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --json

Execute the unchanged validated read in the selected workspace.

: "${ENGAGE_WORKSPACE_ID:?Set ENGAGE_WORKSPACE_ID to your selected workspace identifier}"
topolo actions call app_topolo_engage.approvals.list --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --json

For MCP, use topolo_get_action, topolo_validate_action, topolo_plan_action and topolo_call_action. Pass the selected resource context separately; confirm:true is required for mutations.

Worked requests and responses

Captured approvals.list · HTTP 200

Action input

{}

Actual local response

{
  "messages": [
    {
      "id": "eng_msg_1",
      "campaignId": "eng_cmp_1",
      "enrollmentId": "eng_enr_1",
      "stepId": "eng_stp_1",
      "stepPosition": 1,
      "version": 1,
      "previousMessageId": null,
      "subject": "Quick question, Ada",
      "text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
      "status": "scheduled",
      "approvalState": "pending_approval",
      "policyVersion": 1,
      "batchDate": "2026-10-06",
      "approvedBy": null,
      "approvedAt": null,
      "approvalNote": null,
      "scheduledFor": "2026-10-06T08:30:00.000Z",
      "footerSource": "manual",
      "brandKitRef": null,
      "brandVersionRef": null
    }
  ],
  "workspace": {
    "emergencyStop": false,
    "reason": null,
    "updatedBy": null,
    "updatedAt": null
  }
}

Source test: message approval > the runner never sends an unapproved message: it parks it for approval, then sends it once approved. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Captured approvals.list · HTTP 200

Action input

{}

Actual local response

{
  "messages": [
    {
      "id": "eng_msg_3",
      "campaignId": "eng_cmp_1",
      "enrollmentId": "eng_enr_1",
      "stepId": "eng_stp_1",
      "stepPosition": 1,
      "version": 3,
      "previousMessageId": "eng_msg_2",
      "subject": "New subject",
      "text": "Final text.",
      "status": "scheduled",
      "approvalState": "pending_approval",
      "policyVersion": 1,
      "batchDate": "2026-10-06",
      "approvedBy": null,
      "approvedAt": null,
      "approvalNote": null,
      "scheduledFor": "2026-10-06T08:30:00.000Z",
      "footerSource": "manual",
      "brandKitRef": null,
      "brandVersionRef": null
    }
  ],
  "workspace": {
    "emergencyStop": false,
    "reason": null,
    "updatedBy": null,
    "updatedAt": null
  }
}

Source test: message approval > request_edit creates a new immutable version with its own idempotency key and keeps the previous one unchanged. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Captured approvals.list · HTTP 200

Action input

{}

Actual local response

{
  "messages": [],
  "workspace": {
    "emergencyStop": true,
    "reason": "Incident",
    "updatedBy": "user_1",
    "updatedAt": "2026-10-06T08:30:00.000Z"
  }
}

Source test: emergency stop and seat allowlist > the workspace stop holds every campaign until resumed. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Captured approvals.list · HTTP 200

Action input

{}

Actual local response

{
  "messages": [
    {
      "id": "eng_msg_1",
      "campaignId": "eng_cmp_1",
      "enrollmentId": "eng_enr_1",
      "stepId": "eng_stp_1",
      "stepPosition": 1,
      "version": 1,
      "previousMessageId": null,
      "subject": "Quick question, Ada",
      "text": "Hi Ada,\n\nHow is Analytical handling outreach?\n\nAsh\n\n--\nTest opt-out sentence for fixtures.\nExample Ltd\n1 Example Street\nLondon",
      "status": "scheduled",
      "approvalState": "pending_approval",
      "policyVersion": 1,
      "batchDate": "2026-10-05",
      "approvedBy": null,
      "approvedAt": null,
      "approvalNote": null,
      "scheduledFor": "2026-10-06T07:00:00.000Z",
      "footerSource": "manual",
      "brandKitRef": null,
      "brandVersionRef": null
    }
  ],
  "workspace": {
    "emergencyStop": false,
    "reason": null,
    "updatedBy": null,
    "updatedAt": null
  }
}

Source test: render ahead for approval > queues a message due tomorrow today, sends it in the window once approved, and never renders twice. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Captured approvals.list · HTTP 200

Action input

{}

Actual local response

{
  "messages": [],
  "workspace": {
    "emergencyStop": false,
    "reason": null,
    "updatedBy": null,
    "updatedAt": null
  }
}

Source test: render ahead for approval > fails closed: an incomplete footer or an unresolved merge field is held with its reason, never queued. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Captured approvals.list · HTTP 200

Action input

{
  "campaignId": "eng_cmp_other"
}

Actual local response

{
  "messages": [],
  "workspace": {
    "emergencyStop": false,
    "reason": null,
    "updatedBy": null,
    "updatedAt": null
  }
}

Source test: message approval > the runner never sends an unapproved message: it parks it for approval, then sends it once approved. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Effects and response meaning

  • Returns messages[] ordered by campaign, step position, batch date and id, plus the workspace emergency-stop state. Only scheduled pending-approval rows are included.
  • Read exact subject and text, footer provenance and policyVersion before any decision. An empty queue is not a complete history of approved, rejected or sent messages.

Verify the result

  • Verify every returned row is the intended campaign, version and recipient enrollment before choosing an action. Retain messages[].enrollmentId to read subsequent message history.

Recover

Retry the read in the same verified context after a transient failure. When a retained message disappears, read enrollments.messages.list rather than guessing why it left the queue.

Refused responses

No refusal response for this action is included in this fixture set. The recovery guidance below is based on the route’s declared permission and input-validation checks.

Error recovery

  • HTTP 403 permission_denied: Stop and inspect the action permission in the current credential; do not broaden permissions or switch contexts automatically.
  • HTTP 422 invalid_action_input: Correct the input against the current schema and validate again before planning.

Evidence and limits

  • contracts/autonomy.mjs

  • contracts/sending.mjs

  • apps/api/src/autonomy/approvals-handlers.ts

  • apps/api/src/autonomy/approvals-repository.ts

  • apps/api/src/autonomy/policy-service.ts

  • apps/api/test/autonomy.test.ts

  • Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

  • Widget/bootstrap use actual app handler but mocked worker runtime and app identity.

  • No gateway execution, live permissions, external delivery or deployed proof.

The examples are local route captures with synthetic context and simulated providers. They do not prove live authorization, CLI/MCP gateway execution, production state or delivery.