# Identifiers: id and external_id

> How records are identified in the Scheduling API: the UUID id, your own external_id, references by id or external ID, uniqueness rules and filtering lists by external_id.

Source: https://help.meetone.io/developers/scheduling-api/identifiers
Last updated: 2026-10-23

---

Every record in the Scheduling API can be addressed by an ID MeetOne assigns and by a key from your own system. This article explains both, how they are used in requests, and where they must be unique.

## Two identifiers per record

| Identifier | Set by | Format | Description |
|---|---|---|---|
| `id` | MeetOne | UUID, e.g. `3f6c1a52-8d4e-4b7a-9e21-5c0d7a4b1e90` | Assigned when the record is created and never changes. It is the `{id}` in every URL path. |
| `external_id` | You | String, at most 255 characters, optional | Your own key for the record, for example the primary key in your CRM. Writable when you create or update the record; `null` if you never set one. |

Organizations, users, availability types, appointment resources, availabilities and appointments all have both. The API never exposes MeetOne's internal numeric IDs.

```json
{
  "data": {
    "id": "3f6c1a52-8d4e-4b7a-9e21-5c0d7a4b1e90",
    "external_id": "crm-4711",
    "name": "Praxis Dr. Muster"
  }
}
```

> **Note**
>
> Leading and trailing whitespace in an `external_id` is removed, and a blank value is stored as no external ID at all. A malformed `id` in a URL, one that is not a UUID, simply answers `404`.

## References between records

Many requests point at another record: a booking names a patient, an availability names a practitioner and a type. Each reference can be written in two ways, as `<name>_id` or as `<name>_external_id`:

| Reference | By MeetOne ID | By your ID |
|---|---|---|
| Organization (instance tokens) | `organization_id` | `organization_external_id` |
| Practitioner | `doctor_id` | `doctor_external_id` |
| Patient | `patient_id` | `patient_external_id` |
| Availability type | `availability_type_id` | `availability_type_external_id` |
| Appointment resource | `appointment_resource_id` | `appointment_resource_external_id` |

The same names work as query parameters when you filter or look things up, and as body fields when you create or update a record. This booking uses external IDs only, so your system never has to store a MeetOne ID:

```bash
curl -X POST "https://acme.meetone.io/api/v1/appointments" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "availability_type_external_id": "first-visit",
    "doctor_external_id": "staff-12",
    "patient_external_id": "pat-1001",
    "starts_at": "2026-10-12T09:00:00+02:00"
  }'
```

Rules for references:

- **One form per reference.** Sending both `doctor_id` and `doctor_external_id` returns `422` with the code `ambiguous_reference`.
- **Unknown or foreign records return 404.** A reference to a record that does not exist, or that belongs to another organization, returns `404 Not Found` with the code `not_found` and the `attribute` that was looked up, for example `doctor_external_id`. The API does not tell you whether a record exists in an organization you cannot reach.
- **References stay inside one organization.** They resolve in the organization of the request. With an organization token that is its own organization. With an instance token it is the organization you name with `organization_id` or `organization_external_id`.
- **External IDs need an organization.** Because external IDs are only unique within an organization, an instance token cannot resolve a `*_external_id` reference without naming the organization. The request returns `422` with the code `organization_required`.

## Uniqueness

An `external_id` identifies one record of one resource type:

| Resource | Unique within |
|---|---|
| Users, availability types, appointment resources, availabilities, appointments | The organization. |
| Organizations | The installation (all organizations of the tenant). |

Two different resource types may use the same value: a user and an appointment can both have `external_id` `1001`. Within a type, a value that is already taken returns `422` with the code `taken` and `attribute` `external_id`:

```json
{
  "errors": [
    { "code": "taken", "attribute": "external_id", "message": "External ID has already been taken" }
  ]
}
```

Two concurrent requests that try to write the same `external_id` are also answered with `422 taken`; one of them wins.

## Filtering lists by external_id

Every list endpoint accepts `external_id` as an exact-match filter, except waitlist entries, which have no `external_id`. It is the quickest way to find out whether your record already exists in MeetOne:

```bash
curl "https://acme.meetone.io/api/v1/users?external_id=pat-1001" \
  -H "Authorization: Bearer $TOKEN"
```

```json
{
  "data": [
    {
      "id": "e41c8a0f-6b9d-4d27-a3f5-90c2b7d1e8a4",
      "external_id": "pat-1001",
      "email": "mary.major@example.com",
      "role": "patient"
    }
  ]
}
```

(Abbreviated; see [Users endpoints](https://help.meetone.io/developers/scheduling-api/users-endpoints) for the complete object.) The list is empty when nothing matches. An instance token that does not name an organization searches across all of them, so it can receive one match per organization; add `organization_id` or `organization_external_id` to narrow it down.

> **Note**
>
> Choose external IDs that never change and are never reused for another record. If your system recycles keys, put a prefix or the record type into the value, or keep using MeetOne's `id`.

## Related

- [Scheduling API overview](https://help.meetone.io/developers/scheduling-api/overview)
- [Errors & pagination](https://help.meetone.io/developers/getting-started/errors-and-pagination)
- [Users endpoints](https://help.meetone.io/developers/scheduling-api/users-endpoints)

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