# Waitlist entries endpoints

> Reference for reading waitlist entries: every field of the entry object, filters, syncing with updated_since and removing entries that were deleted in MeetOne.

Source: https://help.meetone.io/developers/waitlist-api/waitlist-entries-endpoints
Last updated: 2026-10-23

---

This article describes the two endpoints of the Waitlist API and the entry object they return. What the waitlist is, which token you need and how an entry moves from sign-up to booking is in the [Waitlist API overview](https://help.meetone.io/developers/waitlist-api/overview).

| Endpoint | Purpose |
|---|---|
| `GET /api/v1/waitlist_entries` | List and filter the entries; the interface for syncing. |
| `GET /api/v1/waitlist_entries/{id}` | Show one entry. |

Both endpoints need an Organization API Token, and read-only tokens may call them. An Instance API Token gets `403` with the code `organization_token_required`.

## The waitlist entry object

| Field | Type | Description |
|---|---|---|
| `id` | string (uuid) | MeetOne's ID of the entry. Waitlist entries have no `external_id`: nothing can write one, so store this `id` in your system. |
| `status` | string (enum) | `waiting`, `invited`, `booked`, `archived` or `withdrawn` (not used). See [Statuses](https://help.meetone.io/developers/waitlist-api/overview#statuses). |
| `first_name`, `last_name` | string | The person's name. |
| `email` | string | The address the person confirmed, in lower case. |
| `phone_number` | string, nullable | The person's phone number, as they entered it. |
| `date_of_birth` | string (date), nullable | `YYYY-MM-DD`. |
| `address_street`, `address_zipcode`, `address_place` | string, nullable | The person's address. |
| `gp_name` | string, nullable | The person's general practitioner. |
| `message` | string, nullable | What the person wrote in the form's message field. |
| `internal_note` | string, nullable | Your team's note on the entry. The person never sees it. |
| `locale` | string, nullable | The language the person signed up in, for example `de`. |
| `insurance_type` | string (enum), nullable | `gkv` (statutory health insurance), `pkv` (private health insurance) or `self_pay`. |
| `preferred_time` | string (enum), nullable | `morning`, `afternoon` or `all_day`. |
| `consultation_requested` | boolean | Whether the person asked for an initial consultation. |
| `submitted_at` | string (date-time) | When the person sent the form. The list is in this order. |
| `email_confirmed_at` | string (date-time), nullable | When the person confirmed their email address. |
| `consent_accepted_at` | string (date-time), nullable | When the person gave their consent on the form. |
| `invited_at` | string (date-time), nullable | When the current invitation was sent. `null` when there is none. |
| `last_reconfirmed_at` | string (date-time), nullable | When the person last answered the "still interested?" email. |
| `created_at`, `updated_at` | string (date-time) | Timestamps. `updated_at` drives [syncing](https://help.meetone.io/developers/waitlist-api/waitlist-entries-endpoints#keep-your-system-in-sync). |
| `doctor` | object, nullable | The practitioner the person chose in the form: `id`, `external_id`, `first_name`, `last_name`. `null` when they chose none. |
| `invitation` | object, nullable | The invitation to book. See [The invitation](https://help.meetone.io/developers/waitlist-api/waitlist-entries-endpoints#the-invitation). |
| `answers` | array | The answers to your organization's waitlist questions. See [Answers](https://help.meetone.io/developers/waitlist-api/waitlist-entries-endpoints#answers). |

Fields the person left empty are `null`. The links in the emails MeetOne sends to the person are never part of the object.

```json
{
  "data": {
    "id": "8a3f6d21-4c7e-4b9a-a1d2-6e5f0c9b7d34",
    "status": "invited",
    "first_name": "Mary",
    "last_name": "Major",
    "email": "mary.major@example.com",
    "phone_number": "+49 30 1234567",
    "date_of_birth": "1984-05-17",
    "address_street": "Hauptstraße 5",
    "address_zipcode": "10115",
    "address_place": "Berlin",
    "gp_name": null,
    "message": "Mornings before ten work best for me.",
    "internal_note": null,
    "locale": "de",
    "insurance_type": "gkv",
    "preferred_time": "morning",
    "consultation_requested": true,
    "submitted_at": "2026-09-14T08:12:44+02:00",
    "email_confirmed_at": "2026-09-14T08:15:02+02:00",
    "consent_accepted_at": "2026-09-14T08:12:44+02:00",
    "invited_at": "2026-10-20T11:30:00+02:00",
    "last_reconfirmed_at": null,
    "created_at": "2026-09-14T08:12:44+02:00",
    "updated_at": "2026-10-20T11:30:00+02:00",
    "doctor": {
      "id": "b7d2e9c4-1a3f-4c58-8e66-2f9a0d7c5b13",
      "external_id": "staff-12",
      "first_name": "Maria",
      "last_name": "Muster"
    },
    "invitation": {
      "doctor": {
        "id": "b7d2e9c4-1a3f-4c58-8e66-2f9a0d7c5b13",
        "external_id": "staff-12",
        "first_name": "Maria",
        "last_name": "Muster"
      },
      "availability_type": {
        "id": "5d9a2f77-c3e1-4b08-8a64-7e1f0c92d3b6",
        "external_id": "first-visit",
        "display_name": "First visit"
      },
      "appointment_id": null,
      "created_at": "2026-10-20T11:30:00+02:00"
    },
    "answers": [
      {
        "question": "Which weekdays suit you?",
        "question_type": "multiple_choice",
        "text": null,
        "number": null,
        "selected_options": [
          { "label": "Monday", "text_input": null },
          { "label": "Thursday", "text_input": null }
        ]
      },
      {
        "question": "How did you hear about us?",
        "question_type": "single_choice",
        "text": null,
        "number": null,
        "selected_options": [
          { "label": "Other", "text_input": "A friend" }
        ]
      }
    ]
  }
}
```

### The invitation

`invitation` is `null` until your team invites the person, and again after an invitation is withdrawn: when the entry is archived, or when the invitation can no longer be used and the entry returns to `waiting`. Inviting again replaces it. Once the person has booked, it stays as it was, with the appointment.

| Field | Type | Description |
|---|---|---|
| `doctor` | object, nullable | The practitioner the invitation is for: `id`, `external_id`, `first_name`, `last_name`. `null` when it is for your organization as a whole. |
| `availability_type` | object, nullable | The availability type the invitation is limited to: `id`, `external_id`, `display_name`. `null` when the person may choose. |
| `appointment_id` | string (uuid), nullable | The `id` of the appointment the person booked through the invitation. Read it with `GET /api/v1/appointments/{id}`; see [Appointments endpoints](https://help.meetone.io/developers/scheduling-api/appointments-endpoints). `null` until they book. |
| `created_at` | string (date-time) | When the invitation was sent. |

### Answers

Your organization can add its own questions to the waitlist form. `answers` holds the person's answers in the order of the questions, and is empty when there are none. A follow-up question that an option opens is an item of its own.

| Field | Type | Description |
|---|---|---|
| `question` | string | The question as your organization wrote it. |
| `question_type` | string (enum) | `free_text`, `dropdown`, `number`, `single_choice` or `multiple_choice`. |
| `text` | string, nullable | The answer to a `free_text` question. |
| `number` | number, nullable | The answer to a `number` question. |
| `selected_options` | array | The options the person picked in a `dropdown`, `single_choice` or `multiple_choice` question: `label`, and `text_input` when the option asks for text of its own, such as "Other". Empty for the other types. |

Answers are given in the person's words and are not translated.

## List waitlist entries

`GET /api/v1/waitlist_entries`

| Query parameter | Type | Description |
|---|---|---|
| `status` | string (enum) | Only entries in this status. Anything other than `waiting`, `invited`, `booked`, `archived` or `withdrawn` returns `422` with the code `inclusion`. |
| `doctor_id`, `doctor_external_id` | string | Only entries whose person chose this practitioner. A practitioner who does not exist, or belongs to another organization, returns `404`. |
| `updated_since` | string (date-time) | Only entries changed at or after this time. See [below](https://help.meetone.io/developers/waitlist-api/waitlist-entries-endpoints#keep-your-system-in-sync). |
| `page`, `per_page` | integer | Pagination. `per_page` defaults to 50, at most 100. With `updated_since`, `page` is an opaque cursor taken from the `next` link instead of a page number. |

Filters combine: all that you send must match. Without `updated_since`, the list is ordered by `submitted_at`, oldest first, which is the order of your waitlist. With `updated_since`, it is ordered by `updated_at`.

```bash
curl "https://acme.meetone.io/api/v1/waitlist_entries?status=waiting&doctor_external_id=staff-12&per_page=100" \
  -H "Authorization: Bearer $TOKEN"
```

Entries whose email address is not confirmed yet are never listed, and they cannot be asked for: `status=unconfirmed` is refused like any unknown status.

```json
{
  "errors": [
    { "code": "inclusion", "attribute": "status", "message": "Must be one of: waiting, invited, booked, withdrawn, archived." }
  ]
}
```

## Show a waitlist entry

`GET /api/v1/waitlist_entries/{id}` returns the entry in the object shown above. It returns `404` if the entry does not exist, belongs to another organization, is not confirmed yet or has been deleted.

## Keep your system in sync

`updated_since` works as on every list of the Scheduling API; [Appointments endpoints](https://help.meetone.io/developers/scheduling-api/appointments-endpoints#keep-your-system-in-sync) explains it in detail. In short:

- `updated_since=<timestamp>` returns the entries whose `updated_at` is **at or after** that time, oldest change first.
- **It pages by cursor.** Follow the `next` URL of the `link` response header until there is none, and do not build the `page` value yourself.
- **Store entries by `id` (upsert).** An entry that changes while you page comes again further on.
- **Next time, send the `updated_at` of the last entry you processed**, or a minute earlier to be safe.

An entry's `updated_at` changes with its status, its invitation, an answer to the "still interested?" email and your team's internal note. It is also set when the person confirms their email address, so a new entry reaches your sync with an `updated_at` later than its `submitted_at`.

> **Warning**
>
> Do not add a `status` filter to your sync. An entry that leaves that status, for example from `invited` to `booked`, no longer matches, and you would never read the change.

### Remove deleted entries

A sync never returns a deleted entry: it simply disappears. The [Waitlist API overview](https://help.meetone.io/developers/waitlist-api/overview#deleted-entries-disappear) lists when that happens. To catch deletions, read the whole list from time to time, for example once a day, and remove from your system every entry that was not in it.

Read the whole list with an `updated_since` far in the past, not with page numbers. Cursor pages continue after the last entry you read, so an entry deleted while you page cannot push another one off its page, as it can with page numbers. The same full read also refreshes what changed without touching `updated_at`, such as a question your organization renamed.

```python
import requests
from datetime import datetime, timedelta

BASE = "https://acme.meetone.io/api/v1"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}

def entries_since(since):
    """Yield every entry changed at or after `since`, page by page."""
    url, params = f"{BASE}/waitlist_entries", {"updated_since": since, "per_page": 100}
    while url:
        response = requests.get(url, headers=HEADERS, params=params)
        response.raise_for_status()
        yield from response.json()["data"]
        # The next URL carries the cursor and every other parameter.
        url, params = response.links.get("next", {}).get("url"), None

def sync(last_updated_at):
    """Every few minutes: store what changed; return the new last updated_at."""
    since = (datetime.fromisoformat(last_updated_at) - timedelta(minutes=1)).isoformat()
    for entry in entries_since(since):
        upsert(entry)                          # idempotent, keyed by entry["id"]
        last_updated_at = entry["updated_at"]  # oldest first: the last is the newest
    return last_updated_at

def reconcile():
    """Once a day, and for the first run: store everything, drop what MeetOne deleted."""
    seen, last_updated_at = set(), None
    for entry in entries_since("2000-01-01T00:00:00+00:00"):
        upsert(entry)
        seen.add(entry["id"])
        last_updated_at = entry["updated_at"]
    delete_all_except(seen)                    # entries deleted in MeetOne
    return last_updated_at
```

Persist the value `sync` and `reconcile` return, and pass it to the next `sync`. A full read costs one request per 100 entries, so keep the [rate limits](https://help.meetone.io/developers/getting-started/rate-limits) of your Organization API Token in mind when you choose how often to run it.

## Errors at a glance

| Status | Code | Meaning |
|---|---|---|
| `403` | `organization_token_required` | The request used an Instance API Token. |
| `403` | `feature_disabled` | The waitlist is not enabled for your organization. |
| `404` | `not_found` | The entry, or the practitioner you filtered by, does not exist or belongs to another organization. Entries that are not confirmed yet or were deleted return it too. |
| `422` | `inclusion` | `status` is not one of the statuses above. |
| `422` | `ambiguous_reference` | Both `doctor_id` and `doctor_external_id` were sent. |
| `422` | `invalid` | `updated_since` or the `page` cursor cannot be read, or `per_page` is below 1. |

## Related

- [Waitlist API overview](https://help.meetone.io/developers/waitlist-api/overview)
- [Appointments endpoints](https://help.meetone.io/developers/scheduling-api/appointments-endpoints)
- [Authentication & API tokens](https://help.meetone.io/developers/getting-started/authentication-api-tokens)
- [Errors & pagination](https://help.meetone.io/developers/getting-started/errors-and-pagination)

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