Read why contacts were or were not enrolled

Search Documentation

Search across all developer documentation

workflows

Read why contacts were or were not enrolled

GET /v1/workflows/{id}/enrollment-decisions
Auth required api.workflows.view

The enrolment decision trace for one workflow, newest first — the answer to "why isn't this contact in this journey".

Every enrolment decision is recorded, positive and negative: the run counts elsewhere tell you who enrolled and say nothing about the far larger number who did not. reason_counts is the shape of it, and the rows are the evidence.

Retained for 14 days. This is a debugging aid, not a record.

Recipient email is not exposed — contact_id is the join key.

Path parameters

id string required

The workflow's UUID or, when the value does not parse as a UUID, its org-unique key. Unknown handles read as 404 not_found. Accepted on every workflow route, read and write alike.

Query parameters

contact_id string optional

Narrow to one contact, by its id, your own external_id, or its email address. The two non-id forms additionally require api.contacts.view, so a workflows-only key cannot use this filter to learn which addresses exist; without it they are 403 invalid_scope. A handle that names no contact is 422 validation_failed, not an empty page.

since string<date-time> optional

RFC 3339. Clamped to the 14-day retention — an earlier value reads as the start of the window rather than failing.

limit integer optional

Responses

Errors follow the RFC 7807 problem format — see the error reference.

200 A page of the trace, plus the tally by reason application/json
data array<object> required

Each entry in data:

workflow_id string<uuid> required
contact_id string<uuid> required
cause string optional

The trigger kind: segment, event, activity or date_rel.

source_key string optional

The segment key, event name or activity name the signal fired on.

decision string enum required

errored is the enrolment path failing rather than declining — a distinction worth keeping, because a contact missing for either reason looks identical from outside.

One of: enrolled, skipped, errored

reason string optional

Which exit was taken. One of workflow_not_active, trigger_stale, before_forward_floor, reentry_once_already_ran, success_exit_still_matches, entry_predicate_false, already_live_run, outside_shadow_cohort, source_unparseable or enroll_insert_failed. Absent on an enrolled decision, which has no reason to give.

detail string optional

Whatever makes the reason actionable — the predicate text, the forward floor and the event instant, the workflow's status.

run_id string<uuid> optional

The run an enrolled decision produced. Absent on a skip.

shadow boolean required

Whether the decision was taken while the workflow was shadowing.

occurred_at string<date-time> required
reason_counts object optional

The tally by reason over the same window. Present only when the read is narrowed to one workflow — a count mixed across every workflow a contact ever touched answers no question anybody asked.

401 Missing, malformed, or unknown API key application/problem+json
403 Key lacks the required scope or plan limit violated application/problem+json
404 Resource not found application/problem+json
422 A query parameter, path value or body field failed validation. The body is a validation_failed Problem. When the refusal is about a reference — a segment key, template slug, topic or attribute name the organization does not have — it additionally carries validation_code, field, line/column, missing, candidates and next_step, so a client can correct the call without a second round of guessing. See ValidationProblem. application/problem+json
429 Per-org rate limit exceeded application/problem+json
500 Unexpected server-side failure. The code is internal_error. The request_id field can be quoted to SendOps support to investigate. application/problem+json