Users — lifecycle mutations, read model, and includeUnvalidated
Summary
This release adds practice user create, update, and delete mutations, extends the User read model so mutations round-trip cleanly, and lets user / users resolve invited (unvalidated) users when you opt in. Changes mirror Heydoc Settings → Users where noted. All query changes are additive — omit new fields and options to keep previous behaviour.
Mutations
| Mutation | Purpose |
|---|---|
createUser | Invite a new user (unvalidated until signup or SSO login) |
updateUser | Patch profile, settings, integrations, and optionally replace access groups |
deleteUser | Soft-delete a practice user |
Authenticate with signIn and pass the token in the x-token header.
Permissions
| Action | Required permission |
|---|---|
createUser, updateUser, deleteUser | settingsEditUsers |
accessGroupIds (any array, including []) or enableAllPublicAccessGroup on create or update | settingsEditAccessGroups |
Each mutation returns UserResponsePayload (data: User, error: String). Validation failures set error and leave data null, consistent with mutations such as createContact.
Behaviour notes
createUsersends an invitation email; the user stays unvalidated until signup or SSO.updateUsercannot change the email of a validated user.deleteUsersoft-deletes (deleted: true), releases the email, clears access groups, and removes SSO identity when enabled. You cannot delete your own account.- Nested
settings,notifications, andserviceson update are patches (only sent subsections change). - Signature/stamp uploads, mobile number updates, and resend-invitation are not on the Public API.
Queries — read model and unvalidated users
New fields on User
Request these when verifying writes or syncing configuration from Semble:
| Field | Notes |
|---|---|
role | Practice role string |
mobileNumber, locum fields, positionTitle, position, automaticDocumentSigning | Profile and preferences |
integrations | healthcode, oneWelbeck; passwords never returned — use hasPassword |
settings, notifications | Public API subset only |
siretNumber, assuranceMaladieNumber, annuaireSanteProfile | French practices, clinicians |
accessGroups and servicesProvided were already on User. Mutation inputs use accessGroupIds (plus enableAllPublicAccessGroup for the Public group) and services. Read field medicalSpecialties maps from write input medicalSpecialty (singular).
includeUnvalidated
Invited users who have not completed signup are unvalidated. By default, user and users return validated users only.
user(id, includeUnvalidated: true)— fetch an invited user by id before signup completes.users(options: { includeUnvalidated: true })— include invited users in the paginated list.
Use after createUser when you need to read the user back before they accept the invitation.
Input guide — common pitfalls
role
- Accepts Semble static role ids as strings: typically
user,manager,practitioner(medical assistant, manager, clinician templates). - This is the permission template, not a custom role document id from
practice.roles. Use the same values as in Heydoc Settings → Users. - Required on
createUser.
accessGroupIds and enableAllPublicAccessGroup
Access group membership is split into two independent inputs, mirroring the Heydoc users form (a Public toggle plus a group selection):
accessGroupIds— the user's non-public groups. Full replace, not a partial patch.- Create: omit or
null→ no groups assigned (unlike Heydoc UI, which defaults new users to the Public group).[]→ explicitly no non-public groups. - Update: omit or
null→ non-public membership unchanged.[]→ remove from all non-public groups. - Valid ids come from
practice { accessGroups { id } }, excluding the Public group. - The Public group id equals the practice id (
practice { id }) and is no longer accepted here — sending it returns a validationerror. UseenableAllPublicAccessGroupinstead. - Invalid or foreign ids are ignored at persist time; prefer validating against the practice query before calling the mutation.
- Create: omit or
enableAllPublicAccessGroup— boolean controlling the Public ("see all patients and contacts") group.- Create: omit or
null→ not in the Public group.true→ added.false→ not added. - Update: omit or
null→ Public membership unchanged.true→ add to Public.false→ remove from Public.
- Create: omit or
- The two inputs are independent: sending only one leaves the other dimension as-is. For example, on update
accessGroupIds: []clears custom groups but keeps Public membership unless you also sendenableAllPublicAccessGroup: false.
French practices — frenchCompliance and RPPS
- Only for French (
FR) practices. Rejected on UK and other countries. siretNumber,assuranceMaladieNumber, and Annuaire Santé fields requireisDoctor: true.- Create (clinicians): provide
frenchCompliance.annuaireSantewithrpps(11 digits). The server resolves the directory profile; you do not send the full profile yourself. - Ambiguous RPPS: when Annuaire Santé returns multiple practitioner roles for one RPPS, also send
annuaireSanteIds.practitionerIdandroleIdentifieron create. - Update: use
annuaireSanteLookuponly when the user has no profile yet; useannuaireSantefor mutable fields when a profile exists. RPPS, idnps, and annuaireSanteIds cannot be changed after link — create a new profile flow via lookup only if none exists. - Do not send both
annuaireSanteLookupandannuaireSantein the same update.
Other nested inputs
integrations.healthcode.password— write-only; never returned on read.gender/pronouns— use GraphQL enums; stored values differ (e.g.sheHer→She/her,notKnown→not known).
Migration notes
- No breaking changes to existing
user/usersqueries. - After
createUserorupdateUser, request the fields you care about in the mutation selection set or follow up withuser(id, includeUnvalidated: true). - For deleted users and listing, see Users deleted & includeDeleted.
See also
- API reference:
User - API reference:
user - API reference:
users - Release note: Users deleted & includeDeleted