Skip to main content

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

MutationPurpose
createUserInvite a new user (unvalidated until signup or SSO login)
updateUserPatch profile, settings, integrations, and optionally replace access groups
deleteUserSoft-delete a practice user

Authenticate with signIn and pass the token in the x-token header.

Permissions

ActionRequired permission
createUser, updateUser, deleteUsersettingsEditUsers
accessGroupIds (any array, including []) or enableAllPublicAccessGroup on create or updatesettingsEditAccessGroups

Each mutation returns UserResponsePayload (data: User, error: String). Validation failures set error and leave data null, consistent with mutations such as createContact.

Behaviour notes

  • createUser sends an invitation email; the user stays unvalidated until signup or SSO.
  • updateUser cannot change the email of a validated user.
  • deleteUser soft-deletes (deleted: true), releases the email, clears access groups, and removes SSO identity when enabled. You cannot delete your own account.
  • Nested settings, notifications, and services on 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:

FieldNotes
rolePractice role string
mobileNumber, locum fields, positionTitle, position, automaticDocumentSigningProfile and preferences
integrationshealthcode, oneWelbeck; passwords never returned — use hasPassword
settings, notificationsPublic API subset only
siretNumber, assuranceMaladieNumber, annuaireSanteProfileFrench 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 validation error. Use enableAllPublicAccessGroup instead.
    • Invalid or foreign ids are ignored at persist time; prefer validating against the practice query before calling the mutation.
  • 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.
  • 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 send enableAllPublicAccessGroup: false.

French practices — frenchCompliance and RPPS

  • Only for French (FR) practices. Rejected on UK and other countries.
  • siretNumber, assuranceMaladieNumber, and Annuaire Santé fields require isDoctor: true.
  • Create (clinicians): provide frenchCompliance.annuaireSante with rpps (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.practitionerId and roleIdentifier on create.
  • Update: use annuaireSanteLookup only when the user has no profile yet; use annuaireSante for 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 annuaireSanteLookup and annuaireSante in 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. sheHerShe/her, notKnownnot known).

Migration notes

  • No breaking changes to existing user / users queries.
  • After createUser or updateUser, request the fields you care about in the mutation selection set or follow up with user(id, includeUnvalidated: true).
  • For deleted users and listing, see Users deleted & includeDeleted.

See also