Users are the people in an organization: the practitioners who offer appointments, the staff who help them, and the patients who book. These endpoints create and maintain them, and control whether a patient can sign in.
Roles
role |
Who | Can sign in | Takes a seat |
|---|---|---|---|
doctor |
A practitioner who offers appointments. | Always. | Yes. |
medical_assistant |
Staff who support practitioners. | Always. | No. |
admin |
An administrator of the organization. | Always. | No. |
patient |
A person who books appointments. | Only with login_enabled: true. |
No. |
The role is set when the user is created and cannot be changed afterwards (422, code immutable). Other roles in MeetOne, such as agents and super admins, are not part of the API, and neither are the service accounts behind API tokens: they never appear in lists.
The user object
| Field | Type | Description |
|---|---|---|
id |
string (uuid) | MeetOne's ID of the user. |
external_id |
string, nullable | Your key for the user. Unique within the organization. See Identifiers. |
email |
string | The email address, stored in lower case. |
role |
string (enum) | doctor, medical_assistant, patient or admin. |
login_enabled |
boolean | Whether the user can sign in. See Login control. |
organization_owner |
boolean | Whether the user owns the organization. Only practitioners and admins can. |
locale |
string, nullable | The user's language, e.g. de or en. |
organization |
object, nullable | The organization, with id and external_id. |
profile |
object | The user's profile. |
deleted_at |
string (date-time), nullable | Set when the user has been deactivated. |
created_at, updated_at |
string (date-time) | Timestamps. |
The profile
Every role has one profile object with the same field names. Not every role has every field: a field the role does not have is null when you read it and returns 422 (code unsupported_for_role) when you send it. A field that is not part of the profile at all returns 422 (code unknown_attribute).
| Field | Type | doctor |
medical_assistant |
patient |
admin |
|---|---|---|---|---|---|
first_name, last_name |
string | Required | Required | Required | Required |
name_title |
string | Yes | Yes | Yes | No |
gender |
male or female |
Yes | Yes | Yes | No |
date_of_birth |
date (YYYY-MM-DD) |
Yes | Yes | Yes | No |
mobile_number, land_line_number |
string | Yes | Yes | Yes | No |
address_street, address_street_number, address_zipcode, address_city, address_country |
string | Yes | No | Yes | No |
insurance_company, insurance_number |
string | No | No | Yes | No |
insurance_type |
gkv, pkv, self_pay or bg |
No | No | Yes | No |
List and show users
GET /api/v1/users and GET /api/v1/users/{id}
| Query parameter | Type | Description |
|---|---|---|
role |
string | Only users with this role. An unknown role returns 422. |
email |
string | Only the user with this email address. |
login_enabled |
boolean | Only users who can (true) or cannot (false) sign in. |
external_id |
string | Only the user with this external ID. |
organization_id, organization_external_id |
string | Instance tokens: only this organization's users. |
updated_since |
string (date-time) | Only users changed at or after this time. See Errors & pagination. |
page, per_page |
integer | Pagination. |
curl "https://acme.meetone.io/api/v1/users?role=doctor&per_page=100" \
-H "Authorization: Bearer $TOKEN"
Deactivated users stay in the list and keep answering GET with deleted_at set, so that a sync learns about them. Filter on deleted_at in your own code if you only want active users.
Create a user
POST /api/v1/users
Instance tokens name the organization with organization_id or organization_external_id; organization tokens create in their own organization.
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Unique across the installation. |
role |
string (enum) | Yes | See Roles. |
profile |
object | Yes | At least first_name and last_name. See The profile. |
external_id |
string | No | Your key for the user. |
login_enabled |
boolean | No | Patients only. See Login control. |
send_invitation |
boolean | No | Send the invitation email with the sign-in link. Default false. |
organization_owner |
boolean | No | Make the user an owner of the organization. Practitioners and admins only; for others it returns 422 (not_allowed_for_role). |
locale |
string | No | The user's language, e.g. de. |
curl -X POST "https://acme.meetone.io/api/v1/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "mary.major@example.com",
"role": "patient",
"external_id": "pat-1001",
"locale": "de",
"profile": {
"first_name": "Mary",
"last_name": "Major",
"date_of_birth": "1990-04-02",
"address_city": "Berlin",
"insurance_type": "pkv"
}
}'
A successful request returns 201 Created:
{
"data": {
"id": "e41c8a0f-6b9d-4d27-a3f5-90c2b7d1e8a4",
"external_id": "pat-1001",
"email": "mary.major@example.com",
"role": "patient",
"login_enabled": false,
"organization_owner": false,
"locale": "de",
"organization": { "id": "3f6c1a52-8d4e-4b7a-9e21-5c0d7a4b1e90", "external_id": "crm-4711" },
"profile": {
"name_title": null,
"first_name": "Mary",
"last_name": "Major",
"gender": null,
"date_of_birth": "1990-04-02",
"mobile_number": null,
"land_line_number": null,
"address_street": null,
"address_street_number": null,
"address_zipcode": null,
"address_city": "Berlin",
"address_country": null,
"insurance_company": null,
"insurance_number": null,
"insurance_type": "pkv"
},
"deleted_at": null,
"created_at": "2026-10-08T10:15:00+02:00",
"updated_at": "2026-10-08T10:15:00+02:00"
}
}
Accounts have no password. A user who can sign in does so with a link sent to their email address. Patients get their message thread where messaging is enabled for the organization.
Email uniqueness
An email address is unique across the whole installation, not only within one organization. If it is already in use, the response is 422 with the code taken and the attribute email, and nothing about the existing account:
{
"errors": [
{ "code": "taken", "attribute": "email", "message": "Email has already been taken" }
]
}
Seats
Practitioners (doctor) take a seat of the organization. Once all seats of a paid plan are in use, creating another practitioner returns 422 with the code seats_limit_reached and the attribute organization. Organizations in a trial can add practitioners without a limit. Medical assistants, admins and patients never take a seat.
Login control
login_enabled decides whether a user can sign in.
| Role | login_enabled |
|---|---|
patient |
Optional. Defaults to false: a patient created through the API cannot sign in. Set true to allow it. |
doctor, medical_assistant, admin |
Always true. Sending false returns 422 with the code required_for_role. |
A patient without a login is a normal patient in every other respect. They appear in patient lists, can be booked, and still receive the emails about their appointments, including the link to join the video call. They just cannot sign in.
To enable the login later, send PATCH with login_enabled: true. To disable it, send login_enabled: false: the user is signed out everywhere at once and can no longer sign in, but still receives appointment emails.
Invitations
With send_invitation: true, a user who can sign in receives the invitation email with a sign-in link. It works on POST (for a patient together with login_enabled: true) and on PATCH, for example to switch a patient's login on and invite them in one request. A PATCH with send_invitation: true also sends the invitation again to a user whose login was already enabled:
curl -X PATCH "https://acme.meetone.io/api/v1/users/e41c8a0f-6b9d-4d27-a3f5-90c2b7d1e8a4" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "login_enabled": true, "send_invitation": true }'
send_invitation is not stored and is not part of the user object. It has no effect for a user who cannot sign in: no email is sent and no error is returned.
Update a user
PATCH /api/v1/users/{id}
Send only the fields you want to change. profile fields are merged into the existing profile. The role cannot change. When a patient's email changes, the appointments of that organization follow the new address (appointments the same address has in other organizations keep theirs), and the previous address receives an email-change notification. A deactivated user cannot be changed and returns 404.
curl -X PATCH "https://acme.meetone.io/api/v1/users/e41c8a0f-6b9d-4d27-a3f5-90c2b7d1e8a4" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "profile": { "mobile_number": "+49 30 1234567" } }'
The response is 200 OK with the updated user.
Deactivate a user
DELETE /api/v1/users/{id}
Deactivates the account: the user can no longer sign in and disappears from the app's lists. The response is 204 No Content. The data of the account is erased after the retention period; for patients that is the statutory retention of clinical documentation.
A user with confirmed appointments still ahead, as practitioner or as patient, returns 409 Conflict with the code future_appointments. Cancel those appointments first (see Appointments endpoints); the API does not decide for you whether the patient hears about it.
{
"errors": [
{ "code": "future_appointments", "attribute": null, "message": "Confirmed appointments are still ahead. Cancel them first." }
]
}
A deactivated user stays readable with deleted_at set, but cannot be changed (404) or used for new bookings and availabilities.
Related
- Scheduling API overview
- Identifiers
- Free slots and booking, where a patient can be created inline
- Errors & pagination
Questions? Contact us at support@meetone.io.