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

# Upload Medical Document

> Upload a document file and attach it to an appointment

## Overview

Uploads a document file and attaches it to an existing appointment. The file is stored in secure object storage and a document record is created and linked to the appointment. The uploading doctor is taken from the JWT token.

Accepted file types are **PDF, PNG, JPEG, JPG and WEBP** (`application/pdf`, `image/png`, `image/jpeg`, `image/jpg`, `image/webp`).

<Info>
  This endpoint is plan-gated by the `documents.attach_additional` feature. Accounts whose plan does not include it receive `403 FEATURE_NOT_AVAILABLE`.
</Info>

## Authentication

<ParamField header="Authorization" type="string" required>
  Bearer token for authenticated access. The uploading doctor's ID is extracted from the JWT token.

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

## Path Parameters

<ParamField path="id" type="string" required>
  The appointment ID (ObjectId) to attach the document to. The appointment must belong to the authenticated doctor, otherwise a `403 Forbidden` is returned.
</ParamField>

## Body Parameters

The request must be sent as `multipart/form-data`.

<ParamField body="document" type="file" required>
  The document file. Allowed types: `application/pdf`, `image/png`, `image/jpeg`, `image/jpg`, `image/webp`.
</ParamField>

<ParamField body="title" type="string">
  Optional display title for the document. If omitted, the uploaded file's original filename is used.
</ParamField>

## Response

<ResponseField name="success" type="boolean">
  Whether the upload was successful
</ResponseField>

<ResponseField name="data" type="object">
  The stored document record

  <Expandable title="Document Object">
    <ResponseField name="_id" type="string">
      Unique document identifier (ObjectId)
    </ResponseField>

    <ResponseField name="appointment_id" type="string">
      The appointment this document is attached to
    </ResponseField>

    <ResponseField name="document_url" type="string">
      Opaque storage URL for the document
    </ResponseField>

    <ResponseField name="document_key" type="string">
      Storage key. Format: `{appointment_id}_{timestamp}_{original_filename}`
    </ResponseField>

    <ResponseField name="doctor_id" type="string">
      ObjectId of the doctor who uploaded the document (the authenticated user)
    </ResponseField>

    <ResponseField name="file_name" type="string">
      Original filename of the uploaded file
    </ResponseField>

    <ResponseField name="title" type="string">
      Display title (defaults to the original filename)
    </ResponseField>

    <ResponseField name="file_type" type="string">
      Inferred short file type (e.g. `pdf`, `png`, `jpeg`, `webp`)
    </ResponseField>

    <ResponseField name="mime_type" type="string">
      MIME type of the stored file
    </ResponseField>

    <ResponseField name="source" type="string">
      Origin of the document. `upload` for files uploaded through this endpoint
    </ResponseField>

    <ResponseField name="extraction_status" type="string">
      Text-extraction state. Newly uploaded documents start as `pending`
    </ResponseField>

    <ResponseField name="include_in_context" type="boolean">
      Whether the document is included when building context. Defaults to `true`
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      Creation timestamp (ISO 8601)
    </ResponseField>

    <ResponseField name="updatedAt" type="string">
      Last-update timestamp (ISO 8601)
    </ResponseField>

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

<ResponseField name="error" type="string">
  Error message (only present when `success` is `false`)
</ResponseField>

<Note>
  Additional fields may be present on the document object and should be treated as opaque.
</Note>

## Example Request

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST \
    'https://app.medisync.me/api/documents/add/665e0b9d3f2a1c4d8e7f0a12' \
    -H 'Authorization: Bearer your_jwt_token_here' \
    -F 'document=@lab_results.pdf' \
    -F 'title=Lab Results'
  ```

  ```javascript JavaScript theme={null}
  const formData = new FormData();
  formData.append('document', file); // PDF or image file
  formData.append('title', 'Lab Results'); // optional

  const response = await fetch(
    'https://app.medisync.me/api/documents/add/665e0b9d3f2a1c4d8e7f0a12',
    {
      method: 'POST',
      headers: { Authorization: 'Bearer your_jwt_token_here' },
      body: formData,
    }
  );

  const result = await response.json();
  ```

  ```python Python theme={null}
  import requests

  files = {'document': open('lab_results.pdf', 'rb')}
  data = {'title': 'Lab Results'}  # optional

  response = requests.post(
      'https://app.medisync.me/api/documents/add/665e0b9d3f2a1c4d8e7f0a12',
      headers={'Authorization': 'Bearer your_jwt_token_here'},
      files=files,
      data=data,
  )

  result = response.json()
  ```
</RequestExample>

## Example Response

<ResponseExample>
  ```json Response theme={null}
  {
    "success": true,
    "data": {
      "_id": "66a3f1c2b8e4d21f9c0a7b31",
      "appointment_id": "665e0b9d3f2a1c4d8e7f0a12",
      "document_url": "https://storage.medisync.me/665e0b9d3f2a1c4d8e7f0a12_1719920400000_lab_results.pdf",
      "document_key": "665e0b9d3f2a1c4d8e7f0a12_1719920400000_lab_results.pdf",
      "doctor_id": "6512a7c9e4b0a1f2d3c45678",
      "file_name": "lab_results.pdf",
      "title": "lab_results.pdf",
      "file_type": "pdf",
      "mime_type": "application/pdf",
      "source": "upload",
      "extraction_status": "pending",
      "include_in_context": true,
      "createdAt": "2026-07-02T14:30:00.000Z",
      "updatedAt": "2026-07-02T14:30:00.000Z",
      "__v": 0
    }
  }
  ```
</ResponseExample>

## Error Responses

<ResponseExample>
  ```json 400 No file uploaded theme={null}
  {
    "success": false,
    "error": "No file uploaded"
  }
  ```

  ```json 400 Unsupported file type theme={null}
  {
    "success": false,
    "error": "Unsupported file type. Allowed: PDF, PNG, JPEG, WEBP."
  }
  ```

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

  ```json 403 Forbidden (appointment owned by another doctor) theme={null}
  {
    "success": false,
    "error": "Forbidden"
  }
  ```

  ```json 403 Feature not in plan theme={null}
  {
    "success": false,
    "code": "FEATURE_NOT_AVAILABLE",
    "feature": "documents.attach_additional",
    "message": "This feature is not included in your current plan."
  }
  ```

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

## Behavior

* **Automatic appointment linking**: on success, the appointment record's `document_id` is set to the newly created document.
* **Upload/save failures** are returned as `400` with `{ "success": false, "error": "Error uploading or saving document" }`.
* The `Authorization` errors above also cover revoked sessions, which return `401` with `{ "success": false, "message": "Session ended on this device because you signed in elsewhere.", "code": "SESSION_REVOKED" }`.
