Data standards
Universal Plan Schema
Every HMO plan — regardless of how the carrier represents it internally — is mapped to this canonical schema before storage and on every API response.
A fintech engineer should be able to compare plans from five HMOs without learning how any of them define products internally. The Universal Plan Schema is how that promise is kept.
Plan object#
{
"id": "plan_b3f9c21a",
"external_id": "REL-SILVER-IND",
"name": "Silver Plan",
"scope": "INDIVIDUAL",
"status": "ACTIVE",
"billing_frequency": "MONTHLY",
"hmo": { "slug": "reliance-hmo", "name": "Reliance HMO" },
"coverage": { /* see below */ },
"pricing": { /* see below */ },
"exclusions": ["HIV/AIDS treatment", "Cosmetic surgery"],
"waiting_periods": { "general": 30, "maternity": 270, "pre_existing": 365 },
"effective_from": "2026-06-01T00:00:00Z",
"effective_to": null,
"last_synced_at": "2026-06-08T08:00:00.000Z",
"last_verified_at": "2026-06-08T07:55:00.000Z",
"is_stale": false
}Enums
scope: INDIVIDUAL | FAMILY | EMPLOYEE_GROUP | STUDENT | OTHER.
status: DRAFT | ACTIVE | WITHDRAWN. Only ACTIVE plans are surfaced via the consumer API.
billing_frequency: MONTHLY | QUARTERLY | ANNUAL.
Coverage#
Coverage is broken down by benefit class with optional limits, co-pay percentages, and per-benefit waiting periods. Unset benefits are absent from the object rather than represented as { covered: false } — though explicit covered: false is also valid.
{
"outpatient": { "covered": true, "limit": 20000000, "co_pay_percent": 0 },
"inpatient": { "covered": true, "limit": 100000000, "co_pay_percent": 10 },
"maternity": { "covered": true, "limit": 30000000, "waiting_period_days": 270 },
"dental": { "covered": false },
"optical": { "covered": true, "limit": 3000000 },
"emergency": { "covered": true, "limit": 50000000, "co_pay_percent": 0 },
"telemedicine": { "covered": true, "unlimited": true },
"diagnostics": { "covered": true, "limit": 5000000 },
"pharmacy": { "covered": true },
"mental_health":{ "covered": false },
"wellness": { "covered": true }
}Benefit classes that don't have a top-level key (a future addition) appear under coverage.extras with the same shape.
Pricing#
Pricing carries a fallback individual_monthly plus an ordered list of age_bands. The engine walks the bands; if no band contains the user's age, the fallback applies. Optional family_rate overrides for FAMILY-scoped plans.
{
"individual_monthly": 850000,
"age_bands": [
{ "min_age": 0, "max_age": 17, "monthly": 600000 },
{ "min_age": 18, "max_age": 35, "monthly": 850000 },
{ "min_age": 36, "max_age": 50, "monthly": 1100000 },
{ "min_age": 51, "max_age": 65, "monthly": 1600000 }
],
"family_rate": 2500000,
"employer_discount_percent": 15
}Exclusions & waiting periods#
Exclusions are plain strings — render them as-is. Waiting periods are days. Surface these prominently in your UI; they're the source of most member disputes.
{
"exclusions": ["HIV/AIDS treatment", "Cosmetic surgery", "Pre-existing conditions"],
"waiting_periods": { "general": 30, "maternity": 270, "pre_existing": 365 }
}