Skip to main content

Overview

Some v2 endpoints can’t return a final result synchronously — typically because they fan out to a slow upstream (a sales agent that takes minutes to confirm a media buy, an audience platform that hashes and matches CRM data, a creative agent rendering a large asset). For those operations, the API returns a task ID immediately with HTTP 202 Accepted. The task tracks the async work; you poll it (or receive a webhook) until it reaches a terminal state. A task has:
  • A stable UUID taskId
  • A taskType describing the operation kind (audience_sync, media_buy_create, creative_sync)
  • A status that progresses through AdCP states: submittedworkingcompleted (or failed, or input-required)
  • A resourceId populated on completion that points at the resource the task created or updated
  • An error object populated on failure
  • A response payload with the full downstream response
  • A retryAfterSeconds hint that tells you how often to poll
Webhooks are preferred over polling whenever they’re available. See Notifications for the push-based path. The task endpoint exists as the AdCP-compatible polling fallback for callers that can’t receive webhooks.
All endpoints below are mounted under https://api.agentic.scope3.com/api/buyer/.

Prerequisites

  • A Scope3 API key (see Authentication)
  • A response from an endpoint that returned a task ID (e.g. an audience sync)
export SCOPE3_API_KEY=scope3_<your_api_key>
export BASE=https://api.agentic.scope3.com/api/buyer

Step 1: Recognize when a task is returned

Async endpoints return HTTP 202 Accepted with a taskId in the response body. For example, syncing an audience:
curl -X POST "$BASE/advertisers/42/audiences/sync" \
  -H "Authorization: Bearer $SCOPE3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "audiences": [
      { "name": "high_value_buyers", "members": [/* ... */] }
    ]
  }'
{
  "data": {
    "success": true,
    "accountId": "42",
    "operationId": "op_5f2a...",
    "taskId": "550e8400-e29b-41d4-a716-446655440000"
  }
}
The taskId is a UUID. Capture it and poll the task endpoint until the operation is done.
The current set of operations that go through the task system is audience_sync, media_buy_create, and creative_sync. Synchronous endpoints (most reads, simple updates) do not return a task ID.

Step 2: Poll for status

curl "$BASE/tasks/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
{
  "data": {
    "taskId": "550e8400-e29b-41d4-a716-446655440000",
    "taskType": "audience_sync",
    "status": "working",
    "resourceType": "audience",
    "resourceId": null,
    "error": null,
    "response": null,
    "metadata": {
      "accountId": "42",
      "audienceCount": 3,
      "deleteMissing": false
    },
    "retryAfterSeconds": 30,
    "createdAt": "2026-04-26T14:30:00.000Z",
    "updatedAt": "2026-04-26T14:30:12.481Z"
  }
}

Status values

StatusMeaningWhat to do
submittedTask accepted, not yet picked upWait retryAfterSeconds and poll again
workingDownstream system is processingWait retryAfterSeconds and poll again
input-requiredThe downstream needs additional input from youInspect response / error.suggestion and resubmit the originating call with corrections
completedDone — resourceId and response populatedStop polling; read the result
failedPermanent failure — error populatedStop polling; read the error and decide whether to retry
submitted and working are the only non-terminal states. completed, failed, and input-required are terminal from the polling perspective — keep polling only while the status is submitted or working.

Step 3: Handle outcomes

completed

Read resourceId to find the entity the task created or updated, and response for the full downstream payload.
{
  "data": {
    "taskId": "550e8400-e29b-41d4-a716-446655440000",
    "taskType": "audience_sync",
    "status": "completed",
    "resourceType": "audience",
    "resourceId": "aud_12345",
    "response": { "matched": 8421, "unmatched": 312 },
    "error": null,
    "retryAfterSeconds": null,
    "createdAt": "2026-04-26T14:30:00.000Z",
    "updatedAt": "2026-04-26T14:32:47.901Z"
  }
}

failed

Inspect the AdCP-compatible error object:
{
  "data": {
    "taskId": "550e8400-e29b-41d4-a716-446655440000",
    "status": "failed",
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "Invalid budget value",
      "field": "packages[0].targeting",
      "suggestion": "Ensure budget is positive",
      "retryAfter": 60,
      "recovery": "correctable"
    }
  }
}
error.recovery classifies the failure for agent retry logic:
RecoveryMeaning
transientRetry the original call after a backoff — the upstream is temporarily unavailable
correctableFix the input per error.field / error.suggestion, then retry
terminalNo automatic retry will help — surface to a human operator
If error.retryAfter is set, wait at least that many seconds before retrying the original operation.

working / submitted — backing off

The response carries retryAfterSeconds as a hint; treat it as a floor. If the operation doesn’t complete within your timeout, surface the task ID to the caller so they can poll later — tasks are durable and outlive the originating request.
# pseudo-loop
while true; do
  resp=$(curl -s "$BASE/tasks/$TASK_ID" -H "Authorization: Bearer $SCOPE3_API_KEY")
  status=$(echo "$resp" | jq -r '.data.status')
  case "$status" in
    completed|failed|input-required) echo "$resp"; break ;;
    *) sleep "$(echo "$resp" | jq -r '.data.retryAfterSeconds // 30')" ;;
  esac
done
If the underlying operation is rate-limited, the API will return a 429 on the originating call (not on the task poll). See Rate limits for Retry-After handling.

Best practices

  • Prefer webhooks — register a pushNotificationConfig on the originating call where the endpoint supports it (e.g. the audience sync endpoint accepts one). The webhook fires on every status transition; polling is only the fallback.
  • Honour retryAfterSeconds — it’s set per task type and reflects how fast the downstream actually changes state. Polling more aggressively wastes quota and won’t make the task complete sooner.
  • Cap polling duration — if a task hasn’t reached a terminal state within an order of magnitude of the typical completion time for its taskType, escalate to operator review rather than spinning forever.
  • Tasks are scoped to your customerGET /tasks/:taskId returns 404 if the task doesn’t belong to the authenticated customer. Treat the task ID as opaque and don’t share it across tenants.
  • Persist the taskId — store it next to the originating request so a later process (or a human operator) can resolve the outcome even if the originating client died.
  • Idempotency — the underlying AdCP operations are idempotent on the originating call’s idempotency key; if a task failed with recovery: transient, you can safely retry the original call with the same idempotency key.

Endpoint reference

MethodPathPurpose
GET/tasks/:taskIdFetch the current status of an async task
Path parameter: taskId (UUID). Response: { data: TaskOutput } where TaskOutput includes taskId, taskType, status, resourceType, resourceId, error, response, metadata, retryAfterSeconds, createdAt, updatedAt. Status enum: submitted, working, completed, failed, input-required. Task type enum: audience_sync, media_buy_create, creative_sync. See the OpenAPI spec for the full schema: API Reference.
  • Notifications — webhook-based delivery of the same task lifecycle events; preferred over polling
  • Rate limitsRetry-After semantics on the originating call
  • Errors — error codes that appear in the task error object