Member Sync
How member sync works under the hood — the replacement model, reference matching, async processing, and how to read the results.
Member sync is the single endpoint you use to add, update, and remove members within a group. You send the complete current roster for one group, and Vitable reconciles the existing membership against what you submitted: anything new is added, anything missing is removed, anything unchanged is left alone.
You send one POST /v1/groups/{group_id}/members/sync request containing your members, and Vitable processes them asynchronously. The HTTP response is an acceptance receipt — not the final result. Final outcomes appear on the sync request itself once processing completes.
This page covers the replacement model, how members are matched across syncs, the async flow, and how to read results — including partial failures.
The Replacement Model
Section titled “The Replacement Model”Every sync replaces the set of members currently subscribed to the group’s plans. Vitable treats the payload as the new truth and reconciles by reference_id:
| What you submit | What Vitable does |
|---|---|
A reference_id not currently subscribed in the group | Add — provision/attach the member, start a plan subscription |
A reference_id currently subscribed in the group | No-op — leave the existing subscription alone (field-level updates to the member record are applied) |
A reference_id currently subscribed in the group but missing from the payload | Remove — end the plan subscription |
An empty members: [] is a valid sync. It removes everyone in the group.
How References Are Matched
Section titled “How References Are Matched”reference_id is your stable, partner-owned identifier for a member. Vitable uses it to recognize the same person across syncs.
Within a single payload, every reference_id must be unique. Sending the same reference_id twice in one sync fails synchronous validation with HTTP 400.
When a new reference_id is added, Vitable will re-use an existing person record if the contact details match someone it already knows — typically by email and date of birth. The member retains their prior care history; only the group membership and the plan subscription are new.
The Async Flow
Section titled “The Async Flow”sequenceDiagram
participant P as Partner
participant V as Vitable API
P->>V: POST /v1/groups/{id}/members/sync (N members)
V-->>P: 202 { data: { request_id, group_id, accepted_at } }
Note over V: Triage: classify each member as add / remove / unchanged
Note over V: Async processing per member
P->>V: GET /v1/groups/{id}/members/sync/{request_id}
V-->>P: 200 { data: { results: { added, removed, failures } } }
Flow summary:
- You submit a roster for one group.
- Vitable validates the payload structure and returns
202with arequest_id(grpmsr_…) andaccepted_at. - Vitable reconciles your payload against the group’s current roster and processes each add/remove asynchronously.
- When processing is complete, the sync request’s
resultsfield is populated with per-member outcomes.
Results: Reading What Happened
Section titled “Results: Reading What Happened”To inspect the outcome of a sync, call:
curl -X GET https://api.vitablehealth.com/v1/groups/{group_id}/members/sync/{request_id} \ -H "Authorization: Bearer $VITABLE_API_KEY"Until processing finishes, completed_at and results are both null. Once processing completes, completed_at is a timestamp and results is an object with three lists:
{ "data": { "request_id": "grpmsr_pQr456sTu789vWxYzAbCd", "group_id": "grp_zYxWvUtSrQpOnMlKjIhGfE", "accepted_at": "2026-03-25T14:30:00+00:00", "completed_at": "2026-03-25T14:30:08+00:00", "results": { "added_group_member_ids": [ "grpmbr_lMn345oPq678rSt", "grpmbr_aBc123dEf456gHi" ], "removed_group_member_ids": ["grpmbr_xYz789uVw012aBc"], "failures": [ { "reference_id": "mem-099", "operation": "add", "reason": "Plan pln_… not found" } ] } }}| Field | Meaning |
|---|---|
added_group_member_ids | Members newly subscribed to the group’s plan. Each ID is prefixed grpmbr_. |
removed_group_member_ids | Members whose plan subscriptions were ended. |
failures | Per-member errors. A member listed here was not applied — the rest of the batch processed normally. |
Each failure entry includes the reference_id you submitted, the operation Vitable was attempting (add or remove), and a human-readable reason.
Idempotency and Repeated Syncs
Section titled “Idempotency and Repeated Syncs”A sync that matches the group’s existing roster exactly is a no-op — no members are added, none are removed, no failures are recorded. This makes it safe to re-submit the same roster:
- After a network error, if you don’t know whether the previous request landed, you can safely resubmit.
- On a schedule, you can run periodic full-roster syncs (e.g. nightly) to keep Vitable in lockstep with your source of truth.
The only thing a re-submitted sync changes is the audit trail: every accepted submission creates a new request_id, even if zero members move.
Partial Failures
Section titled “Partial Failures”A sync that is accepted (202) does not guarantee every member processes successfully. The two most common reasons a single member ends up in failures are:
plan_idnot available to your organization. The plan UUID isn’t linked to your org. Failures cite"Plan {id} not found". Re-checkGET /v1/plansfor the IDs you can use.- Member already has an active subscription elsewhere. The matched member holds an active plan subscription under a different organization. Vitable preserves their existing subscription untouched and rejects the add with
"Member already has an active subscription!". Coordinate with the other organization to end the existing subscription before re-syncing.
Other rows in the same sync are unaffected. A failure list with one entry means one member didn’t apply; the other N–1 did.
Synchronous Validation
Section titled “Synchronous Validation”These errors come back immediately as HTTP 400 — no processing occurs:
- A required field is missing on any member (
reference_id,first_name,last_name,date_of_birth,phone,plan_id,address). phoneis not a 10-digit US number.addressis missing a required field (address_line_1,city,state,zipcode) orstateis not a valid 2-letter US state/territory code.plan_idis not a UUID.- Two members share the same
reference_idwithin the payload. - The
group_idin the URL is malformed or does not belong to your organization.
The 400 response lists every failing field. Fix and resubmit; nothing changes in the group until a 202 is returned.
Related Concepts
Section titled “Related Concepts”- Groups — what a group is and how to organize them.
- Member Lifecycle — what happens to a member after they’re synced.
Related API Endpoints
Section titled “Related API Endpoints”POST /v1/groups/{id}/members/sync— Submit a member syncGET /v1/groups/{id}/members/sync/{request_id}— Retrieve a sync request’s status and resultsGET /v1/plans— List plans available to your organization