Topolo Docs Topolo Platform
Open workspace

List outreach campaigns

Find outreach campaigns and their actual identifiers in the selected Engage workspace.

Outcome

Find outreach campaigns and their actual identifiers in the selected Engage workspace.

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

  • Select an authorized canonical Engage workspace separately from input. No campaign identifier is needed.
  • Use status=archived to find archived campaigns; the default query excludes them. Other status values select exactly that status.
topolo whoami --json
topolo actions get app_topolo_engage.campaigns.list --json
topolo workspaces list topolo-engage --json

Input fields

FieldRequiredMeaning and constraints
statusNoOptional exact status: draft, active, paused, completed or archived. Omit to exclude archived campaigns.

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.campaigns.list --data @payload.json --json
topolo actions plan app_topolo_engage.campaigns.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.campaigns.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 campaigns.list · HTTP 200

Action input

{}

Actual local response

{
  "campaigns": [
    {
      "id": "eng_cmp_1",
      "name": "Q4 outreach",
      "goal": "Book demos",
      "channel": "email",
      "status": "draft",
      "isTest": false,
      "createdBy": "user_1",
      "updatedBy": "user_1",
      "createdAt": "2026-10-01T00:00:00.000Z",
      "updatedAt": "2026-10-01T00:00:00.000Z",
      "archivedAt": null
    }
  ]
}

Source test: campaign routes > creates, lists, reads, edits and transitions a campaign (happy path). Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Captured campaigns.list · HTTP 200

Action input

{}

Actual local response

{
  "campaigns": []
}

Source test: campaign routes > returns 404 for a campaign that lives in another workspace. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Captured campaigns.list · HTTP 200

Action input

{}

Actual local response

{
  "campaigns": [
    {
      "id": "eng_cmp_1",
      "name": "x",
      "goal": null,
      "channel": "email",
      "status": "draft",
      "isTest": false,
      "createdBy": "user_1",
      "updatedBy": "user_1",
      "createdAt": "2026-10-01T00:00:00.000Z",
      "updatedAt": "2026-10-01T00:00:00.000Z",
      "archivedAt": null
    }
  ]
}

Source test: campaign routes > falls back to the first verified workspace when none is marked default. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Effects and response meaning

  • Reads workspace-scoped campaign rows, ordered by createdAt descending and then id descending. The response is campaigns[], including an empty array when no rows match.
  • This action does not paginate or change campaign state. An empty result does not prove the workspace has no archived campaigns.

Verify the result

  • Check the requested status against each returned campaigns[].status. Use campaigns[].id as the input id for campaigns.get; retain the same workspace binding.

Recover

Retry this read in the same verified resource context after a transient failure. If a campaign is missing, inspect status=archived before concluding it was removed. Never substitute a guessed identifier.

Refused responses

Captured campaigns.list · HTTP 403

Action input

{}

Actual local response

{
  "error": {
    "code": "permission_denied",
    "message": "The credential lacks workspace:read.",
    "details": {
      "requiredPermission": "workspace:read"
    }
  }
}

Source test: campaign routes > refuses writes without workspace:write (403) but allows reads. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Captured campaigns.list · HTTP 403

Action input

{}

Actual local response

{
  "error": {
    "code": "organization_required",
    "message": "This action needs an organization context."
  }
}

Source test: campaign routes > requires an organization context. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Captured campaigns.list · HTTP 403

Action input

{}

Actual local response

{
  "error": "workspace_forbidden"
}

Source test: campaign routes > denies a workspace the caller holds no grant for. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Captured campaigns.list · HTTP 422

Refused input; do not execute

{
  "status": "sent"
}

Actual local response

{
  "error": {
    "code": "invalid_action_input",
    "message": "Input for campaigns.list is invalid.",
    "details": [
      {
        "code": "invalid_value",
        "values": [
          "draft",
          "active",
          "paused",
          "completed",
          "archived"
        ],
        "path": [
          "status"
        ],
        "message": "Invalid option: expected one of \"draft\"|\"active\"|\"paused\"|\"completed\"|\"archived\""
      }
    ]
  }
}

Source test: campaign routes > returns 422 for input the contract refuses. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Captured campaigns.list · HTTP 409

Action input

{}

Actual local response

{
  "error": {
    "code": "workspace_required",
    "message": "Create an Engage workspace before using this action."
  }
}

Source test: campaign routes > refuses when the caller has no workspace at all. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Error recovery

  • HTTP 403 permission_denied: Stop and inspect the permission required by this action in the current credential. Do not broaden permissions or switch organization automatically.
  • HTTP 403 organization_required: Establish the intended authorized organization context before another request. Organization identity is not an action-input field.
  • HTTP 403 workspace_forbidden: Discover an accessible Engage workspace with the current credential and bind that resource explicitly; do not retry the denied fixture identifier.
  • HTTP 422 invalid_action_input: Correct the request against the current input schema and validate it again before planning.
  • HTTP 409 workspace_required: Discover an accessible Engage workspace. If none exists, workspace creation is a separate approved operation; this campaign action cannot create one.

Evidence and limits

  • contracts/campaigns.mjs

  • apps/api/src/campaigns/campaigns-routes.ts

  • apps/api/src/campaigns/campaigns-handlers.ts

  • apps/api/src/campaigns/campaigns-domain.ts

  • apps/api/src/campaigns/campaigns-repository.ts

  • apps/api/test/campaigns-routes.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.