# Tasks API

> Create Turnstile tasks, read their results, and follow them live.

Source: https://zerocaptcha.io/docs/reference/api/tasks

## List tasks

`GET /v1/tasks` (`listTasks`)

Newest first, with cursor pagination.

| Parameter | In | Required | What it is |
| --- | --- | --- | --- |
| `status` | query | no | Only tasks with this status. |
| `limit` | query | no | Tasks per page, 1 to 100; 50 by default. |
| `cursor` | query | no | `nextCursor` from the previous page. |
| `idempotencyKey` | query | no | Only the task created with this Idempotency-Key, to recover a reply that was lost. |

Responses:

- 200 OK: A page of tasks.
- 429 Too Many Requests: Over a budget for reading (`rate_limited`); retry after `Retry-After`, and poll less often.
- Any other status: An error, as RFC 9457 problem details.

```bash
curl https://api.zerocaptcha.io/v1/tasks \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
const response = await fetch("https://api.zerocaptcha.io/v1/tasks", {
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
  },
});
console.log(response.status, await response.text());
```

```python
import requests

response = requests.get(
    "https://api.zerocaptcha.io/v1/tasks",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    timeout=30,
)
print(response.status_code, response.text)
```

## Create a task

`POST /v1/tasks` (`createTask`)

Queues a task and holds its price on your balance; the price is charged
only if the task succeeds. Send an `Idempotency-Key` to retry safely: for
24 hours, the same key and request return the first reply instead of a
second task, and the same key with a different request is refused.
Creations are bounded by your balance and your account's share of the
queue, and by no rate budget unless the service sets one.

| Parameter | In | Required | What it is |
| --- | --- | --- | --- |
| `Idempotency-Key` | header | no | Your ID for this task, 1 to 255 visible ASCII characters. |

Body fields:

| Field | Type | Required | What it is |
| --- | --- | --- | --- |

Responses:

- 201 Created: The task, queued; or, for a retry with the same `Idempotency-Key`, the first reply.
- 409 Conflict: A request with this `Idempotency-Key` is still being processed (`idempotency_key_in_use`); retry after `Retry-After` for its reply.
- 429 Too Many Requests: The queue share is full (`queue_full`), or a budget for creating the service has set is spent (`rate_limited`). Nothing was created or charged; retry after `Retry-After`.
- Any other status: An error, as RFC 9457 problem details.

```bash
curl -X POST https://api.zerocaptcha.io/v1/tasks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: order-4521-attempt-1" \
  -H "Content-Type: application/json" \
  -d '{
  "type": "TurnstileTaskProxyless",
  "websiteKey": "0x4AAAAAAAB1cD2eF3gH4iJ5",
  "websiteURL": "https://example.com/login"
}'
```

```js
const response = await fetch("https://api.zerocaptcha.io/v1/tasks", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Idempotency-Key": "order-4521-attempt-1",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "type": "TurnstileTaskProxyless",
    "websiteKey": "0x4AAAAAAAB1cD2eF3gH4iJ5",
    "websiteURL": "https://example.com/login"
  }),
});
console.log(response.status, await response.text());
```

```python
import requests

response = requests.post(
    "https://api.zerocaptcha.io/v1/tasks",
    headers={"Authorization": "Bearer YOUR_API_KEY", "Idempotency-Key": "order-4521-attempt-1"},
    json={
        "type": "TurnstileTaskProxyless",
        "websiteKey": "0x4AAAAAAAB1cD2eF3gH4iJ5",
        "websiteURL": "https://example.com/login",
    },
    timeout=30,
)
print(response.status_code, response.text)
```

## Live task updates

`GET /v1/tasks/events` (`streamTaskEvents`)

Server-sent events for the caller's tasks: `task` carries a task (without
its token) each time it changes, `reset` asks the client to refetch its
list because updates were missed. Start from a list's `liveCursor`.
Opening a stream draws on the caller's budget for reads, as a list read
does; keeping it open costs nothing more. The stream ends when its key or
session no longer allows it, or when its client stops taking events; an
account may hold only a few streams open at once.

| Parameter | In | Required | What it is |
| --- | --- | --- | --- |
| `since` | query | no | Where to start: `liveCursor` from a task list, or the last event's ID. |
| `Last-Event-ID` | header | no | The last event's ID, sent by a reconnecting browser. |

Responses:

- 200 OK: An event stream.
- 429 Too Many Requests: Over a budget for reading, or as many streams open as the account, or this server, may hold (`rate_limited`); reconnect less often, or close a stream, and retry after `Retry-After`.
- Any other status: An error, as RFC 9457 problem details.

```bash
curl -N https://api.zerocaptcha.io/v1/tasks/events \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
const response = await fetch("https://api.zerocaptcha.io/v1/tasks/events", {
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
  },
});
// Events arrive as they happen; stop with Ctrl+C.
const decoder = new TextDecoder();
for await (const chunk of response.body) {
  process.stdout.write(decoder.decode(chunk, { stream: true }));
}
```

```python
import requests

# Events arrive as they happen; stop with Ctrl+C.
with requests.get(
    "https://api.zerocaptcha.io/v1/tasks/events",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    stream=True,
    timeout=(10, None),
) as response:
    for line in response.iter_lines(decode_unicode=True):
        print(line)
```

## Get a task

`GET /v1/tasks/{id}` (`getTask`)

The task's status, cost and, while it is valid, its token: for a
challenge page, its clearance cookie and the user agent it is bound to.
A task that named a callback URL shows it with where its delivery stands
and each attempt: when, the HTTP status, and when the next is due.

| Parameter | In | Required | What it is |
| --- | --- | --- | --- |
| `id` | path | yes | The task's ID. |

Responses:

- 200 OK: The task.
- 429 Too Many Requests: Over a budget for reading (`rate_limited`); retry after `Retry-After`, and poll less often.
- Any other status: An error, as RFC 9457 problem details.

```bash
curl https://api.zerocaptcha.io/v1/tasks/0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
const response = await fetch("https://api.zerocaptcha.io/v1/tasks/0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b", {
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
  },
});
console.log(response.status, await response.text());
```

```python
import requests

response = requests.get(
    "https://api.zerocaptcha.io/v1/tasks/0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    timeout=30,
)
print(response.status_code, response.text)
```

## Send a task's callback again

`POST /v1/tasks/{id}/callback/resend` (`resendTaskCallback`)

Calls the task's callback URL again, as soon as the worker gets to it,
with eight more attempts if it fails: once the callback was delivered or
given up, or after its record was deleted. It is signed and shaped as
the first call was, and carries the same `ZeroCaptcha-Delivery` ID while
its record lasts. A key needs the `tasks:write` scope; a session must be
an owner's (`role_required`), with the CSRF token. A suspended account
sends none (`account_suspended`).

| Parameter | In | Required | What it is |
| --- | --- | --- | --- |
| `id` | path | yes | The task's ID. |

Responses:

- 202 Accepted: Queued: the callback as it stands now.
- 403 Forbidden: A member's session, not an owner's (`role_required`), a key without `tasks:write` (`insufficient_scope`), or a suspended account (`account_suspended`).
- 404 Not Found: No task of this account has this ID, or the task names no callback URL (`not_found`).
- 409 Conflict: The task has not ended yet, or its callback is still being delivered (`state_conflict`).
- Any other status: An error, as RFC 9457 problem details.

```bash
curl -X POST https://api.zerocaptcha.io/v1/tasks/0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b/callback/resend \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
const response = await fetch("https://api.zerocaptcha.io/v1/tasks/0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b/callback/resend", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
  },
});
console.log(response.status, await response.text());
```

```python
import requests

response = requests.post(
    "https://api.zerocaptcha.io/v1/tasks/0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b/callback/resend",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    timeout=30,
)
print(response.status_code, response.text)
```

## Report a task's token

`POST /v1/tasks/{id}/report` (`reportTask`)

Says whether the site accepted a solved task's token: `bad` when it
refused it, `good` when it worked. The report is recorded for our staff,
who watch the solvers' quality with it; tasks are final, so it refunds
nothing. One report per task, and only on a task that succeeded. Needs
the `tasks:write` scope; with a session, the CSRF token.

| Parameter | In | Required | What it is |
| --- | --- | --- | --- |
| `id` | path | yes | The task's ID. |

Body fields:

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `verdict` | Verdict | yes | `bad` when the site refused the token, `good` when it accepted it. |

Responses:

- 201 Created: Recorded.
- 404 Not Found: No task of this account has this ID (`not_found`).
- 409 Conflict: The task did not succeed, so there is no token to report on, or it has a report already (`state_conflict`).
- Any other status: An error, as RFC 9457 problem details.

```bash
curl -X POST https://api.zerocaptcha.io/v1/tasks/0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b/report \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "verdict": "bad"
}'
```

```js
const response = await fetch("https://api.zerocaptcha.io/v1/tasks/0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b/report", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "verdict": "bad"
  }),
});
console.log(response.status, await response.text());
```

```python
import requests

response = requests.post(
    "https://api.zerocaptcha.io/v1/tasks/0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b/report",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "verdict": "bad",
    },
    timeout=30,
)
print(response.status_code, response.text)
```
