Practitioner sign-in
A practitioner has one account across Ovok, with one email and one password, and a membership in each project they belong to. Sign-in therefore has a step that patients do not have: the practitioner signs in first, then chooses a tenant.
| Step | Method | Path | Tenant needed? |
|---|---|---|---|
| 1 | POST | /auth/tenant/Practitioner/login/start | No |
| 2, only with MFA | POST | /auth/tenant/Practitioner/login/mfa | No |
| 3 | POST | /auth/tenant/Practitioner/login/token | Yes: tenantCode |
None of the three needs a bearer token.
Before you start
| You need | Where it comes from |
|---|---|
| A practitioner account with a membership in the project | Practitioner registration or a practitioner invitation. |
| Practitioner sign-in switched on | PRACTITIONER_LOGIN_ENABLED must not be false for the chosen project. It is on when unset. |
Create the code verifier and challenge exactly as in patient sign-in, step 0.
Step 1: Start sign-in
Checks the email and password. No tenant yet.
curl --request POST \
--url 'https://api.sandbox.ovok.com/auth/tenant/Practitioner/login/start' \
--header 'Content-Type: application/json' \
--data '{
"email": "practitioner@example.com",
"password": "your-password",
"codeChallenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
}'
| Body field | Description |
|---|---|
email | The practitioner's email. Ovok lowercases it. |
password | The practitioner's password. |
codeChallenge | The hash of your code verifier. |
Without MFA, the answer carries the session code and every tenant the practitioner can enter:
| Response field | Example | Meaning |
|---|---|---|
nextStep | token or mfa | Tells the app whether to continue to token exchange or verify the one-time code. |
sessionCode | Temporary code | Present when nextStep is token; send it with the verifier in step 3. |
loginId | UUID | Present when nextStep is mfa; send it with the one-time code in step 2. |
profiles[] | One entry per tenant | Memberships the practitioner can choose from. |
With MFA enrolled, nextStep is mfa and the response includes loginId instead of sessionCode and profiles.
Building the tenant chooser
profiles field | Meaning |
|---|---|
tenantCode | Send this in step 3. |
project | id, resourceType and name of the project, for display. |
profile | The practitioner's profile in that project. |
isMainProject | true when the project has no parent. null only when the hierarchy could not be read. |
parentProjectId | The parent project, so you can group sub-tenants beneath it. Set only when the practitioner is also a member of that parent, otherwise null. |
Use isMainProject, not parentProjectId, to decide whether a project is a main account. If isMainProject is null, show a flat list.
| Status | Message | Cause |
|---|---|---|
400 | Username or password is incorrect. | A wrong email, an unknown account, an account without a password, or a wrong password. All give the same answer on purpose. |
422 | A list of path and message | A body field is missing or invalid. |
429 | More than 10 requests in a minute. |
Step 2: Verify the one-time code (only with MFA)
curl --request POST \
--url 'https://api.sandbox.ovok.com/auth/tenant/Practitioner/login/mfa' \
--header 'Content-Type: application/json' \
--data '{"loginId":"8c1e5a52-6f0b-4d9e-9a41-0b2f7c3d9e10","mfaToken":"123456"}'
| Body field | Description |
|---|---|
loginId | The loginId from step 1. A UUID. |
mfaToken | The current one-time code, at least 6 characters. |
On success the answer is { "nextStep": "token", "sessionCode": "…", "profiles": [ … ] }, with the same profiles list as step 1.
| Status | Message | Cause |
|---|---|---|
400 | Invalid MFA token or user. | The code is wrong or expired, or the loginId is unknown. |
422 | A list of path and message | loginId is not a UUID, or mfaToken is shorter than 6 characters. |
429 | More than 5 requests in a minute. |
Step 3: Choose a tenant and get tokens
curl --request POST \
--url 'https://api.sandbox.ovok.com/auth/tenant/Practitioner/login/token' \
--header 'Content-Type: application/json' \
--data '{
"sessionCode": "<sessionCode from step 1 or 2>",
"codeVerifier": "<your code verifier>",
"tenantCode": "big-health-company"
}'
Ovok binds the session to the practitioner's membership in that tenant before it issues tokens, so the tokens are scoped to that one project.
| Response field | Example | Meaning |
|---|---|---|
accessToken | Token string | Bearer token for API calls. |
refreshToken | Token string | Token used to renew the session. |
expiresIn | 3600 | Access-token lifetime in seconds. |
project.reference, project.display | Project/<id>, Big Health Company | Project the tokens are for. |
profile.reference, profile.display | Practitioner/<id>, Example Practitioner | Practitioner profile that signed in. |
| Status | Message | Cause |
|---|---|---|
404 | Tenant not found. | The tenant code is wrong. |
403 | Login is not enabled for this project. | PRACTITIONER_LOGIN_ENABLED is false for that project. Checked before the session code is used. |
404 | Login not found. | The session code is unknown. |
404 | Account not found. | The practitioner has no membership in the chosen tenant. |
400 | Code verification failed. | The codeVerifier does not match the challenge. |
429 | More than 30 requests in a minute. |
To switch tenant later, sign in again and choose another tenantCode.
Gotchas
- The setting is checked at step 3, not step 1. A practitioner can pass the password and MFA steps and still be refused with
403at the end. Your sign-in screen must handle a403at step 3, and theprofileslist can include tenants where sign-in is off. - Project admins are not exempt. If you turn
PRACTITIONER_LOGIN_ENABLEDoff and your own session ends, you cannot sign back in to turn it on again. Keep a signed-in admin session open until you have confirmed the change. - Turning the switch off does not end sessions. Signed-in practitioners keep working until their token expires, up to an hour.
- One account, many projects. A practitioner's email and password are shared across every project they belong to. Registration is refused for an email that already has an account; add that person to your project with an invitation instead.
- A failed read of the project looks like "off". If the project cannot be read for a moment, callers see the same
403. - Every error takes at least a second.