> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting: campaign and journey enrollment errors

> Resolve campaign-side enrollment refusals (AUDIENCE_TOO_LARGE, CAMPAIGN_NOT_RUNNING, CAMPAIGN_TYPE_NOT_ENROLLABLE, ALREADY_ENROLLED), journey-graph validation failures (JOURNEY_VALIDATION_FAILED, JOURNEY_DEFINITION_INVALID, JOURNEY_TRIGGER_MISSING, JOURNEY_TRIGGER_DISCONNECTED), and executor resume failures (RESUME_FAILED) — across POST /campaigns/:id/send, /enroll-contact, /enroll-segment, and /resume-journey.

# Troubleshooting: campaign and journey enrollment errors

Enrollment and graph-validation errors are refused at the door — before
any contact enters the journey, and before a single message is sent.
This page covers the four campaign-side refusal codes, the four
journey-graph validation codes, and the one executor-recovery code on
the resume path. For failures that happen *inside* a running journey
(`failed` execution rows, misbehaving nodes), see
[Troubleshooting: a flow execution that failed](/troubleshooting/flow-executions-failed).

The four surfaces this page debugs:

| Surface                                     | What it does                               |
| ------------------------------------------- | ------------------------------------------ |
| `POST /campaigns/:id/enroll-contact`        | Enroll one contact by `contact_id`         |
| `POST /campaigns/:id/enroll-segment`        | Enroll every current member of a segment   |
| `POST /campaigns/:id/send` (or `/schedule`) | Launch a blast / drip campaign             |
| `POST /campaigns/:id/resume-journey`        | Resume a paused journey (scheduler-driven) |

The first two are the enrollment endpoints; the third refuses
`AUDIENCE_TOO_LARGE` at launch; the fourth hits the re-validation
safeguard covered below.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/campaigns/cmp_123/enroll-contact \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"contact_id": "ct_456"}'
```

## The enrollment validation flow

Every enrollment request runs the same gate sequence. The first gate
that fails returns its own code, so the response tells you which step
rejected it:

```text theme={null}
enroll-contact / enroll-segment
  1. Campaign found?              → 404
  2. Type enrollable?             → CAMPAIGN_TYPE_NOT_ENROLLABLE (400)
  3. Status running?              → CAMPAIGN_NOT_RUNNING (400)
  4. Graph parses?                → JOURNEY_DEFINITION_INVALID (400)
  5. Trigger node present?        → JOURNEY_TRIGGER_MISSING (400)
  6. Trigger has an edge out?     → JOURNEY_TRIGGER_DISCONNECTED (400)
  7. Contact already active?      → ALREADY_ENROLLED (409)
  → enrolled, journey executes
```

The resume path adds a pre-flight gate on step 4: before the executor
runs, the stored journey definition is re-validated against the same
graph rules, so a journey cannot re-enter on a graph that drifted after
it started.

## Campaign-side refusals

These codes come from the campaign record or the launch itself — the
graph never gets looked at.

| Code                           | HTTP | Meaning                                                                                                                                                                      | Fix                                                                                                                                                          |
| ------------------------------ | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `AUDIENCE_TOO_LARGE`           | 400  | Launch/schedule refused: the resolved audience is bigger than your tenant's maximum recipient count.                                                                         | Split the audience into smaller segments and launch them separately, or narrow the audience filter.                                                          |
| `CAMPAIGN_TYPE_NOT_ENROLLABLE` | 400  | Single-contact enrollment only works on `journey`- and `drip`-type campaigns; the target is a `blast` or `ad` campaign, which fills its audience at launch, not per contact. | Point the enrollment call at a journey or drip campaign. To reach blast audiences, use the launch endpoint, not enrollment.                                  |
| `CAMPAIGN_NOT_RUNNING`         | 400  | The target campaign is `draft`, `paused`, `completed`, or `archived` — only `running` accepts new enrollments.                                                               | Start the campaign first, or resume it from `paused`. If it is `archived`, clone it and start the clone.                                                     |
| `ALREADY_ENROLLED`             | 409  | The contact already has an active enrollment in this campaign. This is a re-entry guard so you get a clear answer instead of a silent skip.                                  | Treat 409 as idempotent-success in your integration, or inspect the journey's re-entry policy. A completed or cancelled enrollment does not block a new one. |

## Journey-graph validation

These codes mean the campaign's step graph could not resolve an entry
point. Fix the graph in the campaign editor, then retry enrollment.

| Code                           | HTTP | Meaning                                                                                                                                 | Fix                                                                                                              |
| ------------------------------ | ---- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `JOURNEY_DEFINITION_INVALID`   | 400  | The stored journey steps are not a well-formed graph — nodes or edges are missing, malformed, or unparseable.                           | Rebuild the graph in the campaign editor; cloning a draft and re-editing often clears a mangled graph.           |
| `JOURNEY_TRIGGER_MISSING`      | 400  | The graph has no journey-trigger node, so there is no entry point to resolve. This happens when the trigger was deleted or never added. | Add a journey trigger back and wire it into the first step.                                                      |
| `JOURNEY_TRIGGER_DISCONNECTED` | 400  | The trigger node exists but has no outgoing edge, so no first step can be resolved for an enrolling contact.                            | Connect the trigger to the first node of the journey.                                                            |
| `JOURNEY_VALIDATION_FAILED`    | 400  | On resume, the stored journey definition no longer passes graph validation.                                                             | Re-check the graph in the editor for edits made after launch, then resume again. See the resume safeguard below. |

`JOURNEY_DEFINITION_INVALID`, `JOURNEY_TRIGGER_MISSING`, and
`JOURNEY_TRIGGER_DISCONNECTED` fire at enrollment time;
`JOURNEY_VALIDATION_FAILED` fires at resume time. Same validator,
different gate.

## Executor recovery: `RESUME_FAILED` and the resume safeguard

When a journey enrollment pauses (a delay node, a send-time schedule),
the scheduler later calls the resume endpoint to continue that contact.
Two outcomes matter on that path:

* **`JOURNEY_VALIDATION_FAILED` (400)** — the resume safeguard. Before
  the executor runs a single node, the stored journey definition is
  re-validated. If someone edited the graph after the campaign started
  (deleted the trigger, orphaned a node the paused enrollment was
  parked on), resume refuses and the response names the validation
  error. This refusal is a safeguard catching a graph drift, **not a
  user error you need to retry through** — fix the graph, and the
  scheduler picks the enrollment up on its next sweep.
* **`RESUME_FAILED` (500)** — the executor itself threw mid-resume (the
  message field carries the underlying error). The scheduler counts the
  attempt, backs off exponentially, and retries; after its attempt
  ceiling it stops retrying the enrollment instead of looping on it.
  A `RESUME_FAILED` that keeps recurring on the same enrollment after
  the upstream cause is fixed (a dead webhook, an upstream 5xx) should
  be escalated with the campaign id, contact id, and tenant id.

Recovery, then: correctly form the graph, let the scheduler back off,
and expect eventual recovery. Nothing in this table asks you to poll or
manually re-trigger — enrollment attempts that fail are re-swept, and a
`400` refusal stops retrying precisely because retrying cannot change
it.

## When to escalate

Escalate to support with the campaign id, the raw error response, and
your tenant id (Settings → Organization) when:

* The same contact gets `ALREADY_ENROLLED` after its earlier enrollment
  visibly completed or was cancelled.
* A graph reads valid in the editor but enrollment still returns
  `JOURNEY_DEFINITION_INVALID` (a stale definition is usually the
  culprit; export the campaign as JSON so support can diff).
* `RESUME_FAILED` persists across backoff cycles after the upstream
  cause is fixed, or resume attempts drain without a resolution.

## See also

* [Campaign lifecycle](/concepts/campaign-lifecycle) — which statuses
  gate enrollment (`running`) and where `blast` differs from `journey`.
* [Flow execution model](/concepts/flow-execution-model) — how journeys
  execute once enrolled, pause, and resume.
* [Troubleshooting: a flow execution that failed](/troubleshooting/flow-executions-failed)
  — executor-level failures *after* a contact is enrolled.
* [Error codes](/reference/error-codes) — the full catalog this page
  narrows to enrollment and graph errors.
