API

We offer a Public API to all partners and have developed a self-serve process to integrate. 

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

  1. In the portal, open the left sidebar and select your organization.

  2. Select Settings, then Developer.

  3. Generate the key and copy it. Yuzu shows the key only one time.

Left sidebar, ‘Settings’
Generate an API key on the ‘Developer’ tab in Settings

Pick an environment

Environment

Base URL

Reference

Production

https://api.yuzu.health

api.yuzu.health/api/docs

Demo / test

https://backend.yuzu.earth

backend.yuzu.earth/api/docs

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 Forbidden or 404 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

results

The records on this page.

startAfter

The cursor for the next page. It is null on the last page.

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 id). Medical claims sort by service start date.

Total count

Not returned. Keep paging until startAfter is null.

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

400

The request is not valid (missing field, wrong format, duplicate value, bad cursor).

Read message and errors. Fix the request. Do not retry without a change.

403

The API key is missing, wrong, or expired, or your organization cannot access this record.

Check the Authorization header. Then check the ID and make sure the record is on a plan your organization is on.

404

The record does not exist.

Check the ID. Make sure you are on the correct environment.

422

The request is valid, but Yuzu cannot do it in the current state.

Read message.

429

Too many requests.

Wait for the number of seconds in the Retry-After header, then retry.

500

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 timestamp and path.

Rate limits

Limits apply per API key.

Every response has these headers:

Header

Meaning

RateLimit-Limit

The limit for this window.

RateLimit-Remaining

Requests left in this window.

RateLimit-Reset

Seconds until the window resets.

Retry-After

Only on a 429. Seconds to wait before you retry.

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.id)

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: groupPolicyId.

Yes

Plan Design

A template: benefits, accumulators, cost share, vendors, ID cards, welcome material. On the Designs tab.z

No (planDesignId)

Plan

A copy of a plan design attached to one group. On the Plans tab.

Yes (planId)

Member

A person: name, date of birth, demographics.

No (memberId)

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 (coverageId)

Simplified structure of the platform

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).

The Group houses associated Plans which are on the “Plans” tab. The concept of a Sponsor is not visible in the portal but enables you to be able to toggle between policies in the upper left corner.

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.

Templated Plan Designs live on the “Designs” tab. Plans live in the “Plans” tab on onboarding and active groups.

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 startDate and endDate contain the date you care about.

  • All amounts are in cents. 45000 is $450.00.

  • member is the individual accumulator. family is the family accumulator.

  • appliesToTiers lists 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.limitCents is null and maxOop.isUnlimited is true.

  • 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}/members

Query parameters for GET /v2/members

Parameter

Required

Description

planId

No

Return only members with coverage on this plan. Returns 403 if your key cannot access the plan.

startAfter

No

Pagination cursor.

Things to know:

  • One member can have many coverages, one per plan and plan year. Use coverages[].id (the coverageId) for accumulators, claims, and enrollment changes.

  • coverageStatus is active, inactive, pending, cobra, or annulled. An annulled coverage was voided. Do not use it.

  • relationship is subscriber, spouse, or child. For a dependent, subscriberId points to the subscriber's coverage. For a subscriber it is null.

  • A member's coverages include only coverages on plans your key can access, even when you filter by planId.

  • 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:

  • id is the groupPolicyId. Use it in paths. groupId is the group number that people read. Do not mix them up.

  • plans[].id is the planId to use when you enroll members.

  • sponsor.id stays the same across renewals. id and plans[].id change on renewal.

  • rules holds the waiting period and accumulator settings. enrollmentWaitingPeriod and enrollmentWaitingPeriodType are deprecated. Use waitingPeriodDays and enrollmentWaitingPeriodRounding

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

serviceStartDate

No

Claims with a service start date on or after this date (YYYY-MM-DD).

Medical

serviceEndDate

No

Claims with a service end date on or before this date.

Pharmacy

fillStartDate

Yes

First fill date to include (YYYY-MM-DD).

Pharmacy

fillEndDate

Yes

Last fill date to include.

Medical, Pharmacy

groupPolicyId

No

Only claims for this group.

Medical, Pharmacy

coverageId

No

Only claims for this coverage.

Medical, Pharmacy

claimNumber

No

Find one claim by claim number (for network claims, the network trace number).

Plan

incurredAfterDate

No

Plan claims incurred after this date.

Plan

incurredBeforeDate

No

Plan claims incurred before this date.

All

startAfter

No

Pagination cursor.

Things to know:

  • All amounts are in cents and are on each serviceLines item. Add the lines to get claim totals.

  • status is approved, denied, or processing. finalDisposition is true when the claim is in its final state. A claim with finalDisposition: false can still change.

  • processedDate and paidDate are null until the claim is processed or paid.

  • category is professional or institutional. serviceLines[].networkStatus is INN (in network), OON (out of network), or MIX.

  • Pharmacy claims have amounts at the claim level (planPaidCents, copayCents, deductibleCents, and others), plus drug fields such as ndc, drugName, and daysSupply.

  • Plan claims are plan-level costs that are not tied to one member, for example a vendor invoice. They have groupPolicyId, amountCents, incurredDate, paidDate, and description.

  • 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}/coverages

Body fields:

Field

Required

Description

reason

Yes

newHire, lossOfOtherCoverage, medicaidMedicareInelig, legalMandate, or partTimeToFullTime.

effectiveDate

For any reason except newHire

Coverage start date for a qualifying life event.

subscribers[].plan

Yes

The planId from GET /v2/groups.

subscribers[].hireDate

For newHire

Yuzu calculates the coverage start from the hire date and the plan's waiting period.

subscribers[].self

Yes

The employee. Required: firstName, lastName, birthday, line1, city, state, zip.

subscribers[].spouse

No

Same fields as self.

subscribers[].children

No

An array. Same fields as self.

subscribers[].tags

No

location, department, or employeeClass tags. employeeClass can change the waiting period.

notes

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.

  • employmentTermination is valid only on a subscriber's coverage. For this reason, cobraEligible sets COBRA eligibility. For other reasons, Yuzu sets COBRA eligibility from the reason.

  • terminationDate is required for every reason except mistakeEnrollment. Use mistakeEnrollment to 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

applied

Done. The coverages are changed.

failed, rejected, denied

Not applied. Read errors[].message. You can also see the full detail in the portal.

pendingApproval

Waiting for a person at Yuzu or the sponsor to approve.

submitted, queued, processing, ingested, approved

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

  1. 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

  1. Verify the enrollment request succeeded, Poll GET /v1/enrollmentRequests/{enrollmentRequestId}

  • If it fails, we give you portal access to see exactly what happened.

  1. 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:

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:

  1. Pull down a developer key from either the Demo or Prod environment

  2. Test if the data you want is returning directly from our API documentation sites for Demo or Prod Environment.

    1. 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.

  3. 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.

    1. 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.

Was this page helpful?