--- title: Member Sync | Vitable Docs description: 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 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 | Sync is **not** an append. If you submit `{ "members": [A, B] }` and the group already contains `[A, B, C]`, member C is removed. To add a single new member without affecting the rest, you must submit the **complete** roster — every existing member plus the new one. An empty `members: []` is a valid sync. It removes everyone in the group. ## 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. Pick a `reference_id` that is stable for the life of the member in your system — your HRIS row ID, contract member number, or customer ID. Avoid identifiers that can change (email addresses, names), or that get reissued. 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 ``` 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:** 1. You submit a roster for one group. 2. Vitable validates the payload structure and returns `202` with a `request_id` (`grpmsr_…`) and `accepted_at`. 3. Vitable reconciles your payload against the group’s current roster and processes each add/remove asynchronously. 4. When processing is complete, the sync request’s `results` field is populated with per-member outcomes. The `202` response confirms Vitable accepted your payload for processing. It does **not** mean any members have been added or removed yet. Always inspect the sync request’s `results` to confirm outcomes. There are **no webhooks** for member sync. Neither completion of the batch nor the success/failure of an individual member fires an event. To track outcomes, poll `GET /v1/groups/{group_id}/members/sync/{request_id}` and read its `results`. ## Results: Reading What Happened To inspect the outcome of a sync, call: Terminal window ``` 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 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 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_id` not available to your organization.** The plan UUID isn’t linked to your org. Failures cite `"Plan {id} not found"`. Re-check `GET /v1/plans` for 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. Always inspect `results.failures` before reporting a sync as “done” in your own UI or audit log. A `202` plus a non-empty `failures` array means partial success. ## 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`). - `phone` is not a 10-digit US number. - `address` is missing a required field (`address_line_1`, `city`, `state`, `zipcode`) or `state` is not a valid 2-letter US state/territory code. - `plan_id` is not a UUID. - Two members share the same `reference_id` within the payload. - The `group_id` in 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 - [Groups](/embedded_care/concepts/groups/index.md) — what a group is and how to organize them. - [Member Lifecycle](/embedded_care/concepts/member-lifecycle/index.md) — what happens to a member after they’re synced. ## Related API Endpoints - [`POST /v1/groups/{id}/members/sync`](/api/resources/groups/subresources/members/subresources/sync/methods/submit/index.md) — Submit a member sync - [`GET /v1/groups/{id}/members/sync/{request_id}`](/api/resources/groups/subresources/members/subresources/sync/methods/retrieve/index.md) — Retrieve a sync request’s status and results - [`GET /v1/plans`](/api/resources/plans/methods/list/index.md) — List plans available to your organization