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

# Tasks

> The Task is Synthpop's one first-class resource: create it, add data to it, read it back. This is its shape and lifecycle.

A **Task** is the single resource you program against. Every capability — intake,
coverage, engagement — is a Task differentiated by its `task_type`. Everything you
do is: create a Task, add data to it, read it back, or list Tasks. A Task is
returned as a `TaskDetails` object from every endpoint except the download-link
one.

## Creating a Task

`POST /task/create` is the entry point. It is a `multipart/form-data` request:

```bash cURL theme={null}
curl -X POST "https://app.synthpop.ai/public/v1/task/create" \
  -H "Authorization: Bearer $SYNTHPOP_JWT_TOKEN" \
  -F "task_type=task" \
  -F "uploads=@referral-packet.pdf;type=application/pdf" \
  -F "external_id=order-4821"
```

The response comes back immediately with `status: "pending"` and empty `verdicts` —
Synthpop processes the work in the background.

```json theme={null}
{
  "uuid": "0685bb22-eeec-776c-8000-ad631be0e894",
  "external_id": "order-4821",
  "task_type": "task",
  "status": "pending",
  "name": "Robert: 7",
  "cleared": null,
  "created": "2026-07-15T08:24:15.571185Z",
  "latest_status_change": "2026-07-15T08:24:15.571185Z",
  "category": null,
  "data_items": [ /* ... */ ],
  "issues": [],
  "verdicts": []
}
```

<Note>
  The API is asynchronous. Results exist only after you poll `GET /task/get` to a
  terminal status, or receive a callback. See
  [Async results](/guides/async-results).
</Note>

### `task_type` (required)

<ResponseField name="task_type" type="string" required>
  Determines what processing Synthpop performs. Task types are **provisioned for
  your organization** during onboarding — there is no discovery endpoint, so the
  values available to you come from your Synthpop contact. Passing an
  unprovisioned type returns `400 "Task type 'X' not in list of tasks for your organization: [...]."`
</ResponseField>

### Optional inputs on create

<ResponseField name="uploads" type="file[]">
  Zero or more documents (PDFs, images, text, audio). Uploads are deduplicated by
  content — re-submitting identical bytes is a no-op, not a duplicate. (An upload where every file is a duplicate returns `409` rather than a no-op.)
</ResponseField>

<ResponseField name="request_spec" type="object">
  Task-type-specific JSON configuration. What it accepts depends on the
  `task_type`; leave it empty unless your Synthpop contact specifies fields.
</ResponseField>

<ResponseField name="external_id" type="string">
  Your own identifier for this Task, echoed back on every read. It is a
  **correlation key, not an idempotency key** — sending the same `external_id`
  twice creates two Tasks. Use it to tie Synthpop's work back to a record in your
  system and to find related Tasks via [`GET /task/list`](/guides/listing-tasks).
</ResponseField>

<ResponseField name="name" type="string">
  An optional friendly label. If you omit it, Synthpop derives a name from the
  extracted content (e.g. `"Jordan Lee (1985-04-12): Order intake"`).
</ResponseField>

<ResponseField name="callback_template" type="string">
  A URL pattern (with `{uuid}` and `{external_id}` placeholders) that Synthpop
  calls when the Task finishes. Callbacks are **opt-in and org-gated** — they fire
  only for organizations that have them enabled. See
  [Async results](/guides/async-results).
</ResponseField>

## The `TaskDetails` shape

<ResponseField name="uuid" type="string (uuid)" required>
  Synthpop's unique ID for the Task.
</ResponseField>

<ResponseField name="external_id" type="string | null" required>
  Your correlation key (see above), or `null`.
</ResponseField>

<ResponseField name="task_type" type="string" required>
  The type the Task was created with.
</ResponseField>

<ResponseField name="status" type="TaskStatus" required>
  The current state. See [the status lifecycle](#the-status-lifecycle) below.
</ResponseField>

<ResponseField name="name" type="string" required>
  A user-friendly name, derived from extracted inputs if you did not supply one.
</ResponseField>

<ResponseField name="category" type="string | null" required>
  A suggested category determined from the medical records found (e.g.
  `"outpatient-referral"`). `null` until enough has been extracted.
</ResponseField>

<ResponseField name="cleared" type="string (date-time) | null" required>
  Marks that follow-up work *external to Synthpop* is done. You **set** `cleared`
  with a boolean via [`PATCH /task/{uuid}/update`](/guides/responding-to-waiting-tasks);
  it **reads back** as the timestamp it was set at (or `null` if never set).
</ResponseField>

<ResponseField name="created" type="string (date-time)" required>
  When the Task was created.
</ResponseField>

<ResponseField name="latest_status_change" type="string (date-time)" required>
  When the status last changed (entered `processing`, `waiting`, `completed`, or
  `failed`). Equal to `created` while the Task is `pending`.
</ResponseField>

<ResponseField name="data_items" type="DataItem[]" required>
  The Task's inputs, outputs, and extracted records. See
  [Data Items](/concepts/data-items).
</ResponseField>

<ResponseField name="issues" type="TaskIssue[]" required>
  Issues detected with the Task or its medical records. See [Results](/concepts/results#issues).
</ResponseField>

<ResponseField name="verdicts" type="Verdict[]">
  Per-code approval decisions (validation tasks). See [Results](/concepts/results#verdicts).
</ResponseField>

<ResponseField name="verdict_summaries" type="VerdictSummary[]">
  Per-code human-readable conclusions (validation tasks). See [Results](/concepts/results#conclusions).
</ResponseField>

## The status lifecycle

`status` is a `TaskStatus` — one of **exactly these six values** you can observe and no
others. (The underlying `TaskStatus` enum also defines `deferred`, but the API
never returns it — a deferrable Task reports as `pending` until it runs.)

| Status       | Terminal? | Meaning                                                                                                   |
| ------------ | --------- | --------------------------------------------------------------------------------------------------------- |
| `pending`    | No        | Created and waiting to be processed. The initial status of every Task.                                    |
| `processing` | No        | Actively being processed.                                                                                 |
| `waiting`    | No        | Paused, awaiting additional input from you (e.g. a missing document). Resume it by updating or uploading. |
| `completed`  | Yes       | Processing finished successfully; results are available.                                                  |
| `failed`     | Yes       | An error occurred during processing.                                                                      |
| `invalid`    | Yes       | The uploaded data provides no information relevant to the requested task.                                 |

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending
    pending --> processing
    processing --> waiting
    waiting --> processing
    processing --> completed
    processing --> failed
    processing --> invalid
```

<Warning>
  `waiting` is **not** an error or a stuck state. A Task in `waiting` needs something from you
  before it can continue — typically a document flagged in its
  [issues](/concepts/results#issues). Supply it and the Task resumes. See
  [Responding to waiting tasks](/guides/responding-to-waiting-tasks).
</Warning>

## Frozen tasks — a 409 condition, not a status

When a newer patient-matched Task supersedes an older one, the older Task is
**frozen**. Frozen is *not* a status value — it is a condition you discover by
trying to add to the Task. Uploading to a frozen Task returns
`409` with a `TaskFrozenError` body:

<ResponseField name="message" type="string" required>
  Human-readable description of the error.
</ResponseField>

<ResponseField name="current_task_uuid" type="string (uuid)" required>
  The UUID of the current, non-frozen Task that your uploads should be directed to.
</ResponseField>

```json theme={null}
{
  "message": "This task has been superseded and can no longer accept uploads.",
  "current_task_uuid": "0685cd41-1a2b-7c3d-8000-9e0f1a2b3c4d"
}
```

Redirect your upload to `current_task_uuid`. This is detailed in
[Responding to waiting tasks](/guides/responding-to-waiting-tasks).

## Working with an existing Task

| Endpoint                           | What it does                                                                            |
| ---------------------------------- | --------------------------------------------------------------------------------------- |
| `GET /task/get`                    | Read a Task by `uuid` **xor** `external_id` (supplying neither or both is a `400`).     |
| `PATCH /task/{uuid}/update`        | Change metadata (`name`, `cleared`, `external_id`) and resume a `waiting` Task.         |
| `POST /task/{uuid}/upload`         | Add more files to a Task awaiting input.                                                |
| `POST /task/{task_uuid}/log_event` | Record a client-side event against a Task (your own event logging).                     |
| `GET /task/list`                   | List your Tasks, filtered/sorted/paginated. See [Listing tasks](/guides/listing-tasks). |

<Note>
  Every Task is org-scoped: it is visible only to the organization that owns it.
  See [Authentication](/authentication).
</Note>
