Skip to content

programs

A program is the container entities enroll into. It owns the entry requirements, the tier ladder, the Partner Connect surface, and — through its enrollments — the referral codes that make the evergreen rail work.

const page = await boomin.programs.list({ limit: 20 });
const program = await boomin.programs.retrieve("prog_...");
MethodRouteScope
programs.list(params, options)GET /programsprograms:read
programs.retrieve(id, options)GET /programs/{id}programs:read
{
"id": "prog_...",
"object": "program",
"name": "Launch Entities",
"type": "performance",
"description": null,
"status": "active",
"visibility": "private",
"metadata": {},
"createdAt": "2026-08-01T00:00:00.000Z",
"updatedAt": "2026-08-01T00:00:00.000Z"
}

visibility controls whether the program appears on the public Discover feed. Programs are private by default.

Qualification rules. They evaluate continuously from tracked program activity — this is the machinery behind the evergreen rail, and it runs whether or not a distribution ever launches.

await boomin.programs.requirements.create("prog_...", {
scope: "program_entry",
metricKey: "followers",
operator: "gte",
threshold: 5000,
required: true,
});
const { data } = await boomin.programs.requirements.list("prog_...");
await boomin.programs.requirements.update("prog_...", requirementId, { threshold: 10000 });
await boomin.programs.requirements.del("prog_...", requirementId);
MethodRouteScope
list(programId, params, options)GET /programs/{id}/requirementsprogram_requirements:read
create(programId, params, options)POST /programs/{id}/requirementsprogram_requirements:write
retrieve(programId, id, options)GET /programs/{id}/requirements/{rid}program_requirements:read
update(programId, id, params, options)POST /programs/{id}/requirements/{rid}program_requirements:write
del(programId, id, options)DELETE /programs/{id}/requirements/{rid}program_requirements:write

Deleting archives the requirement (status: "archived", deleted: true in the response) and re-evaluates the program.

FieldValues
scopeprogram_entry program_maintenance tier campaign benefit invite
scopeIdThe tier/campaign/benefit the rule attaches to, when scope is not program-level
metricKeySee the metric vocabulary below
operatorgte lte eq neq exists
thresholdInteger, or null for exists
windowDaysRolling window; null = lifetime
weightInteger weight in the score
requiredtrue = a hard gate; false = contributes to the score
operatingTypeOptional capacity key/id — the requirement applies only to enrollments operating as that type
failurePolicygrace or immediate; null = the surface default (immediate for assert: keys, grace otherwise)
statusactive paused archived

Three namespaces, and the standing surface accepts all three:

  • Built-insfollowers, views, post_count, collab_posts, link_clicks, referral_count, gmv_cents, sales_count, product_usage_count, channel_connected, manual_approval, event_registration, template_install.
  • Tenant keysx:-namespaced, registered by API call: metricKeys. An unregistered or archived x: key is refused with metric_key_invalid and the precise reason.
  • Assertion claimsassert:<key> gates standing on tenant truth (assert:advisor_verified with operator: "exists").

Which namespaces each surface accepts differs on purpose — reward rules take built-ins ∪ x:, payout rules stay built-ins-only in v1. The vocabulary ≠ capability table is the reference.

Per-enrollment adjustments to any requirement live on enrollments.requirementOverrides.

The ladder enrollments climb as requirements are met.

await boomin.programs.tiers.create("prog_...", { name: "Gold", rank: 3 });
const { data } = await boomin.programs.tiers.list("prog_...");
MethodRouteScope
list(programId, params, options)GET /programs/{id}/tiersprogram_tiers:read
create(programId, params, options)POST /programs/{id}/tiersprogram_tiers:write
retrieve(programId, id, options)GET /programs/{id}/tiers/{tid}program_tiers:read
update(programId, id, params, options)POST /programs/{id}/tiers/{tid}program_tiers:write
del(programId, id, options)DELETE /programs/{id}/tiers/{tid}program_tiers:write

Tiers are keyed by rank — creating a tier at an existing rank upserts it. Lists come back ordered by ascending rank.

The Partner Connect surface: the public key your browser code uses, the origins allowed to call it, and what happens when a entity joins.

const config = await boomin.programs.connectConfig.retrieve("prog_...");
console.log(config.publicKey, config.allowedOrigins);
await boomin.programs.connectConfig.update("prog_...", {
allowedOrigins: ["https://your-app.com", "http://localhost:5173"],
allowedRedirectOrigins: ["https://your-app.com"],
requiredChannels: ["instagram"],
defaultApprovalStatus: "pending",
});
MethodRouteScope
retrieve(programId, options)GET /programs/{id}/connect_configconnect_config:read
update(programId, params, options)POST /programs/{id}/connect_configconnect_config:write

retrieve answers JSON null when the surface has not been minted yet; the first update mints it. defaultApprovalStatus accepts pending (an applications inbox) or approved (open enrollment).

Signing configuration for Signed Handoff — one entry per issuer, so an app with several trusted front ends can rotate them independently.

const { data } = await boomin.programs.handoffConfig.retrieve("prog_...");
const config = await boomin.programs.handoffConfig.update("prog_...", {
issuer: "your-app.com",
audience: "boomin.ai",
});
console.log(config.signingSecret); // present only when just minted or supplied
MethodRouteScope
retrieve(programId, options)GET /programs/{id}/handoff_confighandoff:read
update(programId, params, options)POST /programs/{id}/handoff_confighandoff:write

retrieve returns a { object: "list", data, hasMore } envelope of issuer configs (filter with ?issuer=). update is create-or-update per issuer: omit signingSecret on first call and Boomin mints an hs_boomin_live_... secret, returned in that response only. Pass signingSecret explicitly to rotate it.

Read-only standing — programs:read, and it persists nothing: no state, no run row, no events.

// The whole-program reporting shape:
const preview = await boomin.programs.standingPreview("prog_...");
// counts, requirements, tiers, enrollments with override provenance
// One member's pass/fail through the evaluator's exact read half:
const targeted = await boomin.programs.standingPreview("prog_...", {
enrollment: "enr_...",
});
// What-ifs — simulate claims and capacity before committing them:
const simulated = await boomin.programs.standingPreview("prog_...", {
enrollment: "enr_...",
simulate: {
assertions: { advisor_verified: true }, // null = simulate absence
operatingType: "advisor", // null = simulate untyped
},
});
MethodRouteScope
standingPreview(id, params, options)POST /programs/{id}/standing_previewprograms:read

The targeted response’s enrollment carries the would-be status next to storedStatus (what the ledger currently says), met/failed as { requirement, metricKey, scope } entries, the effective operatingType, and every assertion claim the evaluation saw — simulated overlays flagged simulated: true. Simulation runs through the evaluator’s exact read half, so what the preview says is what a real evaluation would decide.

simulate requires enrollment — a what-if is asked of one member’s standing. The CLI wraps this as boomin standing test:

Terminal window
npx @boomin/cli standing test --program prog_... --enrollment enr_... \
--assert advisor_verified=true --operating-type advisor