API reference
A REST API over HTTPS with bearer-token auth, JSON bodies, cursor pagination, and idempotency keys on writes. The same engine also answers as an MCP server.
Conventions
These hold across every endpoint, so the resource groups below only describe what is specific to them.
Solved Enroll is in private beta. This page documents the shape of the API ahead of the 2027 public rollout. Credentials are issued through cohort onboarding or a licensing conversation, not self-service, and exact schemas ship with them. Join the waitlist or ask about licensing.
| Base URL | https://api.solvedenroll.com/v1. HTTPS only. Requests over plain HTTP are refused rather than redirected. |
|---|---|
| Authentication | A bearer token on every request: Authorization: Bearer <token>. Tokens are scoped to one account and one integration, and inherit the permissions and hierarchy scope of the account they belong to. |
| Content type | application/json on requests with a body, and on every response. Bodies are UTF-8. |
| Versioning | The version is in the path, currently v1. Additive changes ship inside a version; anything that would break a client gets a new one. |
| Pagination | Cursor-based on every list endpoint. Pass limit up to 100 and cursor; read next_cursor from the response and pass it back. A null next_cursor means you have reached the end. Offsets are not supported, because a book of business changes under you while you page through it. |
| Idempotency | Writes accept an Idempotency-Key header. Replaying the same key with the same body inside 24 hours returns the original response instead of creating a second record, which is what you want when a timeout leaves you unsure whether a submission landed. |
| Filtering | List endpoints accept changed_since as an RFC 3339 timestamp, plus resource-specific filters such as agent, agency node, state, product type, and external_ref. |
| External references | Clients, quotes, and applications accept an external_ref you control, so you can reconcile against the keys your own system already trusts. |
| Identifiers | Opaque prefixed strings, for example cli_8Fq2mR, qte_51Bn7c, app_0Kd3Vx. Treat them as strings; do not parse them. |
| Timestamps | RFC 3339 in UTC, for example 2027-10-15T14:32:08Z. Dates without a time, such as a date of birth or an effective date, are YYYY-MM-DD. |
| Rate limits | 600 requests per minute per account by default, reported on every response. See rate limiting. |
Rank the plans a client can actually take, in one call.
Post a client and a line of business. Get back ordered plans with a score, the factors behind each score, and the rule set version the ranking ran against. Values below are illustrative.
curl -X POST https://api.solvedenroll.com/v1/recommendations/rank \
-H "Authorization: Bearer $SOLVED_ENROLL_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8f14e45f-ea2b-4c1e-9d6a" \
-d '{
"client_id": "cli_8Fq2mR",
"line": "medicare",
"product_types": ["mapd", "pdp"],
"limit": 3
}'
{
"id": "rec_3Wd9Lp",
"client_id": "cli_8Fq2mR",
"line": "medicare",
"plan_year": 2027,
"ruleset_version": "2027.04",
"results": [{
"rank": 1,
"score": 92,
"plan_id": "pln_4ac81b",
"product_type": "mapd",
"factors": [
{"name": "drug_cost", "effect": 31},
{"name": "provider_match", "effect": 18},
{"name": "premium", "effect": -4}
]
}],
"excluded_count": 26,
"next_cursor": null
}
Resource groups
Nine groups, in the order a case moves through them.
Clients
One client record carries every line of business. Everything else on this page hangs off a client.
Health profiles
The profile that drives life underwriting and drug pricing. Free-text strings in, canonical terms out.
Quotes
One endpoint per line of business, because the inputs genuinely differ. All three return the same envelope.
Recommendations
The AI Plan Recommender. Ranked, scored, explainable, and deterministic, so a case can be reconstructed later.
Plans
Plan, formulary, and provider data on its own, for systems that want the reference data rather than a quote.
Applications
From a chosen plan to a signed, submitted, acknowledged application, without retyping anything.
Documents
The case file and everything in it. This is the group a compliance team cares about.
Agencies
Structure, licensure, and volume. Read-only, because hierarchy changes belong in the portal where they are audited.
MCP
The same engine behind a Model Context Protocol endpoint, so an agent runtime can call it directly.
Endpoints
Paths are relative to https://api.solvedenroll.com/v1.
Clients
One client record carries every line of business. Everything else on this page hangs off a client.
POST/clients |
Create a client record: name, date of birth, contact details, address and county, and your external_ref. County matters, because Medicare service areas are county-level. Accepts an idempotency key. |
|---|---|
GET/clients/{id} |
Fetch one client with the health profile summary, open cases, quotes, applications, and policies recorded in force. |
PATCH/clients/{id} |
Update contact details, address, or external_ref. Health data is written through the health profile endpoints instead, so that every change to a medication or condition list lands in the audit trail as a health event. |
GET/clients |
List clients with cursor pagination. Filter by agent, agency node, state, changed_since, or external_ref. Scope follows the token: a token belonging to an agent sees that agent's book, a token belonging to a principal sees the downline. |
POST/clients/{id}/merge |
Merge a duplicate into a surviving record. Health profiles, quotes, applications, and documents move across, conflicting fields are reported rather than silently dropped, and the merge is written to the audit trail with attribution. |
Health profiles
The profile that drives life underwriting and drug pricing. Free-text strings in, canonical terms out.
POST/clients/{id}/health-profile |
Create or replace the profile: height, weight, tobacco use, and the medication and condition lists. Returns the profile with resolved terms and the carrier-relevant flags derived from it. |
|---|---|
POST/clients/{id}/health-profile/medications |
Add a medication from a free-text string. The response returns the resolved term with strength and form, or a candidate list when the string is genuinely ambiguous, so your interface can ask rather than guess. |
POST/clients/{id}/health-profile/conditions |
Add a condition with onset date, treatment, and control status. Follow-up requirements are returned with it, because what a carrier needs next depends on what you just recorded. |
POST/terms/resolve |
Resolve a free-text medication or condition string to a canonical term without writing anything to a profile. Handles abbreviations, brand and generic names, and misspellings. Useful for a type-ahead in your own interface. |
GET/clients/{id}/health-profile |
Fetch the current profile, its resolved terms, and the derived flags, including which carriers the profile currently knocks out and why. |
Quotes
One endpoint per line of business, because the inputs genuinely differ. All three return the same envelope.
POST/quotes/medicare |
Create a Medicare quote. Accepts the client, product types (mapd, pdp, medsupp, snp), county, effective date, the pharmacy the client actually uses, and any named providers. Eligibility, service area, formulary, and network are applied before plans are returned. |
|---|---|
POST/quotes/life |
Create a life quote. Accepts product types (term, final_expense, whole_life, preneed), a face amount or a target monthly premium, and term length. Products the health profile disqualifies do not come back as options. |
POST/quotes/ancillary |
Create an ancillary quote for dental, vision, hearing, hospital indemnity, accident, or critical illness, standalone or alongside a medical plan on the same client. |
GET/quotes/{id} |
Fetch a quote with the plans returned, priced drug costs by coverage phase, provider network matches, and the excluded list with a reason on every exclusion. The excluded list is the part most integrations end up relying on. |
GET/quotes |
List quotes with cursor pagination. Filter by client, agent, line, product type, or changed_since. |
Recommendations
The AI Plan Recommender. Ranked, scored, explainable, and deterministic, so a case can be reconstructed later.
POST/recommendations/rank |
Rank the eligible plans for a client and a line of business. Returns ordered results with a score and the factors that lifted or lowered each one, plus the rule set version and plan year the ranking ran against. |
|---|---|
POST/recommendations/explain |
Return the full factor breakdown for one plan against one profile, including what would have to change for it to move up. This is the call to make when a client asks why the second option is not the first. |
GET/recommendations/{id} |
Fetch a stored recommendation exactly as it was produced, with the weighting and rule set version in effect at the time. Re-running is not the same as retrieving, and an audit wants the original. |
Plans
Plan, formulary, and provider data on its own, for systems that want the reference data rather than a quote.
GET/plans/search |
Search plans by county, plan year, product type, and carrier. Both the current and the upcoming plan year are searchable once next year's data has loaded. |
|---|---|
GET/plans/{id} |
Fetch one plan with benefits, premium, service area, cost sharing, and the published star rating where CMS publishes one. |
GET/plans/{id}/formulary |
Formulary lookup for a drug list: tier placement, prior authorization, step therapy, and quantity limits, per drug. |
GET/plans/{id}/providers |
Provider lookup: network status for named doctors, facilities, or pharmacies, including whether a pharmacy is preferred or standard. |
Applications
From a chosen plan to a signed, submitted, acknowledged application, without retyping anything.
POST/applications |
Create an application from a quote and a chosen plan. Returns the application with the carrier form version it will be written on. |
|---|---|
POST/applications/{id}/prefill |
Populate carrier form fields from the client record, health profile, and quote. Returns the fields still required, so your interface can ask for exactly those and nothing else. |
POST/applications/{id}/send |
Send for signature. delivery is sms, tablet, or email. Sending again after a failed delivery reuses the same packet rather than building a new one. |
GET/applications/{id}/status |
Signature and submission state, the carrier reference, any requirements the carrier has raised, and the final disposition. The same transitions arrive as webhooks if you would rather not poll. |
GET/applications |
List applications with cursor pagination. Filter by client, agent, carrier, state, status, or changed_since. |
Documents
The case file and everything in it. This is the group a compliance team cares about.
GET/documents/case-file/{case_id} |
Generate the complete case file as one document: Scope of Appointment with its timestamp, disclosures presented and acknowledged, plans presented, plan chosen, the recommendation and its factors, the signed application, and the submission and acknowledgement record, in order. |
|---|---|
GET/documents |
List documents on a client or a case, with type, size, and creation time. |
POST/documents/{id}/download-url |
Issue a short-lived, single-use download URL. Documents are never served from a permanent public link, and issuing the URL is itself written to the audit trail. |
Agencies
Structure, licensure, and volume. Read-only, because hierarchy changes belong in the portal where they are audited.
GET/agencies/{id}/hierarchy |
The upline and downline structure as a tree, with the node identifiers the other endpoints filter on. |
|---|---|
GET/agencies/{id}/agents |
Agents with their licenses, carrier appointments, and can-write status by carrier, product, and state. Check this before your system starts a case it cannot finish. |
GET/agencies/{id}/usage |
Cases, quotes, recommendations, submissions, and issued policies by agent, product, carrier, and period, matching the numbers the agency dashboards show. |
MCP
The same engine behind a Model Context Protocol endpoint, so an agent runtime can call it directly.
POST/mcp (tools/list) |
Tool discovery. Returns the available tools with their input schemas: resolve_term, check_eligibility, quote_medicare, quote_life, quote_ancillary, lookup_formulary, lookup_provider, rank_plans, and explain_plan. |
|---|---|
POST/mcp (tools/call) |
Invoke one tool by name with its arguments. Results come back as structured content, not prose, so your model reads a ranking rather than parsing a paragraph. Authentication, permissions, hierarchy scope, rate limits, and audit logging are identical to the REST surface. |
Webhooks
Eight event types, delivered as signed POSTs to your endpoint within seconds of the event, and retried with backoff for up to 24 hours.
client.created
A client record was created, by an agent in the portal or by an API call from your system.
quote.completed
A quote finished. The payload carries the quote identifier, the line, the plans returned, and the excluded count.
recommendation.ready
A ranking is available, with the top results and the rule set version it ran against.
application.sent
A signature packet was sent, with the delivery method used.
application.signed
The client completed signing. Signature metadata is on the case; the document is fetched through the documents endpoints.
application.submitted
The application was transmitted to the carrier.
application.acknowledged
The carrier acknowledged receipt, or raised a requirement. This is the event most agency management systems key their pipeline off.
soa.captured
A Scope of Appointment was captured and timestamped for a client, with the products it covers.
Delivery behavior
- Signed and timestamped. Verify the signature against the raw request bytes before parsing, and reject a timestamp outside your tolerance so a captured delivery cannot be replayed at you.
- At least once. Respond 2xx quickly and process asynchronously. Key on the event identifier, because a retry after a slow response will deliver the same event twice.
- Retried with backoff. Failed deliveries retry on an increasing interval for up to 24 hours, then stop. Anything missed after that is recoverable with a
changed_sincelist call. - Thin payloads. Events carry identifiers and the fields you need to route on, not whole documents. Fetch the rest, so protected health information is not sitting in your delivery logs.
POST /your-endpoint
Solved-Enroll-Event: recommendation.ready
Solved-Enroll-Timestamp: 1791040328
Solved-Enroll-Signature: v1=3a7f9c...
{
"id": "evt_7Hs2Qa",
"type": "recommendation.ready",
"created": "2027-10-15T14:32:08Z",
"data": {
"recommendation_id": "rec_3Wd9Lp",
"client_id": "cli_8Fq2mR",
"external_ref": "crm-40219",
"line": "medicare",
"top_plan_id": "pln_4ac81b"
}
}
Rate limiting
Limits are per account, not per token, so issuing a token per integration does not buy extra headroom. It does make it obvious which integration is spending the budget.
- 600 requests per minute by default. Every response reports the limit, the remaining allowance, and when the window resets.
- 429 means back off. Read
Retry-Afterand honor it. Retrying immediately on a 429 is the fastest way to stay rate limited. - Quoting and ranking cost more. A Medicare quote prices a drug list across formularies, so quote and rank calls draw on a separate, smaller concurrency allowance than plain reads.
- Bulk work has its own path. Do not page a list endpoint to move a whole book. Use a scheduled export or a warehouse extract, which are built for volume and do not compete with live agents for the limit.
- Higher limits on request. Tell us the integration and the pattern, and the limit is raised where it makes sense.
Defaults
- 600 / min
- Requests per account, reported on every response.
- 100
- Maximum records per page on a list endpoint.
- 24 hours
- Window in which an idempotency key replays the original response.
- 24 hours
- Webhook retry window before delivery stops.
Error format
Standard HTTP status codes, plus one error object shape everywhere so you write the handler once.
HTTP/1.1 422 Unprocessable Entity
{
"error": {
"type": "eligibility_error",
"code": "election_period_not_open",
"message": "The client has no election period open on the requested effective date.",
"param": "effective_date",
"detail": {
"effective_date": "2027-03-01",
"next_open_period": "AEP",
"next_open_date": "2027-10-15"
},
"request_id": "req_Pd81Zk"
}
}
type is broad enough to switch on, code is stable and specific, message is safe to log but not written for a client to read, and request_id is the thing to quote when you email us. Validation failures list every offending field rather than stopping at the first.
200 OK |
The request succeeded and the body is the resource or the list. |
|---|---|
201 Created |
A write created a resource. The body is the new resource, and Location points at it. |
202 Accepted |
Work was queued: a case file generation, a warehouse export, or a carrier submission that the carrier answers asynchronously. Poll the resource or wait for the webhook. |
400 Bad Request |
The body was not valid JSON, or a required parameter is missing. |
401 Unauthorized |
No token, a malformed token, or a revoked token. |
403 Forbidden |
The token is valid but the account cannot reach that record. Hierarchy scope and appointment-based permissions both produce this. |
404 Not Found |
No such resource, or one outside the token's scope. We do not distinguish the two, because doing so leaks whether a record exists. |
409 Conflict |
An idempotency key was replayed with a different body, or a merge target is already merged. |
422 Unprocessable Entity |
The request was well formed but cannot be acted on: an election period that does not permit the plan, a county outside the service area, or a profile the carrier knocks out. The error detail names the rule. |
429 Too Many Requests |
Rate limited. Read Retry-After and back off. |
5xx |
Our problem. Writes are safe to retry with the same idempotency key. |
Access and licensing
There are two reasons to want a token, and they are separate conversations.
- Platform access. If your agency is using Solved Enroll, API tokens are issued as part of cohort onboarding. Ask during onboarding and you get a token scoped to your agency with the same hierarchy rules your users have.
- Licensing the recommender. If you want ranking inside your own product without your users touching Solved Enroll, the AI Plan Recommender licenses separately as a REST endpoint or an MCP server. That is available before general availability. See licensing on the AI Plan Recommender page.
- What ships with credentials. Field-level schemas, enumerations, sandbox data, the webhook signing secret, and a contact who has read your integration plan. This page is the shape; those are the details.
- No self-service signup. Tokens reach protected health information and Medicare beneficiary data, so they are issued to a named agency or licensee after a conversation. See security and privacy for how credentials are handled.
FAQs
API questions
Can I get credentials today?
Not self-service. Solved Enroll is in private beta, so tokens are issued through cohort onboarding for platform access, or through a licensing conversation if you want the AI Plan Recommender on its own. There is no signup form that hands you a key.
Is this documentation or a specification?
It documents the shape of the API: the resource groups, the endpoints, the conventions, the event types, and the error format. It is written so you can plan an integration and ask precise questions. Exact field lists, enumerations, and schemas come with your credentials, because they are still moving while cohorts are onboarding.
Can I license just the recommender?
Yes. The ranking engine is available as a REST endpoint and as an MCP server without platform access, so a CRM, a dialer, or an agent runtime can rank plans without rebuilding Medicare and underwriting logic. See the licensing section on the AI Plan Recommender page.
How do I avoid double-creating records when my job retries?
Send an Idempotency-Key header on writes. Replaying the same key with the same body inside 24 hours returns the original response rather than creating a second record. Replaying the same key with a different body returns 409, which is a bug in your job rather than a problem with the record.
What do the webhook signatures look like?
Each delivery carries a timestamp and a signature computed over the timestamp and the raw body with your endpoint secret. Verify the signature against the raw bytes before parsing, and reject deliveries whose timestamp is outside your tolerance window so a captured request cannot be replayed at you later.
Does a token see everything in my agency?
It sees exactly what its owning account sees. A token belonging to an agent reads that agent's book; a token belonging to a principal reads the downline. Hierarchy scope and appointment-based permissions are enforced on the API, not just in the interface, and every read is attributed to the token in the audit trail.
Something else? Contact us
Planning an integration?
Send us what you are building and which direction the data moves. We will tell you which endpoints and events you need before you write anything.