--- title: Group Member Sync | Vitable Docs description: Submit your member roster for a group. Vitable provisions accounts and starts plan subscriptions asynchronously. --- This guide picks up where [Group Onboarding](/embedded_care/guides/group-onboarding/index.md) leaves off. Once you have a group and know which `plan_id` to subscribe members to, you can submit the group’s roster via `POST /v1/groups/{group_id}/members/sync`. The endpoint accepts the **complete current roster** for one group, validates it, and queues it for asynchronous processing. Before you begin, make sure you have a valid API key. See [Authentication](/getting-started/authentication/index.md) for details. ## Submit a Member Sync Send the complete current roster as an array of `members`: Terminal window ``` curl -X POST https://api.vitablehealth.com/v1/groups/{group_id}/members/sync \ -H "Authorization: Bearer $VITABLE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "members": [ { "reference_id": "mem-001", "first_name": "Maria", "last_name": "Garcia", "date_of_birth": "1990-05-15", "phone": "5555550101", "plan_id": "pln_aBcDeFgHiJkLmNoPqRsTuV", "address": { "address_line_1": "123 Main Street", "city": "Pittsburgh", "state": "PA", "zipcode": "15222" }, "email": "maria@beanworks.example.com" }, { "reference_id": "mem-002", "first_name": "James", "last_name": "Chen", "date_of_birth": "1985-11-22", "phone": "5555550102", "plan_id": "pln_aBcDeFgHiJkLmNoPqRsTuV", "address": { "address_line_1": "456 Oak Avenue", "address_line_2": "Apt 4B", "city": "Pittsburgh", "state": "PA", "zipcode": "15201" } } ] }' ``` A successful response is `202 Accepted` with an acceptance receipt: ``` { "data": { "request_id": "grpmsr_pQr456sTu789vWxYzAbCd", "group_id": "grp_zYxWvUtSrQpOnMlKjIhGfE", "accepted_at": "2026-03-25T14:30:00+00:00" } } ``` **Keep the `request_id`.** It’s how you’ll inspect the per-member results once processing finishes — there are no webhooks for this flow, so polling the sync request is the only way to know how each row was applied. ### Per-Member Fields | Field | Type | Required | Rules | | ------------------------ | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------- | | `reference_id` | string | Yes | 1–255 chars. Must be unique within the payload. Used to match existing members across syncs. | | `first_name` | string | Yes | Max 255 chars. | | `last_name` | string | Yes | Max 255 chars. | | `date_of_birth` | string | Yes | ISO 8601 date (e.g. `1990-05-15`). | | `phone` | string | Yes | 10-digit US number, digits only (`5551234567`). | | `plan_id` | string | Yes | A `pln_` ID available to your organization. See [Group Onboarding — Step 1](/embedded_care/guides/group-onboarding/index.md). | | `address` | object | Yes | See address fields below. | | `address.address_line_1` | string | Yes | Max 255 chars. | | `address.address_line_2` | string | No | Max 255 chars. | | `address.city` | string | Yes | Max 255 chars. | | `address.state` | string | Yes | 2-letter US state or territory code (`PA`, `CA`, etc.). | | `address.zipcode` | string | Yes | US ZIP. | | `email` | string | No | Valid email address. | Member sync **replaces the group’s active roster**. Any member previously synced into this group but **omitted** from the new payload will have their subscription ended and will be dropped from the group. This is not an append operation. Always submit the complete current roster. **Limits:** Each `reference_id` must be unique within the payload. Contact your account manager before sending payloads larger than a few thousand rows. See the [Submit Group Member Sync endpoint](/api/resources/groups/subresources/members/subresources/sync/methods/submit/index.md) for the full request and response schema. ## What Happens Next After your sync is accepted, Vitable processes each member asynchronously: 1. **Triage.** Vitable compares the incoming `reference_id` set against the members currently subscribed to your organization’s plan in this group. - References present in the payload but not currently subscribed → **add** - References currently subscribed but not in the payload → **remove** - References present in both → **no-op** (existing subscription preserved) 2. **Add.** For each new reference, Vitable provisions (or re-uses an existing) account, attaches it to the group, and starts a plan subscription on the requested `plan_id`. A new member can authenticate immediately via the passwordless flow — see [Authentication](/getting-started/authentication/index.md). 3. **Remove.** For each missing reference, Vitable ends the member’s plan subscription. The member’s account is preserved so prior care history stays accessible; they simply are no longer subscribed. 4. **Complete.** Per-member outcomes are recorded on the sync request as `results`. The `request_id` returned above identifies that record. There are **no webhooks** for group member sync. Neither completion of the batch nor success/failure of an individual member triggers an event. To verify outcomes, poll the sync request endpoint and inspect the `results` payload. ### Inspecting Results Poll `GET /v1/groups/{group_id}/members/sync/{request_id}` to read the status of a previously-submitted sync: Terminal window ``` curl -X GET https://api.vitablehealth.com/v1/groups/{group_id}/members/sync/{request_id} \ -H "Authorization: Bearer $VITABLE_API_KEY" ``` While processing is in flight, `completed_at` and `results` are both `null`. Once processing finishes, `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"], "removed_group_member_ids": [], "failures": [ { "reference_id": "mem-003", "operation": "add", "reason": "Plan pln_… not found" } ] } } } ``` A member appearing in `failures` was **not applied**. The rest of the batch still processed normally; failures do not abort the sync. ## Updating, Adding, and Removing Members Subsequent syncs follow the same model — submit the complete current roster every time. - **Add a new member:** include their record (with a new `reference_id`) in the next sync. - **Update an existing member:** keep their `reference_id` stable and submit the new values. Field-level updates are applied to the existing record. - **Remove a member:** omit their `reference_id` from the next sync. Their subscription is ended; their account is preserved so prior care history stays accessible. - **Re-add a removed member:** include them again in a later sync. Their account is reused; a fresh subscription is started. - **Remove everyone:** submit `{ "members": [] }`. Treat your `reference_id` as a stable, durable identifier for the member on **your** side (e.g. your HRIS row ID). Changing it across syncs causes Vitable to treat the row as a new member — ending the old subscription and starting a new one with broken continuity. ## Troubleshooting - **`400` on submit — missing or invalid field:** the payload failed synchronous validation. Common causes: a required address field missing, `phone` not exactly 10 digits, invalid `state` code, duplicate `reference_id` within the payload, or `plan_id` that isn’t a valid UUID. The response message lists every failing field. - **`202` accepted, but a member is in `failures` with `"Plan … not found"`:** the `plan_id` for that member is not linked to your organization. Re-check `GET /v1/plans` for the IDs you can use. - **`202` accepted, but a member is in `failures` with `"Member already has an active subscription!"`:** the matched member already holds an active plan subscription under a different organization. Their existing subscription is preserved untouched; the sync rejects the add. Coordinate with the other organization to end their subscription before re-syncing. - **Member added, but they can’t log in:** account provisioning is asynchronous and continues briefly after the sync completes. If the passwordless flow returns “user not found” right after a sync, wait a few seconds and retry. - **`results` is still `null` long after submit:** the worker may be processing a backlog. If it stays `null` for more than a few minutes on a small batch, contact Vitable support with the `request_id`. ## Related API Endpoints - [`POST /v1/groups/{group_id}/members/sync`](/api/resources/groups/subresources/members/subresources/sync/methods/submit/index.md) — Submit a member sync - [`GET /v1/groups/{group_id}/members/sync/{request_id}`](/api/resources/groups/subresources/members/subresources/sync/methods/retrieve/index.md) — Retrieve a sync request’s status and results