Cadences
Get an enrollment + its step-run history
Returns the enrollment row plus its stepRuns[] history (last 50, newest-first). Use this when you need per-step status — completed, skipped, failed — to render a step-by-step ✅ timeline.
Step run statuses
- sent — step fired and LinkedIn / SMTP accepted the action. Render as a green checkmark.
- skipped — step was evaluated but its condition didn't apply (e.g. after_accept step on an unaccepted invitation, or never_replied step when the lead replied). Normal cadence flow — render as a grey checkmark or skip-icon, NOT as an error.
- failed — runner tried and got back ok:false from the downstream call. errorCode is set. The enrollment is deferred 1h and retried on the next tick — this is NOT a terminal state on its own.
- queued — internal scheduling state. You'll rarely see this in practice; included for completeness.
Error codes you'll see in errorCode
Set by api/src/lib/cadences/runner.ts. The catalogue:
- NO_SESSION_AVAILABLE — workspace has zero connected LinkedIn sessions. Customer needs to connect one at /dashboard/linkedin.
- WORKSPACE_QUOTA_EXHAUSTED — sessions exist but all at the per-action daily cap (e.g. 30 connection_requests/day per session). Resets at midnight UTC, or add more sessions.
- PINNED_SESSION_UNUSABLE — enrollment.sessionId was set manually but that session is revoked / not-active / capped. Customer should reconnect or repin.
- NO_TARGET — leadLinkedinUrl (for LinkedIn steps) or leadEmail (for email steps) was missing at enrollment.
- NO_INBOX — email step but no inboxId on the enrollment or cadence.
- DRAFT_WEBHOOK_FAILED — step has draftStrategy: "webhook" and the customer's draft handler timed out (>30s), 5xx'd, returned non-JSON, returned an invalid defer, or returned no body. errorMessage carries the upstream detail (which status code, "no body in response", etc). The step defers 1h and retries — which means a flaky handler will keep getting opportunities to succeed. To stop retries, mark the enrollment state: "paused" or unenroll.
- AI_STRATEGY_REMOVED — pre-existing step still on draftStrategy: "ai" (removed 2026-05-19). Update the step to static (with staticBody) or webhook (with the cadence's webhookUrl set to your draft handler).
- send_failed — downstream LinkedIn / SMTP returned an error. errorMessage carries the upstream detail.
Worked example response
{
"ok": true,
"enrollment": {
"id": "01HZ…",
"cadenceId": "01HY…",
"leadExternalId": "delegated:lead:abc-123",
"leadName": "Jane Doe",
"leadLinkedinUrl": "https://www.linkedin.com/in/jane-doe-1a2b3c/",
"leadProfileUrn": "urn:li:fsd_profile:ACoAAA…aaaa",
"sessionId": "01HX…",
"currentStep": 3,
"state": "active",
"nextStepDueAt": "2026-05-21T14:00:00Z",
"lastStepFiredAt": "2026-05-19T14:00:00Z",
"enrolledAt": "2026-05-18T10:30:00Z",
"updatedAt": "2026-05-19T14:00:00Z"
},
"stepRuns": [
{
"id": "01HZ…",
"stepIndex": 2,
"stepKind": "linkedin_message",
"status": "sent",
"channelUrn": "urn:li:msg_message:(…)",
"renderedSubject": null,
"renderedText": "Hey Jane — saw your post about cold outbound…",
"errorMessage": null,
"errorCode": null,
"skipReason": null,
"firedAt": "2026-05-19T14:00:00Z"
},
{
"id": "01HZ…",
"stepIndex": 1,
"stepKind": "wait",
"status": "sent",
"channelUrn": null,
"renderedSubject": null,
"renderedText": null,
"errorMessage": null,
"errorCode": null,
"skipReason": null,
"firedAt": "2026-05-19T13:00:00Z"
},
{
"id": "01HZ…",
"stepIndex": 0,
"stepKind": "linkedin_connect",
"status": "sent",
"channelUrn": "urn:li:fsd_profile:ACoAAA…aaaa",
"renderedSubject": null,
"renderedText": "Hi Jane — common interest in outbound; thought I'd connect.",
"errorMessage": null,
"errorCode": null,
"skipReason": null,
"firedAt": "2026-05-18T10:30:00Z"
}
]
}
Rendering ✅ progress
const r = await mam.getEnrollment({ id });
const sentByIndex = new Map<number, true>();
const skippedByIndex = new Map<number, true>();
let lastFailure: typeof r.stepRuns[number] | null = null;
for (const run of r.stepRuns) {
if (run.status === "sent") sentByIndex.set(run.stepIndex, true);
else if (run.status === "skipped") skippedByIndex.set(run.stepIndex, true);
else if (run.status === "failed" && !lastFailure) lastFailure = run;
}
// For each step in cadence.steps[]:
// if (sentByIndex.has(i)) → ✅ done
// else if (skippedByIndex.has(i)) → ⊘ skipped (condition not met)
// else if (i === enrollment.currentStep && lastFailure) → ⚠ blocked
// else if (i === enrollment.currentStep) → ⏳ pending
// else → ◯ upcoming
get/enrollments/{id}
Request
- Base URL: https://anjal.app
- URL: https://anjal.app/enrollments/{id}
- Auth: API key in header X-API-Key
Path parameters
idstring uuid required
Response
Enrollment + step runs.
Changes
No recorded changes to this operation across all 1 revision of this API.