# Users endpoints

> Reference for creating, reading, updating and deactivating practitioners, medical assistants, admins and patients, including roles, profiles, login control, invitations and seats.

Source: https://help.meetone.io/developers/scheduling-api/users-endpoints
Last updated: 2026-10-08

---

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](https://help.meetone.io/developers/scheduling-api/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](https://help.meetone.io/developers/scheduling-api/users-endpoints#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](https://help.meetone.io/developers/scheduling-api/users-endpoints#the-profile). |
| `deleted_at` | string (date-time), nullable | Set when the user has been [deactivated](https://help.meetone.io/developers/scheduling-api/users-endpoints#deactivate-a-user). |
| `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](https://help.meetone.io/developers/getting-started/errors-and-pagination). |
| `page`, `per_page` | integer | Pagination. |

```bash
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](https://help.meetone.io/developers/scheduling-api/users-endpoints#roles). |
| `profile` | object | Yes | At least `first_name` and `last_name`. See [The profile](https://help.meetone.io/developers/scheduling-api/users-endpoints#the-profile). |
| `external_id` | string | No | Your key for the user. |
| `login_enabled` | boolean | No | Patients only. See [Login control](https://help.meetone.io/developers/scheduling-api/users-endpoints#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`. |

```bash
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`:

```json
{
  "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:

```json
{
  "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:

```bash
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`.

```bash
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](https://help.meetone.io/developers/scheduling-api/appointments-endpoints#cancel-an-appointment)); the API does not decide for you whether the patient hears about it.

```json
{
  "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](https://help.meetone.io/developers/scheduling-api/overview)
- [Identifiers](https://help.meetone.io/developers/scheduling-api/identifiers)
- [Free slots and booking](https://help.meetone.io/developers/scheduling-api/availability-slots-and-booking), where a patient can be created inline
- [Errors & pagination](https://help.meetone.io/developers/getting-started/errors-and-pagination)

Questions? Contact us at [support@meetone.io](mailto:support@meetone.io).
