Outcome
Stop one active enrollment and cancel its remaining scheduled messages and open tasks.
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
- Select the authorized Engage workspace and discover enrollment id through enrollments.list or retain the id from an earlier approved enrollments.create. Read enrollments.get before acting on its status.
- Read its retained messages and tasks before approving the stop; review already sent or in-flight external work separately.
topolo whoami --json
topolo actions get app_topolo_engage.enrollments.stop --json
topolo workspaces list topolo-engage --json
Input fields
| Field | Required | Meaning and constraints |
|---|---|---|
id | Yes | Required enrollment identifier returned by enrollments.create or enrollments.list in the selected workspace, trimmed non-empty string up to 128 characters. This is not a campaign, contact or message id. |
reason | No | Optional trimmed non-empty reason up to 500 characters; omitted uses manual_stop. Supplying reply or another reason does not change this action’s terminal status from stopped. |
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.enrollments.stop --data @payload.json --json
topolo actions plan app_topolo_engage.enrollments.stop --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.enrollments.stop --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 enrollments.stop · HTTP 200
Action input
{
"id": "eng_enr_4"
}
Actual local response
{
"enrollment": {
"id": "eng_enr_4",
"campaignId": "eng_cmp_1",
"sequenceVersionId": "eng_seqv_1",
"contactId": "contact_4",
"seatId": "eng_seat_1",
"status": "stopped",
"stopReason": "manual_stop",
"nextStepPosition": 2,
"nextSendAt": null,
"firstMessageId": "email_stub_59b1c93bc14c3738c72e841f48a4b628",
"firstSubject": "Quick question, Ada",
"outcome": null,
"enrolledAt": "2026-10-06T08:30:00.000Z",
"updatedAt": "2026-10-06T08:30:00.000Z"
}
}
Source test: engagement analytics > reads ledger signals after the first touch by contact or address: a later bounce removes the reply. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.stop · HTTP 200
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"enrollment": {
"id": "eng_enr_1",
"campaignId": "eng_cmp_1",
"sequenceVersionId": "eng_seqv_1",
"contactId": "contact_1",
"seatId": "eng_seat_1",
"status": "stopped",
"stopReason": "manual_stop",
"nextStepPosition": 1,
"nextSendAt": null,
"firstMessageId": null,
"firstSubject": null,
"outcome": null,
"enrolledAt": "2026-10-06T08:30:00.000Z",
"updatedAt": "2026-10-06T08:30:00.000Z"
}
}
Source test: the runner on a task step > stopping the enrollment cancels its open task. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.stop · HTTP 200
Action input
{
"id": "eng_enr_6"
}
Actual local response
{
"enrollment": {
"id": "eng_enr_6",
"campaignId": "eng_cmp_3",
"sequenceVersionId": "eng_seqv_3",
"contactId": "contact_6",
"seatId": "eng_seat_2",
"status": "stopped",
"stopReason": "manual_stop",
"nextStepPosition": 2,
"nextSendAt": null,
"firstMessageId": "email_stub_ed3d841e4634a2afd7e4d1b577a2a352",
"firstSubject": "Quick question, Ada",
"outcome": null,
"enrolledAt": "2026-10-06T08:30:00.000Z",
"updatedAt": "2026-10-06T08:30:00.000Z"
}
}
Source test: engagement analytics > reports first-touch rates by campaign: each contact once, bounced never replied, test campaigns excluded. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.stop · HTTP 200
Action input
{
"id": "eng_enr_1",
"reason": "Asked by sales"
}
Actual local response
{
"enrollment": {
"id": "eng_enr_1",
"campaignId": "eng_cmp_1",
"sequenceVersionId": "eng_seqv_1",
"contactId": "contact_1",
"seatId": "eng_seat_1",
"status": "stopped",
"stopReason": "Asked by sales",
"nextStepPosition": 1,
"nextSendAt": null,
"firstMessageId": null,
"firstSubject": null,
"outcome": null,
"enrolledAt": "2026-10-06T08:30:00.000Z",
"updatedAt": "2026-10-06T08:30:00.000Z"
}
}
Source test: enrollment routes > stops an enrollment, cancelling its scheduled messages, once. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Effects and response meaning
- Changes active enrollment to stopped with the supplied reason or manual_stop, clears nextSendAt, then cancels scheduled messages and open tasks. A completed/stopped/replied/bounced enrollment returns state_conflict rather than another stop.
- Enrollment status, message cancellation and task cancellation are separate writes. An interruption can leave a stopped enrollment with incomplete cancellation; repeating stop then refuses because it is no longer active.
- Sent messages and completed tasks remain historical. This is not a provider recall or guarantee that a transport call already in flight was undone. No enrollment-resume action is presented.
Verify the result
- Read enrollments.get and confirm status=stopped, intended stopReason and nextSendAt:null. Read messages.list to verify scheduled messages were cancelled and inspect tasks.list for remaining open tasks.
- Check all three retained states after an uncertain response; stopped status alone does not prove cancellation writes completed.
Recover
After an uncertain stop, read enrollment, messages and tasks. If stopped, do not repeat the mutation. If scheduled messages or open tasks remain, report the incomplete cancellation to the operator: this action exposes no supported repair once the enrollment has ended. Re-enrollment is a new separately reviewed outreach operation, not a reversal or safe retry of stop.
Refused responses
Captured enrollments.stop · HTTP 409
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"error": {
"code": "state_conflict",
"message": "This enrollment has already ended."
}
}
Source test: enrollment routes > stops an enrollment, cancelling its scheduled messages, once. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured enrollments.stop · HTTP 403
Action input
{
"id": "eng_enr_1"
}
Actual local response
{
"error": {
"code": "permission_denied",
"message": "The credential lacks workspace:write.",
"details": {
"requiredPermission": "workspace:write"
}
}
}
Source test: enrollment routes > keeps enrollments inside the workspace and enforces permissions. 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: Validate the current strict input schema. Creation requires campaignId, seatId and exactly one of contactIds or listId; do not provide both or neither. - HTTP 409
state_conflict: Read the campaign, published sequence and enrollment state before planning again. Creation requires an active campaign and active sequence with a published version; stop requires an active enrollment. Do not activate a campaign or publish content automatically to bypass a refusal.
Related actions
Evidence and limits
-
contracts/sending.mjs -
apps/api/src/enrollments/enrollments-handlers.ts -
apps/api/src/enrollments/enrollments-repository.ts -
apps/api/src/enrollments/enrollments-domain.ts -
apps/api/src/enrollments/messages-repository.ts -
apps/api/src/audience/lists-repository.ts -
apps/api/src/tasks/tasks-repository.ts -
apps/api/src/sending/outreach-eligibility.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.