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.

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.
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.
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.
answers array The answers to your organization's waitlist questions. See Answers.

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

{
  "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. 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.
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.

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.

{
  "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 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.

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

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

Questions? Contact us at support@meetone.io.