Overview
The Yuzu API allows all users to programmatically pull data and manage groups and members. It complements the portal interface for organizations that need automated workflows or third-party integrations. You can use it to pull data into your app, create a group and upload a census, maintain eligibility, and much more.
Yuzu takes an “API-first” approach to vendor integrations. This is preferable for both time savings and functionality improvements. Vendors that take an “API-first” approach receive stronger consideration for inclusion on our “Preferred Vendors” list. Yuzu uses this list to recommend vendors to new blocks of business.
If you spot a gap, please reach out to Adam (adam@yuzu.health), a Product Manager on the Yuzu team. Yuzu’s goal is to have the best API in the industry.
New to APIs? See I have no technical experience, can I really use an API? at the bottom of this guide.
Quick start
Generate API key
In the portal, open the left sidebar and select your organization.
Select Settings, then Developer.
Generate the key and copy it. Yuzu shows the key only one time.


Pick an environment
Environment | Base URL | Reference | ||
|---|---|---|---|---|
Production |
| |||
Demo / test |
|
Make your first request
This request lists the groups your key can see.
curl
curl https://backend.yuzu.earth/v2/groups \
-H "Authorization: Bearer $YUZU_API_KEY"Python
import os
import requests
BASE_URL = "https://backend.yuzu.earth"
HEADERS = {"Authorization": f"Bearer {os.environ['YUZU_API_KEY']}"}
response = requests.get(f"{BASE_URL}/v2/groups", headers=HEADERS)
response.raise_for_status()
print(response.json())JavaScript (Node 18+)
const BASE_URL = "https://backend.yuzu.earth";
const headers = { Authorization: `Bearer ${process.env.YUZU_API_KEY}` };
const response = await fetch(`${BASE_URL}/v2/groups`, { headers });
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
console.log(await response.json());All examples in this guide use the demo URL and IDs that start with test_. Replace them with your own values.
Authentication
Send your API key in the Authorization header as a bearer token on every request:
Authorization: Bearer <YOUR_API_KEY>Yuzu also accepts the key in an api-key header. Use one of the two, not both.
A request with a missing, wrong, or expired key returns 403 Forbidden.
Rotate a key
When you generate a new key, Yuzu deletes the old keys for your user. The old key stops working immediately. Update your systems right after you generate the new key.
What your key can see
Your key can see only the data for plans where your organization is the plan sponsor or a vendor on the plan. This applies to every endpoint.
A list endpoint (for example
GET /v2/members) returns only records on plans you can access.A request for one record on a plan you cannot access returns
403 Forbiddenor404 Not Found.
If you expect to see a group and you do not, ask the Plan Designer to add your organization as a vendor on the plan.
Pagination
List endpoints return one page at a time. Pages use a cursor, not page numbers.
Response shape
{
"results": [ { "id": "test_mem_001" }, { "id": "test_mem_002" } ],
"startAfter": "test_mem_002"
}Field | Meaning |
|---|---|
| The records on this page. |
| The cursor for the next page. It is |
To get the next page, send the startAfter value back as a query parameter:
curl "https://backend.yuzu.earth/v2/members?startAfter=test_mem_002" \
-H "Authorization: Bearer $YUZU_API_KEY"Rule | Value |
|---|---|
Page size | 50 records (100 for member lists). You cannot change it. |
Order | Oldest first (by creation time, then |
Total count | Not returned. Keep paging until |
Get every page (Python)
def list_all(path, params=None):
params = dict(params or {})
while True:
response = requests.get(f"{BASE_URL}{path}", headers=HEADERS, params=params)
response.raise_for_status()
page = response.json()
yield from page["results"]
if not page.get("startAfter"):
break
params["startAfter"] = page["startAfter"]
members = list(list_all("/v2/members"))Get every page (JavaScript)
async function* listAll(path, params = {}) {
const query = new URLSearchParams(params);
while (true) {
const response = await fetch(`${BASE_URL}${path}?${query}`, { headers });
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const page = await response.json();
yield* page.results;
if (!page.startAfter) break;
query.set("startAfter", page.startAfter);
}
}
const members = [];
for await (const member of listAll("/v2/members")) members.push(member);Errors
Yuzu uses standard HTTP status codes. Every error has the same JSON body:
{
"statusCode": 400,
"timestamp": "2026-11-04T16:31:50.988Z",
"message": "Validation failed",
"path": "/v3/groups/test_grp_001/enroll/add-subscriber"
}For a 400 caused by a request that fails validation, the body also has an errors array. Each item has a path (the field that failed) and a message.
Status | Meaning | What to do | |
|---|---|---|---|
| The request is not valid (missing field, wrong format, duplicate value, bad cursor). | Read | |
| The API key is missing, wrong, or expired, or your organization cannot access this record. | Check the | |
| The record does not exist. | Check the ID. Make sure you are on the correct environment. | |
| The request is valid, but Yuzu cannot do it in the current state. | Read | |
| Too many requests. | Wait for the number of seconds in the | |
| An error on the Yuzu side, usually caused by an unexpected data shape. | Check the request body against the reference. If it continues, contact Yuzu with the |
Rate limits
Limits apply per API key.
Every response has these headers:
Header | Meaning |
|---|---|
| The limit for this window. |
| Requests left in this window. |
| Seconds until the window resets. |
| Only on a |
Versioning
The version is part of the path: /v1, /v2, /v3. Each endpoint lives in one version. The newest version of an endpoint is the one to use. For example, use /v3/.../add-subscriber, not the older /v2/.../new-subscribers.
Within a version, we only add things: new optional fields and new endpoints. Your code must accept fields that it does not know. If we must remove or rename a field, we contact each partner before the change. We do not have a fixed notice period.
Platform structure
You must know how the platform is structured before you use the API.
Object | What it is | ID changes on renewal? |
|---|---|---|
Plan Designer | Creates plan designs and manages the vendors on them. Full portal permissions. This is a type of vendor. | No |
Vendor | A company on a plan that is not the employer (broker, PBM, stop-loss). Portal access depends on its role on the plan. | No |
Sponsor | The employer. Holds company details such as EIN and address. One sponsor can have many groups. | No ( |
Sponsor Employee | A sponsor user with admin roles, for example the HR contact. | No |
Group (group policy) | The sponsor for one plan year: start date, end date, group number, onboarding status. API name: | Yes |
Plan Design | A template: benefits, accumulators, cost share, vendors, ID cards, welcome material. On the Designs tab.z | No ( |
Plan | A copy of a plan design attached to one group. On the Plans tab. | Yes ( |
Member | A person: name, date of birth, demographics. | No ( |
Subscriber | The employee who enrolls. | No |
Dependent | The spouse or child of a subscriber. | No |
Coverage | One member on one plan for a period: start date, end date, claims. | Yes ( |

Object details
Sponsor vs. Group vs. Plan
Sponsors are durable, the ID will stay the same from year to year. Core information like the EIN is associated with the Sponsor ID.
Groups are for a given plan year. The Group ID will change on renewal. The start and end date of the policy is associated with the Group ID.
Plans are the benefits, accumulators, cost sharing, vendors, cards, welcome materials associated with a single plan. One group can, and frequently will have multiple plans associated with it (e.g., 1500 PPO and 7500 HSA).

Member vs. Coverage
Member IDs are durable and will stay the same. Core demographic information like Name, Date of Birth is associated with the Member ID.
Coverage IDs will change with the new group IDs or be unique if a Member has multiple coverages on different policies. The start and end date of the policy is associated with the Coverage IDs.
Plan Design vs. Plan
You must know this difference if you make groups.
Plan Designs are the templates attached to a group have PlanDesignIDs that do not change. These live on the ‘Designs” tab in the portal. Similar to plans referenced above, these have benefits, accumulators, cost sharing, vendors, cards, welcome materials associated with a single plan. These are created during the “Build” and ideally remain templated and untouched.
Plan is created when a PlanDesign is attached to a group during Group Onboarding, the benefits, etc. now are associated with that group. This will be a unique identifier, scoped to that specific group. The “basePlanDesignId” will continue to be associated with this Plan ID for auditability and traceability. Worth noting, the “basePlanDesignId” will remain in the returned pull, even if the Plan’s benefits or any other details change.

Common endpoints
These are the endpoints partners use most. The reference has every endpoint and every field.
Accumulators
Use accumulators to show how much of the deductible and out-of-pocket maximum a member has used. This is the most-called endpoint in the Yuzu API.
GET /v2/accumulator/coverage/{coverageId}
GET /v2/accumulator/coverage/buckets/{coverageId}Get accumulators
curl https://backend.yuzu.earth/v2/accumulator/coverage/test_cov_001 \
-H "Authorization: Bearer $YUZU_API_KEY"Things to know:
The response is an array. Each item is one accumulation period. There is usually one item. There can be more than one when the plan has run-in, a mid-year calendar-year plan, or stop-loss incurred months. Find the item whose
startDateandendDatecontain the date you care about.All amounts are in cents.
45000is $450.00.memberis the individual accumulator.familyis the family accumulator.appliesToTierslists the benefit tiers (for example in-network and out-of-network) that count toward this accumulator.When there is no out-of-pocket maximum,
maxOop.limitCentsisnullandmaxOop.isUnlimitedistrue.Yuzu leaves out accumulators that are 0.
Get buckets (visit, day, and dollar limits)
curl https://backend.yuzu.earth/v2/accumulator/coverage/buckets/test_cov_001 \
-H "Authorization: Bearer $YUZU_API_KEY"Members
GET /v2/members
GET /v2/members/{memberId}
GET /v2/groups/{groupPolicyId}/membersQuery parameters for GET /v2/members
Parameter | Required | Description |
|---|---|---|
| No | Return only members with coverage on this plan. Returns |
| No | Pagination cursor. |
Things to know:
One member can have many
coverages, one per plan and plan year. Usecoverages[].id(thecoverageId) for accumulators, claims, and enrollment changes.coverageStatusisactive,inactive,pending,cobra, orannulled. Anannulledcoverage was voided. Do not use it.relationshipissubscriber,spouse, orchild. For a dependent,subscriberIdpoints to the subscriber's coverage. For a subscriber it isnull.A member's
coveragesinclude only coverages on plans your key can access, even when you filter byplanId.There is no "last modified" field. To find changes, compare two pulls, or use Webhooks.
Groups
GET /v2/groups
GET /v2/groups/{groupPolicyId}Things to know:
idis thegroupPolicyId. Use it in paths.groupIdis the group number that people read. Do not mix them up.plans[].idis theplanIdto use when you enroll members.sponsor.idstays the same across renewals.idandplans[].idchange on renewal.rulesholds the waiting period and accumulator settings.enrollmentWaitingPeriodandenrollmentWaitingPeriodTypeare deprecated. UsewaitingPeriodDaysandenrollmentWaitingPeriodRounding
Claims
Use claims to pull medical, pharmacy, and plan-level claims.
GET /v1/claims/medical
GET /v1/claims/medical/{claimId}
GET /v1/claims/pharmacy
GET /v1/claims/pharmacy/{claimId}
GET /v1/claims/plan
GET /v1/claims/plan/{planClaimId}Endpoint | Parameter | Required | Description |
|---|---|---|---|
Medical |
| No | Claims with a service start date on or after this date ( |
Medical |
| No | Claims with a service end date on or before this date. |
Pharmacy |
| Yes | First fill date to include ( |
Pharmacy |
| Yes | Last fill date to include. |
Medical, Pharmacy |
| No | Only claims for this group. |
Medical, Pharmacy |
| No | Only claims for this coverage. |
Medical, Pharmacy |
| No | Find one claim by claim number (for network claims, the network trace number). |
Plan |
| No | Plan claims incurred after this date. |
Plan |
| No | Plan claims incurred before this date. |
All |
| No | Pagination cursor. |
Things to know:
All amounts are in cents and are on each
serviceLinesitem. Add the lines to get claim totals.statusisapproved,denied, orprocessing.finalDispositionistruewhen the claim is in its final state. A claim withfinalDisposition: falsecan still change.processedDateandpaidDatearenulluntil the claim is processed or paid.categoryisprofessionalorinstitutional.serviceLines[].networkStatusisINN(in network),OON(out of network), orMIX.Pharmacy claims have amounts at the claim level (
planPaidCents,copayCents,deductibleCents, and others), plus drug fields such asndc,drugName, anddaysSupply.Plan claims are plan-level costs that are not tied to one member, for example a vendor invoice. They have
groupPolicyId,amountCents,incurredDate,paidDate, anddescription.For large pulls, send one request per group and one date range at a time, and follow
startAfter.
Enrollment
POST /v3/groups/{groupPolicyId}/enroll/add-subscriber
POST /v3/groups/{groupPolicyId}/enroll/{subscriberCoverageId}/add-dependent
POST /v3/groups/{groupPolicyId}/enroll/terminate
GET /v1/enrollmentRequests/{enrollmentRequestId}
GET /v2/enroll/{enrollmentRequestId}/coveragesBody fields:
Field | Required | Description |
|---|---|---|
| Yes |
|
| For any reason except | Coverage start date for a qualifying life event. |
| Yes | The |
| For | Yuzu calculates the coverage start from the hire date and the plan's waiting period. |
| Yes | The employee. Required: |
| No | Same fields as |
| No | An array. Same fields as |
| No |
|
| No | Saved on the enrollment request. |
Terminate coverage
When you terminate a subscriber's coverage, Yuzu terminates the whole family. When you terminate a dependent's coverage, Yuzu terminates only that dependent.
employmentTerminationis valid only on a subscriber's coverage. For this reason,cobraEligiblesets COBRA eligibility. For other reasons, Yuzu sets COBRA eligibility from the reason.terminationDateis required for every reason exceptmistakeEnrollment. UsemistakeEnrollmentto void a coverage that was added by mistake.
Check the result
Poll the enrollment request until its status stops changing:
curl https://backend.yuzu.earth/v1/enrollmentRequests/test_er_001 \
-H "Authorization: Bearer $YUZU_API_KEY"{ "id": "test_er_001", "status": "applied", "errors": [] }Status | Meaning | |
|---|---|---|
| Done. The coverages are changed. | |
| Not applied. Read | |
| Waiting for a person at Yuzu or the sponsor to approve. | |
| Yuzu is still working on it. Poll again. |
When the request is applied, get the coverages it changed:
curl https://backend.yuzu.earth/v2/enroll/test_er_001/coverages \
-H "Authorization: Bearer $YUZU_API_KEY"changeType is created, terminated, annulled, planChanged, or updated. The list is empty until the request is done. A plan change reports the new coverage as created and the old one as annulled. Do not send claims or care events against an annulled coverage.
Popular use cases
Create a group and add an initial enrollment
1. Get available plan designs with GET v2/designs
Returns list of planDesignId values with their configurations
Use these IDs to programmatically assign plans when creating groups
2. Create the group with POST v2/groups
Include groupId, adminEmail, sponsorName, and ein
Reference valid planDesignId, benefitBundleId, and vendorId from step 1
Set effectiveDate and terminationDate for the policy period
3. Add members with POST v2/groups/{groupPolicyId}/enroll/initial-enrollment
Provide required fields: memberId, firstName, lastName, dateOfBirth, ssn
Set memberType (e.g., "employee", "dependent")
Include address and contact information
Pull down enrollment and claims data
1. Query enrollments with GET v2/members
Filter by groupId to get all enrollments for a specific group
Use effectiveDate and terminationDate parameters to filter by date range
Include status parameter to filter by enrollment status (active, terminated, pending)
2. Retrieve claims with GET v1/claims/medical
Filter by groupId or memberId as needed
Use serviceDate range parameters to get claims within a specific period
Claims include claimStatus, claimAmount, and processing details
3. Export data in bulk
Use pagination parameters (page, limit) for large datasets
Results are returned with totalCount for tracking progress
Consider filtering by lastModifiedDate to pull only updated records
Maintain eligibility
1. Pull down relevant planIds with GET /v2/groups
Returns a paginated list of groups
Each group carries a plans array, and planId is the id on each plan object
Add subscribers and their dependents with POST /v3/groups/{groupPolicyId}/enroll/add-subscriber
Provide a subscribers array; each subscriber sets plan (the plan id from Step 1), self, optional spouse/children, and tags
Note: if a member changes plans, Yuzu must be notified - we currently only support plan changes initiated by Yuzu
Verify the enrollment request succeeded, Poll GET /v1/enrollmentRequests/{enrollmentRequestId}
If it fails, we give you portal access to see exactly what happened.
Pull down the new member and coverage UUID via GET /v2/members
Today you'd list all member (optionally filtered by planId) and match demographics against your system to map the UUID.
Additional functionality:
Add a dependent to an existing subscriber → POST /v3/groups/{groupPolicyId}/enroll/{subscriberCoverageId}/add-dependent
Terminate coverage → POST /v3/groups/{groupPolicyId}/enroll/terminate
Update demographic information → PATCH /v2/members/{memberId}
FAQs
What is the API versioning and deprecation policy?
Yuzu's public API is versioned by URL prefix (v1, v2), each authenticated via API-key bearer tokens.
There is no codified sunset timeline (no guaranteed notice period) but we do proactively reach out to Partners before all deprecations and can deploy deprecation if reasonable.
Will Yuzu ever remove or rename a field in an existing endpoint?
Our goal is within a version, we only add fields. New functionality is introduced as new (optional) fields or new endpoints.
If we do remove fields, we will provide proactive notice to all of our partners.
What is the pagination limit and can it be overridden?
Pagination is cursor-based, not offset-based. The default page size is PAGINATION_LIMIT = 50, except member-listing endpoints which use MEMBER_PAGINATION_LIMIT = 100
Is there a last_modified field on objects?
No. There is no updatedAt or lastModified field exposed in the public API schema or types. To see changes, pull down data consecutively and compare the diff. An alternative is to use “Webhooks” which are similarly configurable from the developers page to receive data each time an event occurs. Details on Webhooks HERE.
How do I get and pass an API key?
Keys are created in the vendor portal's Developer settings and passed as a bearer token; each key matches to an owning org/employee.
What is the ordering based on?
Results are ordered deterministically by createdAt then id, so the startAfter cursor is an object id.
Is there a sandbox / test environment?
Yes, we allow you to test our API through our yuzu.earth environment.
How do I rotate an API key?
Creating a new key deletes existing keys for that employee (so rotation = create new, old one immediately stops working), and the secret is shown only once.
What does an error response look like?
All errors return a consistent JSON body: { timestamp, path, message }, with the appropriate HTTP status. For 400 BadRequest validation failures, the body additionally includes a Zod errors array detailing which fields failed. More on common pitfalls.
Does Yuzu plan on "publishing" its Yuzu API - Open API Specification (OAS 3.0 JSON or YAML file download link)?
Not currently on the roadmap.
Does this data include all termed and renewed policies?
Yes, if you are added as a vendor to a termed or renewed policy, you will be able to see this data. One thing to note is that as the group renews the groupID changes. The associated planId and coverageId also change. sponsorId and memberId will be consistent across years/policies.
Common pitfalls
Unique constraints cause 400 errors. These fields must be unique across the system:
adminEmail: each group needs a distinct admin email
groupId (group number): cannot reuse across groups
ein + date range combination: a sponsor cannot have overlapping policies with the same EIN and dates
Required IDs must be valid. When creating a group, you must reference existing:
planDesignId: the plan template to use
benefitBundleId: the benefit bundle for the plan
vendorId: the broker or vendor associated with the plan
Invalid IDs return a 400: These IDs are available in the portal or from your Plan Designer:
500 errors are usually data issues. If you receive a 500 error, double-check your request
Payload against the required format. Common causes include missing nested fields or malformed data structures.
I have no technical experience, can I really use an API?
Yes! With the rise of apps like Postman, Claude Code, and Codex it is easy and intuitive to pull down information via an API. We encourage all partners to attempt to use an API prior to a data exchange feed which are more timely to set up, and require dated SFTP technology.
To do this you will be using a GET command and pull down items like claims and eligibility. For basics, Postman has a good article: What is an API?
However, please leave POST, PATCH, and DELETE endpoints for more technical developers as these have a direct impact on the objects within the portal.
Typical steps are as follows:
Pull down a developer key from either the Demo or Prod environment
Test if the data you want is returning directly from our API documentation sites for Demo or Prod Environment.
If you are not able to pull down the information that you believe you should be able to (e.g., 403 Forbidden), your org likely needs to have permissions adjusted. Reach out to partners@yuzu.health with “API Permissions Issues” as the subject.
Once tested, begin testing pulling down the information in your own environment. Here, you can use AI tools to describe what you are trying to do (e.g., pull claims data monthly) and it can help from there. It is important to save down this context into a .md file so it can be referenced later.
Tip: If you want to kickstart the context for the coding tool, you can copy and paste the text from this doc into the context window as well as link directly to the API documentation site.
One last thing, it is highly recommended to keep the Yuzu portal open while testing. If you are receiving an empty set on a pull, there is a good chance there is no data to begin with. If you are unable to POST an object, there is a good chance you do not have permissions to view in the portal. When troubleshooting the API with Yuzu, the first question we will always ask is what does it look like on the portal.
