Skip to content

enrollments

An enrollment is a relationship’s participation in exactly one program. It is where the referralCode lives, and it is the unit the evergreen rail runs on.

Enrollments are flat — there is no /programs/{id}/enrollments nesting. The payload carries the program.

const enrollment = await boomin.enrollments.create({
program: "prog_...",
name: "Creator",
});
await boomin.enrollments.approve(enrollment.id);
MethodRouteScope
create(params, options)POST /enrollmentsenrollments:write
list(params, options)GET /enrollmentsenrollments:read
retrieve(id, options)GET /enrollments/{id}enrollments:read
approve(id, params, options)POST /enrollments/{id}/approveenrollments:write
reject(id, params, options)POST /enrollments/{id}/rejectenrollments:write
pause(id, params, options)POST /enrollments/{id}/pauseenrollments:write
resume(id, params, options)POST /enrollments/{id}/resumeenrollments:write
update(id, params, options)POST /enrollments/{id}enrollments:write

Legacy program_members:* scopes are accepted as aliases on already-issued tokens.

Per-member requirement adjustments live on the nested enrollments.requirementOverrides subcollection.

{
"id": "enr_...",
"object": "enrollment",
"program": "prog_...",
"relationship": "rel_...",
"entity": "ent_...",
"referralCode": "ABC123",
"approvalStatus": "approved",
"status": "active",
"billingStatus": "billable",
"qualificationStatus": "qualified",
"operatingType": "otype_...",
"source": "platform_api",
"metadata": {},
"livemode": true,
"joinedAt": "2026-08-01T00:00:00.000Z",
"approvedAt": "2026-08-01T00:00:00.000Z",
"rejectedAt": null,
"createdAt": "2026-08-01T00:00:00.000Z",
"updatedAt": "2026-08-01T00:00:00.000Z"
}

Raw HTTP responses use the snake_case spellings (referral_code, approval_status); the SDK camelCases every response key.

qualificationStatus is present on create, retrieve, approve, and reject — it comes from the enrollment’s evaluated performance, so the pause and resume responses omit it rather than serve a stale value.

This is the single most common source of confusion in the API, so it is worth stating flatly:

FieldValuesMoved by
approvalStatuspending approved rejectedapprove() / reject() only
statusactive paused archivedpause() / resume() only

Approval commands never touch status. Pause and resume never touch approvalStatus. An enrollment can perfectly well be (approved, paused) or (pending, active) — those are not contradictions, they are two different questions.

resume is the canonical verb on every surface. There is no unpause.

// by email — upserts the entity identity
await boomin.enrollments.create({
program: "prog_...",
name: "Creator",
referralCode: "CREATOR10", // optional; generated when omitted
metadata: { cohort: "spring" },
});
// or against an existing entity
await boomin.enrollments.create({ program: "prog_...", entity: "ent_..." });

One of email or entity is required. create answers 201 for a new enrollment and 200 when one already existed.

In one call it: upserts the entity identity, opens the durable relationship as pending if there wasn’t one, and creates the enrollment as (pending, active).

Rejection is not terminal. Re-inviting a rejected enrollment resets approvalStatus to pending, leaving status untouched. You may also approve a previously rejected enrollment directly, without a re-invite.

await boomin.enrollments.approve("enr_...");
await boomin.enrollments.reject("enr_...");

Approval fires a qualification evaluation and activates the durable relationship if it was still pending. It is the one flip point where the relationship becomes real.

await boomin.enrollments.pause("enr_...");
await boomin.enrollments.resume("enr_...");

While an enrollment is paused: links keep resolving, attribution continues, standing evaluation continues, new deployments skip the enrollment, and paused-period activity is permanently ineligible for reward grants — decided at the event’s occurred_at.

Billing continues while paused, deliberately: the entity’s links still work. Archiving is the billing exit, and it keeps the referralCode for attribution history.

await boomin.enrollments.update("enr_...", { operatingType: "advisor" });
await boomin.enrollments.update("enr_...", { operatingType: null }); // clear

operatingType takes an operating-type KEY (or otype_... id); null clears the capacity. Typed requirements and money rules apply only while the enrollment operates in their capacity, and every change re-evaluates standing. Assigning an archived type answers operating_type_archived (409).

A Signed Handoff can carry operatingType on its signed payload, so your app sets the capacity at login time without a second call.

ParamValues
programA prog_... id
statusactive paused archived
approvalStatuspending approved rejected
limit1–100, default 20
startingAfterAn enr_... cursor
for await (const enrollment of boomin.enrollments.list({
program: "prog_...",
approvalStatus: "pending",
})) {
await boomin.enrollments.approve(enrollment.id);
}

Query params and body fields are both camel→snake converted by the client (approvalStatusapproval_status, referralCodereferral_code). Already-snake_case spellings still pass through; raw HTTP callers send snake_case.