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

# Responding to waiting Tasks

> When a Task pauses at waiting it needs more input. Add files or data and resume it.

## The waiting/resume loop

A Task does not always run straight to a terminal status. When Synthpop
determines it needs more from you — a missing document, an additional field — the
Task pauses at `status: "waiting"`. This is a normal, expected state, not a
failure. Your integration's job is to supply what's missing and let the Task
continue:

<Steps>
  <Step title="Detect waiting">
    While polling [`GET /task/get`](/guides/async-results) (or on a callback), you
    see `status: "waiting"`. Inspect the Task's `issues` to understand what is
    needed — for example a `task.medical_record.missing` or `task.data.missing`
    entry.
  </Step>

  <Step title="Supply the missing input">
    Add **files** with `POST /task/{uuid}/upload`, or add **structured JSON** with
    `PATCH /task/{uuid}/update`. Both are covered below.
  </Step>

  <Step title="Resume and keep polling">
    Uploading resumes the Task automatically. Updating resumes it only when you
    pass `?should_resume=true`. The Task moves back to `processing` and continues
    toward a terminal status (or pauses at `waiting` again if more is still
    needed).
  </Step>
</Steps>

<Note>
  A Task only accepts additional input while it is `waiting`. Sending files or a
  resume to a Task in any other status is rejected with `409` — see
  [Frozen and non-waiting Tasks](#frozen-and-non-waiting-tasks).
</Note>

## Add files: `POST /task/{uuid}/upload`

Use this when the missing input is one or more documents. It is a
`multipart/form-data` request with one or more `uploads` parts — the same shape as
the files you passed to `POST /task/create`. The Task **must be in `waiting`**.

The response is an `UploadDataResponse`:

| Field            | Type           | Description                                                                          |
| ---------------- | -------------- | ------------------------------------------------------------------------------------ |
| `new_data_items` | array of UUIDs | IDs of the newly created data items, in the same order as the files in your request. |
| `task`           | `TaskDetails`  | The Task's new state after the uploads were applied.                                 |

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://app.synthpop.ai/public/v1/task/$TASK_UUID/upload" \
    -H "Authorization: Bearer $SYNTHPOP_JWT_TOKEN" \
    -F "uploads=@progress-note.pdf;type=application/pdf" \
    -F "uploads=@insurance-card.jpg;type=image/jpeg"
  ```

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

  BASE_URL = "https://app.synthpop.ai/public/v1"

  with open("progress-note.pdf", "rb") as f1, open("insurance-card.jpg", "rb") as f2:
      resp = requests.post(
          f"{BASE_URL}/task/{task_uuid}/upload",
          headers={"Authorization": f"Bearer {jwt_token}"},
          files=[
              ("uploads", ("progress-note.pdf", f1, "application/pdf")),
              ("uploads", ("insurance-card.jpg", f2, "image/jpeg")),
          ],
      )

  resp.raise_for_status()
  body = resp.json()
  new_ids = body["new_data_items"]      # -> ["<uuid>", "<uuid>"]
  task = body["task"]                    # -> TaskDetails, now back in "processing"
  ```
</CodeGroup>

<Tip>
  Uploads are deduplicated by content: re-sending the identical bytes is a no-op
  rather than a duplicate data item. `external_id` is a correlation key, not an
  idempotency key — it does not affect dedup. If **all** files in an upload are duplicates, the upload returns `409` instead.
</Tip>

## Add structured data: `PATCH /task/{uuid}/update`

Use this when the missing input is structured data rather than a file, or when you
want to change Task metadata. The request body is a `TaskUpdateRequest`; **set only
the fields you want to change and omit the rest.**

| Field           | Type        | Description                                                                                                                                                                       |
| --------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`          | string      | Set the Task's user-friendly name.                                                                                                                                                |
| `cleared`       | boolean     | Mark that your org's follow-up work for this Task is (in)complete. You **set** `cleared` with a boolean via update; it **reads back** as the timestamp it was set at (or `null`). |
| `external_id`   | string      | Set or change your correlation ID for the Task.                                                                                                                                   |
| `new_data_item` | JSON object | Add a new JSON data item to the Task — this is how you supply structured input to a `waiting` Task.                                                                               |

To actually **resume** a waiting Task after adding data, pass the query parameter
`?should_resume=true`. It defaults to `false`, so an update alone changes metadata
without restarting processing.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH "https://app.synthpop.ai/public/v1/task/$TASK_UUID/update?should_resume=true" \
    -H "Authorization: Bearer $SYNTHPOP_JWT_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
          "new_data_item": {
            "date_of_service": "2026-07-01",
            "referring_provider_npi": "1234567890"
          }
        }'
  ```

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

  BASE_URL = "https://app.synthpop.ai/public/v1"

  resp = requests.patch(
      f"{BASE_URL}/task/{task_uuid}/update",
      headers={"Authorization": f"Bearer {jwt_token}"},
      params={"should_resume": "true"},
      json={
          "new_data_item": {
              "date_of_service": "2026-07-01",
              "referring_provider_npi": "1234567890",
          }
      },
  )

  resp.raise_for_status()
  task = resp.json()  # -> TaskDetails
  ```
</CodeGroup>

<Note>
  Metadata-only changes (renaming, setting `cleared`, changing `external_id`) are
  valid on a Task in any status and do not require `should_resume`. Use
  `should_resume=true` only when you have added the input the Task was waiting for.
</Note>

## Frozen and non-waiting Tasks

Two situations return `409 Conflict` from `POST /task/{uuid}/upload`:

1. **The Task is not `waiting`.** It is not accepting additional input.
2. **The Task is frozen.** A newer, patient-matched Task has superseded this one.
   The `409` body is a `TaskFrozenError`, which names the Task you should be
   working with instead:

<Accordion title="409 TaskFrozenError response body">
  ```json theme={null}
  {
    "message": "This task has been superseded and is frozen; upload to the current task instead.",
    "current_task_uuid": "3f6b2c1a-9d4e-4b7a-8c21-0a1b2c3d4e5f"
  }
  ```
</Accordion>

<Warning>
  When you receive a `TaskFrozenError`, **redirect your upload to
  `current_task_uuid`** — do not retry against the frozen Task. Fetch that Task
  with `GET /task/get?uuid=<current_task_uuid>`, confirm it is `waiting`, and add
  your input there. Retrying the frozen UUID will only return `409` again.
</Warning>

## Related

<CardGroup cols={2}>
  <Card title="Working with async results" icon="rotate" href="/guides/async-results">
    How to detect the `waiting` status while polling or via callback.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/guides/errors">
    Every status code the API returns, with real error bodies.
  </Card>
</CardGroup>
