List the employer's plans
You already loaded the employer and its plans through the Management API. Start from that list: for each plan you want in the view, you need its plan_id and its benefit line (medical, dental, vision, basiclife, voluntarylife, std, ltd, stoploss). You will score each plan one at a time.
Scope the set
Choose one cohort (query)
Define the peer group once as a query filter set and reuse it for every plan, so each plan is measured against the same employers and the scores are comparable. The query is a multi-dimension filter: state[], industry[], size[], and other[] (all optional, all multi-select).
const query = {
state: ["CA", "OR", "WA"],
industry: ["services_legal_services"],
};Cohort rules
- The cohort must resolve to between 20 and 2000 peer employers. Too few and a plan cannot be scored; too broad and you are asked to narrow it (a
422 cohort_out_of_rangeat request time). - For
stoploss, only thesizevalues apply, state and industry are ignored. With nosize, stop loss comes backinsufficient(“needs a size band”) rather than falling back to the whole market. - Resolve the dimension keys from
GET /insights/dimensions; industry and other are served live.
Score each plan
For each plan, create a score with POST /insights/scores and poll it. Scoring is an on-demand, live benchmark, so it is always async: the create call returns 202 with a score_id, and you poll GET /insights/scores/{score_id} until the status is no longer processing.
const results = [];
for (const plan of employer.plans) {
// plan_id must be one of YOUR plans (ownership-checked; 404 otherwise)
const { score_id } = await post("/insights/scores", {
benefit: plan.benefit,
plan_id: plan.id,
query, // the shared cohort from step 2
});
let result;
do {
result = await get(`/insights/scores/${score_id}`);
} while (result.status === "processing");
results.push(result);
}Run them in parallel
POSTs and poll the score_ids concurrently rather than strictly one after another. The result is transient (short TTL, retrieved once), so read each score when it completes and keep it.Handle the not-scorable cases
Not every plan ends with a score. Surface these outcomes to the user rather than dropping the plan, each one tells them exactly what to fix.
Cohort out of range (at request time)
POST /insights/scores returns 422 cohort_out_of_range when the query resolved to fewer than 20 or more than 2000 peer employers. Adjust the query, broaden a too-thin cohort or narrow a too-broad one, and retry.
Plan missing required fields
The score completes with scorable: false and a missing_fields: [{key, label}] list when the plan lacks data needed to score it. Prompt the user to complete those fields, then re-score.
Cohort too thin to rank
The score comes back with status insufficient when the cohort has data but too few comparable plans for this plan’s plan-type to produce a stable ranking. This is distinct from the 20/2000 employer gate, widen the cohort so more comparable plans qualify.
Present the results together
Stack the scored plans into one view, a card per plan, exactly like the app. Each result gives you the headline and the drivers:
score(0–100, exact) andband(Limited / Balanced / Robust), the headline for the plan.positions, keyed by field: distribution fields carryplan,p25/p50/p75, and arank; copay/CPT fields are a single stitchedplan+p50string, and the weighted ones also carry arank.weight_percenton each scored field, its share of the score. Use it to show the drivers (“this field drives N% of the score”). Booster fields instead carrybooster_points, flat points added outside the weighted average.
Rank is masked at the extremes
rank is a string, masked to ">90" or "<10" at the ends where the exact percentile is noisy. Render the string as-is. The headline score is never masked.Want the surrounding distribution too? The market face of the same model, GET /insights/{benefit}/{dimension}/{value} (no plan_id), returns the pre-computed weekly cut you can show alongside a plan.
More flexible than one employer endpoint
Composing the view from plan scores is not a workaround, it is the more capable design. A few reasons it beats a single employer call:
- The plan is the unit of decision. You see exactly which plans are strong and which are weak, and why, instead of one blended number that hides the detail.
- You control the peer group. Pick any cohort and apply it consistently across the plans, or re-run the same plans against a different cohort for a what-if, all from the same call.
- You own the presentation. Order, group, weight, and emphasize however your product needs, nothing is locked into a fixed, server-rendered rollup.
- One primitive, many views. The same scoring call powers an employer view, a book-of-business comparison, or a side-by-side of plan options, with no new endpoints to learn.
- Full transparency. Every score ships with its drivers (
weight_percent, per-field ranks), so you can explain the number to a client rather than hand them a black box.