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

# Tasks

> Async operation polling: receive a task id, poll it for status, and handle the outcome.

Some operations can't finish inside one request because they wait on something slower: a seller that
reviews a media buy by hand, or an audience upload that has to be processed. For those, Semicola gives
you a **task id** and you poll the task until it reaches a final state.

A task has:

* a `taskId`;
* a `taskType`: `audience_sync` or `media_buy_create`;
* a `status` that follows AdCP task states: `submitted`, `working`, then a final state;
* a `resourceType` and `resourceId` naming the record the task works on;
* an `error` message when it fails;
* a `response` with the result once it completes;
* a `retryAfterSeconds` hint for how long to wait before polling again.

<Note>
  Task polling is the only way to follow these operations today. Webhook notifications for tasks
  aren't available yet: the buyer webhook subscriptions only carry the `discovery.revision` event.
</Note>

All endpoints below are under `https://api.semicola.com/api/v2/buyer/`.

## Where task ids come from

| Operation                                   | Where you get the task id                                                                                              |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Audience sync (`audience_sync`)             | `POST /advertisers/{id}/audiences/sync` answers `202 Accepted` with `taskId` in the body.                              |
| Seller review of a buy (`media_buy_create`) | When a seller accepts a buy for review instead of at once, the media buy carries the seller's task id in `adcpTaskId`. |

```bash theme={null}
curl -X POST "https://api.semicola.com/api/v2/buyer/advertisers/12/audiences/sync" \
  -H "Authorization: Bearer $SEMICOLA_API_KEY" \
  -H "X-Account-Id: $ACCOUNT_ID" \
  -H "Idempotency-Key: glaze-audiences-2026-11-01" \
  -H "Content-Type: application/json" \
  -d '{ "audiences": [ … ] }'
```

```json theme={null}
{
  "data": {
    "success": true,
    "accountId": "12",
    "operationId": "…",
    "taskId": "…"
  },
  "error": null
}
```

Most reads and ordinary writes are synchronous and never return a task id.

## Poll the task

```bash theme={null}
curl "https://api.semicola.com/api/v2/buyer/tasks/$TASK_ID" \
  -H "Authorization: Bearer $SEMICOLA_API_KEY" \
  -H "X-Account-Id: $ACCOUNT_ID"
```

```json theme={null}
{
  "data": {
    "taskId": "…",
    "taskType": "audience_sync",
    "status": "working",
    "resourceType": "advertiser",
    "resourceId": "12",
    "error": null,
    "response": null,
    "retryAfterSeconds": 5,
    "createdAt": "2026-11-01T14:30:00.000Z",
    "updatedAt": "2026-11-01T14:30:02.481Z"
  },
  "error": null
}
```

An id that isn't a task of your account returns `404 NOT_FOUND`.

### Status values

| Status           | Meaning                                                        | What to do                                                    |
| ---------------- | -------------------------------------------------------------- | ------------------------------------------------------------- |
| `submitted`      | Accepted, not finished. For a buy: the seller is reviewing it. | Wait `retryAfterSeconds`, then poll again.                    |
| `working`        | Being processed.                                               | Wait `retryAfterSeconds`, then poll again.                    |
| `input-required` | The seller needs something from you.                           | Read the media buy and answer what the seller asks.           |
| `completed`      | Done. `response` holds the result.                             | Stop polling and read the result.                             |
| `rejected`       | The seller declined the buy.                                   | Stop polling. The buy is `REJECTED` with the seller's reason. |
| `failed`         | Permanent failure. `error` says why.                           | Stop polling; fix the cause before trying again.              |

Keep polling only while the status is `submitted` or `working`. `retryAfterSeconds` is `5` for an
audience sync in progress, `30` for a buy under review, and `null` once the task is final.

## Outcomes by task type

**`audience_sync`.** `resourceType` is `advertiser` and `resourceId` is the advertiser id. On
`completed`, `response` holds the sync result; read the audiences themselves with
`GET /advertisers/{id}/audiences` (or `list_audiences` over MCP).

**`media_buy_create`.** `resourceType` is `media_buy` and `resourceId` is the Semicola media buy id
(`mb_…`). The task mirrors the buy's status: `PENDING_APPROVAL` reads as `submitted`,
`INPUT_REQUIRED` as `input-required`, `REJECTED` as `rejected`, `FAILED` as `failed`, and any other
status as `completed`. On `completed`, `response` is `{ "media_buy_id": "…" }`, the seller's own id for
the buy. `error` carries the seller's message when there is one.

Semicola follows seller tasks for you: it polls the seller and updates the media buy when the seller
decides, so reading the buy (or its campaign's media buy status) tells you the same thing.

## Related

<CardGroup cols={2}>
  <Card title="Media buy lifecycle" icon="arrows-spin" href="/concepts/media-buy-lifecycle">
    Every status a buy moves through.
  </Card>

  <Card title="Diagnosing stuck media buys" icon="stethoscope" href="/guides/diagnosing-stuck-media-buys">
    What to check when a buy doesn't move.
  </Card>
</CardGroup>
