> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.vitablehealth.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.vitablehealth.com/_mcp/server.

# Group Member Sync

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

## Submit a Member Sync

Send the complete current roster as an array of `members`:

```bash
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:

```json
{
  "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). |
| `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.                                                                                                 |

> **Warning**
>
> 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/groups/members/sync/submit) 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).
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.

> **Warning**
>
> 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:

```bash
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:

```json
{
  "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": [] }`.

> **Tip**
>
> 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/groups/members/sync/submit) — Submit a member sync
* [`GET /v1/groups/{group_id}/members/sync/{request_id}`](/api/groups/members/sync/get) — Retrieve a sync request's status and results