> ## Documentation Index
> Fetch the complete documentation index at: https://docs.medisync.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Appointment

> Create a new appointment for the authenticated doctor

## Overview

Creates a new appointment owned by the authenticated doctor. The `doctor_id` is
taken from the JWT; all other details come from the request body. `name`,
`date` and `time` are required by the data model.

Optionally, supplying `notify_patient: true` together with a `patient` object
turns the same appointment into a confirmed booking and sends the patient a
confirmation email with a calendar (ICS) invite — no separate record is created.

<Note>
  This endpoint sits behind a subscription gate. If your trial or subscription
  has expired the request is rejected with `403` before it runs (see Error
  Responses). Free-trial and paid accounts are also subject to appointment /
  encounter caps that return structured error codes your integration should
  handle.
</Note>

## Authentication

<ParamField header="Authorization" type="string" required>
  Bearer token for authenticated access. The doctor id is taken from the token.

  ```
  Authorization: Bearer <JWT>
  ```
</ParamField>

## Body Parameters

<ParamField body="name" type="string" required>
  Appointment title.
</ParamField>

<ParamField body="date" type="string" required>
  `YYYY-MM-DD` (or ISO). When creating a `notify_patient` booking, this is
  combined with `time` in the practice timezone into a precise instant.
</ParamField>

<ParamField body="time" type="string" required>
  Wall-clock time, `HH:mm`.
</ParamField>

<ParamField body="status" type="string">
  Recording lifecycle status. Defaults to `recording`; pass `planned` to create
  a planned appointment.
</ParamField>

<ParamField body="recording_id" type="string">
  Associated recording id.
</ParamField>

<ParamField body="recording_url" type="string">
  Recording URL.
</ParamField>

<ParamField body="recording_key" type="string">
  Recording storage key.
</ParamField>

<ParamField body="notes" type="string">
  Private clinical notes.
</ParamField>

<ParamField body="language" type="string">
  ISO language code for note generation. Default `de`.
</ParamField>

<ParamField body="notes_type" type="string">
  Notes template key. Default `null` (the service selects a template).
</ParamField>

<ParamField body="appointment_type" type="string">
  Free-form appointment type. Default `in_person`.
</ParamField>

<ParamField body="duration_minutes" type="number">
  Duration in minutes, `5`–`480`. An out-of-range or non-numeric value falls
  back to the appointment type's duration (when `appointment_type_id` is given)
  or `null`.
</ParamField>

<ParamField body="room" type="string">
  Room / location label. Whitespace-only or empty is stored as `null`.
  Remembered for the practice's room autocomplete.
</ParamField>

<ParamField body="appointment_type_id" type="string">
  ObjectId of an appointment type you own. Used to default the duration and tag
  the booking source. An unknown or unowned id returns `404`.
</ParamField>

<ParamField body="notify_patient" type="boolean">
  When `true`, the appointment becomes a confirmed booking and a confirmation
  email + ICS invite is sent. Requires a valid `patient.email`.
</ParamField>

<ParamField body="patient" type="object">
  Patient contact details for the confirmation email.

  <Expandable title="patient">
    <ParamField body="first_name" type="string">
      Patient first name.
    </ParamField>

    <ParamField body="last_name" type="string">
      Patient last name.
    </ParamField>

    <ParamField body="email" type="string">
      Patient email. Required and validated only when `notify_patient` is `true`.
    </ParamField>

    <ParamField body="phone" type="string">
      Patient phone number.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="doctor_note" type="string">
  Patient-facing note stored on the booking (only when a booking is created).
</ParamField>

## Response

<ResponseField name="success" type="boolean">
  Whether the request succeeded.
</ResponseField>

<ResponseField name="data" type="object">
  The created appointment object.

  <Expandable title="Appointment Object">
    <ResponseField name="_id" type="string">
      Unique appointment identifier (ObjectId).
    </ResponseField>

    <ResponseField name="name" type="string">
      Appointment title.
    </ResponseField>

    <ResponseField name="date" type="string">
      Appointment date, serialized as a full ISO datetime.
    </ResponseField>

    <ResponseField name="time" type="string">
      Wall-clock time, `HH:mm`.
    </ResponseField>

    <ResponseField name="status" type="string">
      Recording lifecycle status.
    </ResponseField>

    <ResponseField name="error_reason" type="string | null">
      Human-readable failure reason, or `null`.
    </ResponseField>

    <ResponseField name="doctor_id" type="string">
      Owning doctor's user id.
    </ResponseField>

    <ResponseField name="notes" type="string">
      Private clinical notes.
    </ResponseField>

    <ResponseField name="language" type="string">
      Language code. Default `de`.
    </ResponseField>

    <ResponseField name="notes_type" type="string | null">
      Notes template key. Default `null`.
    </ResponseField>

    <ResponseField name="appointment_type" type="string">
      Free-form appointment type. Default `in_person`.
    </ResponseField>

    <ResponseField name="room" type="string | null">
      Room / location label, or `null`.
    </ResponseField>

    <ResponseField name="duration_minutes" type="number | null">
      Duration in minutes, or `null`.
    </ResponseField>

    <ResponseField name="deletionDate" type="string">
      ISO datetime for automatic deletion (appointment date plus your retention
      window, default 14 days).
    </ResponseField>

    <ResponseField name="booking_status" type="string">
      Booking lifecycle state. `none` for a plain appointment; `confirmed` when
      created as a `notify_patient` booking.
    </ResponseField>

    <ResponseField name="booking" type="object | null">
      Booking subdocument. `null` unless the appointment was created with
      `appointment_type_id` or `notify_patient`.
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      ISO creation timestamp.
    </ResponseField>

    <ResponseField name="updatedAt" type="string">
      ISO last-update timestamp.
    </ResponseField>

    <ResponseField name="__v" type="number">
      Internal document version.
    </ResponseField>
  </Expandable>
</ResponseField>

<Note>
  Additional fields may appear on the appointment object. Treat any field not
  documented here as opaque.
</Note>

## Example Request

<RequestExample>
  ```bash curl theme={null}
  curl -X POST "https://app.medisync.me/api/appointments/add" \
    -H "Authorization: Bearer <JWT>" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Allgemeine Beratung",
      "date": "2026-07-15",
      "time": "14:30",
      "status": "planned",
      "notes": "Erstberatung",
      "language": "de",
      "appointment_type": "in_person"
    }'
  ```
</RequestExample>

## Example Response

<ResponseExample>
  ```json Response theme={null}
  {
    "success": true,
    "data": {
      "_id": "65f8a1b2c3d4e5f6789012ef",
      "name": "Allgemeine Beratung",
      "date": "2026-07-15T00:00:00.000Z",
      "time": "14:30",
      "status": "planned",
      "error_reason": null,
      "doctor_id": "65f0a1b2c3d4e5f678901234",
      "notes": "Erstberatung",
      "language": "de",
      "notes_type": null,
      "appointment_type": "in_person",
      "room": null,
      "duration_minutes": null,
      "deletionDate": "2026-07-29T00:00:00.000Z",
      "booking_status": "none",
      "booking": null,
      "createdAt": "2026-07-02T09:12:00.000Z",
      "updatedAt": "2026-07-02T09:12:00.000Z",
      "__v": 0
    }
  }
  ```
</ResponseExample>

## Error Responses

<ResponseExample>
  ```json 403 Trial Appointment Limit theme={null}
  {
    "success": false,
    "code": "TRIAL_APPOINTMENT_LIMIT_REACHED",
    "limit": 10,
    "used": 10,
    "message": "Free-trial appointment limit reached. Upgrade to create more appointments."
  }
  ```

  ```json 403 Encounter Limit theme={null}
  {
    "success": false,
    "code": "ENCOUNTER_LIMIT_REACHED",
    "limit": 200,
    "used": 200,
    "message": "Monthly encounter limit reached. Purchase an encounter pack to continue."
  }
  ```

  ```json 409 Slot Taken theme={null}
  {
    "success": false,
    "code": "SLOT_TAKEN",
    "error": "A confirmed appointment already exists at this time"
  }
  ```

  ```json 404 Appointment Type Not Found theme={null}
  {
    "success": false,
    "error": "Appointment type not found"
  }
  ```

  ```json 400 Patient Email Required theme={null}
  {
    "success": false,
    "code": "PATIENT_EMAIL_REQUIRED",
    "error": "A valid patient email is required to send a notification"
  }
  ```

  ```json 403 Trial Expired (subscription gate) theme={null}
  {
    "success": false,
    "message": "Ihr Probezeitraum ist abgelaufen. Bitte abonnieren Sie einen Plan, um MediSync weiterhin zu nutzen.",
    "trialExpired": true
  }
  ```

  ```json 401 Unauthorized theme={null}
  {
    "success": false,
    "message": "Unauthorized"
  }
  ```
</ResponseExample>

<Note>
  Branch on the `code` field for the structured limit responses
  (`TRIAL_APPOINTMENT_LIMIT_REACHED`, `ENCOUNTER_LIMIT_REACHED`, `SLOT_TAKEN`,
  `PATIENT_EMAIL_REQUIRED`). The subscription gate returns a German `message`
  with a `trialExpired` (or `subscriptionExpired`) flag. Missing required fields
  or other save failures return `400`/`500` with the raw error in the `error`
  field. A missing or invalid Bearer token returns `401` with a `message` key.
</Note>
