Skip to content

Groups and multi-site

Many Shepherd customers run more than one location as a clinic group. If you only ever target one clinic you can skim this, but any integration that a multi-location operator might enable should understand how groups change what your queries return. A common surprise is duplicate or “missing” records that are really just shared across a group.

Every request targets exactly one clinic through the X-Clinic-Id header (see Authentication). Clinics run by the same operator roll up under a single group, identified by a group id. Clinic records returned by the API carry the id of the group they belong to; a standalone clinic has no group.

So there are two identifiers in play:

  • Clinic id : the tenancy boundary you pass in X-Clinic-Id. One request, one clinic.
  • Group id : the operator the clinic rolls up to. You don’t send it as a header; you read it off clinic and group records.

Check this even for accounts you assume are single-location: an operator can add locations later, and the answer changes how you read results. Call the groups endpoint with the clinic you’re working in; it returns the group that clinic belongs to, including whether the operator is multi-site and what they share.

Terminal window
curl -X POST https://open-api.shepherd.vet/pav2/open-api-groups \
-H "Content-Type: application/json" \
-H "X-Integration-Public-Key: $SHEPHERD_PUBLIC_KEY" \
-H "X-Integration-Private-Key: $SHEPHERD_PRIVATE_KEY" \
-H "X-Clinic-Id: $SHEPHERD_CLINIC_ID" \
-d '{}'

The response tells you two things that shape the rest of your integration:

  • isMultiSite : whether this operator runs as a multi-location group.
  • A set of sharing flags : whether clients and patients, products, pricing, and other data are shared across the group’s clinics (for example shareClientsAndPatients, shareProducts, shareProductPricing). The live Swagger reference has the full field list and types.

When an operator shares a dataset across the group, a record’s home clinic can differ from the clinic you queried. With client and patient sharing enabled, for example, a client created at one location is visible from the others; each record still carries the id of the clinic it belongs to, so you can tell where it lives.

Two practical consequences:

  • The same record can appear under more than one clinic. If you pull a list for each clinic in a group and concatenate, you’ll get duplicates for shared data. De-duplicate on the record id, and use each record’s home clinic id when you need to attribute it.
  • Cross-clinic filtering only works where sharing is on. Endpoints accept a clinicIds filter to scope a query to specific clinics in the group, but it’s honored only when the relevant data is shared; otherwise a request is limited to the single clinic in X-Clinic-Id. To cover an entire group, either iterate X-Clinic-Id across its clinics or, where sharing is enabled, pass clinicIds.

Set the expectation up front: there is no single call that returns every location’s data at once. Each request is scoped to the one clinic in X-Clinic-Id. To cover a group you have two patterns:

  1. Iterate X-Clinic-Id. List the clinics in the group, then make the same call once per clinic, changing the header each time. This always works, regardless of sharing settings, and is the right default.

  2. Filter with clinicIds where the data is shared. On endpoints that support it, a clinicIds body field scopes one query to several clinics at once:

    { "page": 1, "rpp": 100, "clinicIds": "a1b2...,c3d4..." }

    This is honored only when the relevant dataset is shared across the group (the share* flags above). If it isn’t, the filter is ignored and you get just the clinic in X-Clinic-Id, so don’t rely on it without checking the flags first.

Whichever pattern you use, remember that shared records can surface under more than one clinic; de-duplicate on the record id and attribute each record by its home clinic id.

A robust multi-site integration usually:

  1. Resolves the group for the clinic it’s enabling, and records isMultiSite and the sharing flags.
  2. Lists the clinics in that group (clinic records carry the group id) so it knows the full set of locations.
  3. Decides, per dataset, whether to iterate per clinic or rely on group sharing, based on the flags.
  4. De-duplicates shared records by id and attributes them by home clinic id.

If isMultiSite is false, treat the account as a single clinic: no sharing, one location, nothing extra to do.