Invite a team member
Adds a practitioner to your project and, if they have no account yet, emails them a link to set a password. Use it to bring in a colleague. It does not share any patient's record.
| Method | Path | Who |
|---|---|---|
POST | /v1/projects/me/members | A project admin |
What the project needs
| Requirement | Detail |
| --- | --- | --- |
| Practitioner invitations on | PRACTITIONER_INVITATION_ENABLED must not be false. It is on when unset. |
| A practitioner app URL | The link needs one: PRACTITIONER_APP_URL on the project, then the parent, then older settings, then the request's Origin if it is on the allowed list, then the platform default. If nothing resolves the call answers 409 and creates nothing. |
| A default practitioner AccessPolicy, only if you send access | DEFAULT_PRACTITIONER_ACCESS_POLICY. |
| The INVITE_TO_PROJECT email template | Mapped to your template, with a provider that is ready. |
Request
curl --request POST \
--url 'https://api.sandbox.ovok.com/v1/projects/me/members' \
--header "Authorization: Bearer ${OVOK_TOKEN}" \
--header 'Content-Type: application/json' \
--data '{
"email": "alex@example.com",
"firstName": "Alex",
"lastName": "Lee",
"admin": false
}'
| Body field | Required | Description |
|---|---|---|
email | Yes | A valid address. Ovok trims and lowercases it. |
firstName | Yes, but may be null | Given name. null becomes New. |
lastName | Yes, but may be null | Family name. null becomes Member. |
admin | Yes, but may be null | true makes the member a project admin. null becomes false. |
access | No | One to 100 entries that limit what the member can do. See limit what the member can do. |
The three "may be
null" fields are still required keys. Leaving them out answers422withRequiredfor each. Sendnullto take the default.
Limit what the member can do
Without access, the new member has no AccessPolicy, and can reach the whole project. To limit them, send access:
{
"email": "alex@example.com",
"firstName": "Alex",
"lastName": "Lee",
"admin": false,
"access": [
{ "policy": { "reference": "AccessPolicy/care-team-policy" } }
]
}
With access, the member's base AccessPolicy is the project's default practitioner AccessPolicy, and each entry adds to it. Every entry must name an AccessPolicy of this project, written AccessPolicy/<id>. An entry can also carry parameter values (name plus either valueReference or valueString) for policies that take parameters.
Response
The answer is 200 with the new member:
| Field | Example | Meaning |
|---|---|---|
id | … | Membership id. |
email | alex@example.com | Normalized email address. |
displayName | Alex Lee | Display name for the new member. |
firstName | Alex | Given name. |
lastName | Lee | Family name. |
admin | false | Whether the member is a project admin. |
profileType | Practitioner | Profile resource type. |
profileId | … | Id of the practitioner's profile. |
pending | true | Remains true until the member sets a password through the emailed link. |
invitedAt | 2026-10-06T08:15:00.000Z | Invitation timestamp. |
emailVerified | false | Becomes true after an emailed set-password link is used; null when it could not be read. |
existingAccount | false | True when the address could already sign in, so no email was sent. |
What can go wrong
Checks run in this order.
| Status | Message or error | Cause |
|---|---|---|
401 | The token is missing or invalid. | |
403 | Forbidden resource | The caller is not a project admin. |
422 | A list of path and message | A field is missing or invalid, including the three required keys above. |
403 | Practitioner invitations are disabled for this project. | PRACTITIONER_INVITATION_ENABLED is false. |
404 | The project in the caller's token cannot be read. | |
409 | A member with this email already exists on this project. | The address is already a practitioner member here. |
409 | error invitation_not_configured | access was sent, but there is no valid default practitioner AccessPolicy. |
400 | Every access entry must name an AccessPolicy of this project. | An access entry names another project's policy, or one that does not exist. |
409 | code app_url_not_configured | No practitioner app URL resolves. Nothing was created. |
409 | Another write holds the account. Retry. | |
500 | Failed to send invite email. Please add the user manually if the issue persists. | The email could not be sent. The new account was removed. |
Gotchas
- No
accessmeans whole-project access. Treataccessas required unless you mean it. The other two invitation routes refuse a member who has no AccessPolicy, because a share would narrow their access. - An existing account gets no email. An address that already has a password, or belongs to another project, receives the membership silently, and
existingAccountistrue. Tell them yourself. - The placeholders are real.
nullnames becomeNewandMember, and those appear in the email greeting. Send real names. - Only an existing member of this project is a conflict. The same address in another project is fine.
- A browser call can borrow its own
Origin. When no app URL is configured, a call made from a browser app on the allowed list builds the link on that origin; a call from your server does not. SetPRACTITIONER_APP_URLso the link never depends on the caller. admin: trueis a full admin. Admins can invite and remove members and change settings.- Invitation is undone with the email. A failed send leaves no account behind, so it is safe to retry.
Manage members
The same path family lists and changes members. All of these need a project admin except the list, which any practitioner may read.
| Method | Path | What it does |
|---|---|---|
GET | /v1/projects/me/members | List practitioner members. Query accessPolicyId, profileId, userId, page (from 0) and count (1 to 100, default 20). |
GET | /v1/projects/me/members/:membershipId | Read one member. 404 for an id in another project. |
PATCH | /v1/projects/me/members/:membershipId | Change admin, or replace access. 409 if it would demote the last admin or the member changed since you read it. |
DELETE | /v1/projects/me/members/:membershipId | Remove a member. 409 for the last admin. |
These routes handle practitioner memberships only; another type answers 400.