Topolo Docs Topolo Platform
Open workspace

Remove contacts from list

Remove selected memberships while keeping the prospect contacts.

Outcome

Remove selected memberships while keeping the prospect contacts.

Before you start

  • Verify the environment, credential, authorized organization and the published action permission workspace:write.
  • Discover an accessible Engage workspace and bind resourceType=workspace and its resourceId separately from action input. Fixture identifiers grant no access.
  • Approve the exact mutation and its effects before CLI --confirm or MCP confirm:true. This documentation does not authorize outreach.

Discover the target

  • Discover list id through lists.list, then read lists.get in the selected workspace and inspect archivedAt. Retain the current name, description and active membership state before planning.
  • Read contacts.list with listId and follow its cursors to review intended active members. Retain the exact contact ids and prior membership state.
topolo whoami --json
topolo actions get app_topolo_engage.lists.members.remove --json
topolo workspaces list topolo-engage --json

Input fields

FieldRequiredMeaning and constraints
idYesRequired identifier discovered or retained in the same authorized workspace, trimmed non-empty string up to 128 characters.
contactIdsYesRequired non-empty array of at most 500 discovered contact identifiers, each trimmed 1–128 characters. Bind the list and all contacts to the same selected workspace; duplicate ids are deduplicated.

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.lists.members.remove --data @payload.json --json
topolo actions plan app_topolo_engage.lists.members.remove --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --confirm --json

Review the plan and obtain approval for the exact effects before executing the unchanged request.

: "${ENGAGE_WORKSPACE_ID:?Set ENGAGE_WORKSPACE_ID to your selected workspace identifier}"
topolo actions call app_topolo_engage.lists.members.remove --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --confirm --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 lists.members.remove · HTTP 200

Action input

{
  "id": "list_1",
  "contactIds": [
    "contact_1",
    "contact_9"
  ]
}

Actual local response

{
  "list": {
    "id": "list_1",
    "name": "Founders",
    "description": null,
    "memberCount": 0,
    "createdAt": "2026-10-01T00:00:00.000Z",
    "updatedAt": "2026-10-01T00:00:00.000Z",
    "archivedAt": null
  },
  "removed": 1
}

Source test: audience routes > lists > adds and removes members, counting only active contacts. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Effects and response meaning

  • Deletes only matching membership rows in the selected list/workspace. The contacts themselves remain. Returns list plus removed, the actual count deleted, which can be zero for absent memberships.
  • Archived lists are refused. Deletions run in chunks of 80 ids, so the full request is not a single atomic transaction. This action does not cancel an enrollment or recall outreach.

Verify the result

  • Read contacts.list with listId and follow all nextCursor values to confirm the intended active memberships are absent. Use contacts.get to confirm retained contact records; removed is a write count, not a deletion of contacts.

Recover

After an uncertain response, read current memberships and reconcile the intended ids before retrying. An unchanged removal ignores absent memberships, but could remove a concurrently re-added membership, so review current intent first. Re-adding exactly reviewed prior members is a separately approved action and requires active contacts and an active list.

Refused responses

Captured lists.members.remove · HTTP 409

Action input

{
  "id": "list_1",
  "contactIds": [
    "contact_1"
  ]
}

Actual local response

{
  "error": {
    "code": "state_conflict",
    "message": "Archived lists cannot be changed."
  }
}

Source test: audience routes > lists > creates, lists, edits and archives a list. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.

Captured lists.members.remove · HTTP 404

Action input

{
  "id": "list_1",
  "contactIds": [
    "contact_1"
  ]
}

Actual local response

{
  "error": {
    "code": "not_found",
    "message": "List was not found in this workspace."
  }
}

Source test: audience routes > lists > keeps lists and memberships inside their workspace. 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 422 invalid_action_input: Correct the request against the current input schema and validate it again before planning.
  • HTTP 409 state_conflict: Read the exact retained contact or list and inspect archivedAt. Archived records cannot be edited or have list memberships changed; repeated archive is refused. Do not invent an unarchive operation.
  • HTTP 404 not_found: Rediscover the identifier in the same authorized workspace. An identifier from another workspace is not a valid substitute; retain archived identifiers for direct reads.

Evidence and limits

  • contracts/audience.mjs

  • contracts/audience-filter.mjs

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

  • apps/api/src/audience/contacts-handlers.ts

  • apps/api/src/audience/contact-search.ts

  • apps/api/src/audience/contacts-repository.ts

  • apps/api/src/audience/lists-handlers.ts

  • apps/api/src/audience/lists-repository.ts

  • apps/api/src/audience/filter-source.ts

  • apps/api/test/audience-routes.test.ts

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