## ZeroCaptcha

ZeroCaptcha solves Cloudflare Turnstile widgets and Cloudflare WAF and 5-second challenge pages (the "Just a moment..." screen) over HTTP. Every task is real and charged from a prepaid balance if it succeeds; failed and expired tasks cost nothing. The full brief, with tested reference clients in Node, Python, Go and bash: https://zerocaptcha.io/ai/integration.md. Machine-readable: https://zerocaptcha.io/ai/zerocaptcha.json. Contract: https://zerocaptcha.io/openapi.json.

#### Configuration

- Base URL from `ZEROCAPTCHA_API`, default `https://api.zerocaptcha.io`.
- API key (`zc_live_…`, 41 characters) from `ZEROCAPTCHA_KEY` or the secret store. Never hard-code, log, commit or send it to a browser; never ask for it in chat.

#### Calls (REST v1, `Authorization: Bearer <key>`)

- `GET /v1/tasks` (listTasks): List tasks. Auth: Authorization: Bearer <key>.
- `POST /v1/tasks` (createTask): Create a task. Auth: Authorization: Bearer <key>.
- `GET /v1/tasks/events` (streamTaskEvents): Live task updates. Auth: Authorization: Bearer <key>.
- `GET /v1/tasks/{id}` (getTask): Get a task. Auth: Authorization: Bearer <key>.
- `POST /v1/tasks/{id}/callback/resend` (resendTaskCallback): Send a task's callback again. Auth: Authorization: Bearer <key>.
- `POST /v1/tasks/{id}/report` (reportTask): Report a task's token. Auth: Authorization: Bearer <key>.
- `GET /v1/balance` (getBalance): Get the balance. Auth: Authorization: Bearer <key>.

#### Task types

- `TurnstileTaskProxyless`: needs `websiteKey`, `websiteURL`; optional `action`, `callbackUrl`, `cdata`.
- `TurnstileTask`: needs `proxy`, `websiteKey`, `websiteURL`; optional `action`, `callbackUrl`, `cdata`.
- `CloudflareChallengeTask`: needs `proxy`, `websiteURL`; optional `callbackUrl`.

#### Flow

1. `POST /v1/tasks` with a new `Idempotency-Key` per task (reuse it only to retry that create), and the widget's `websiteKey`, plus its `action` and `cdata` whenever it sets them (`data-action` and `data-cdata`, or `turnstile.render()`'s `action` and `cData`): many sites refuse a token solved without them. Reply: `201` with the task's `id`.
2. `GET /v1/tasks/{id}` every 2 s until `status` is `succeeded`, `failed` or `expired`; stop at the task's `deadline` (150 s by default) plus a margin. Or pass `callbackUrl` and verify `ZeroCaptcha-Signature` (HMAC-SHA256 of `<t>.<raw body>`, 300 s tolerance).
3. `succeeded`: use `solution.token` at once (a Turnstile token works once, for 300 s) in the form's `cf-turnstile-response` field or the widget's callback. A challenge page gives `solution.cookie` (`cf_clearance`) and `solution.userAgent`: send both, through the task's proxy.
4. `failed` / `expired`: report `errorCode`; nothing was charged. Create a new task only if a token is still needed.

#### Errors

REST errors are RFC 9457 `application/problem+json`: branch on `code`, quote `request_id`.
- Retry with backoff (same Idempotency-Key for creates): `internal_error`, `service_unavailable`, `request_timeout`, `payments_unavailable`, `ERROR_SERVICE_UNAVAILABLE`.
- Wait for `Retry-After`, then retry: `idempotency_key_in_use`, `queue_full`, `rate_limited`, `ERROR_NO_SLOT_AVAILABLE`, `ERROR_RATE_LIMIT`, `ERROR_IDEMPOTENCY_KEY_IN_USE`.
- Fix first, never retry as is: `bad_request`, `not_found`, `method_not_allowed`, `payload_too_large`, `unauthorized`, `invalid_credentials`, `key_revoked`, `key_limit_reached`, `key_state_conflict`, `csrf_rejected`, `insufficient_scope`, `ip_not_allowed`, `insufficient_funds`, `validation_failed`, `idempotency_key_reused`, `reauthentication_required`, `link_invalid`, `link_expired`, `link_used`, `weak_password`, `state_conflict`, `role_required`, `spend_cap_reached`, `email_taken`, `email_unverified`, `ERROR_TASK_ABSENT`, `ERROR_TASK_NOT_SUPPORTED`, `ERROR_INVALID_TASK_DATA`, `ERROR_KEY_DOES_NOT_EXIST`, `ERROR_KEY_REVOKED`, `ERROR_IP_NOT_ALLOWED`, `ERROR_ACCESS_DENIED`, `ERROR_ZERO_BALANCE`, `ERROR_IDEMPOTENCY_KEY_REUSED`, `ERROR_NO_SUCH_CAPCHA_ID`, `ERROR_REPORT_NOT_RECORDED`, `ERROR_DUPLICATE_REPORT`, `ERROR_INVALID_REQUEST`, `ERROR_SPEND_CAP_REACHED`, `ERROR_PROXY_NOT_ALLOWED`.
- Create a new task if still needed: `ERROR_TOKEN_EXPIRED`, `ERROR_CAPTCHA_UNSOLVABLE`, `ERROR_TASK_TIMEOUT`.
- Stop and tell the user: `account_suspended`, `domain_blocked`, `ERROR_ACCOUNT_SUSPENDED`, `ERROR_DOMAIN_BLOCKED`, `ERROR_ACCOUNT_DELETED`.
- Retry only HTTP 429, 500, 502, 503, 504, lost connections and `idempotency_key_in_use`: wait `Retry-After`, else 1 s doubling to 16 s with jitter; 15 s per request.

#### Before you say you are done

- [ ] The base URL is read from `ZEROCAPTCHA_API`, defaulting to `https://api.zerocaptcha.io`; no other host is called.
- [ ] The API key is read from `ZEROCAPTCHA_KEY` (or the project's secret store), never written in source, logs, error messages, URLs or anything sent to a browser.
- [ ] Every create sends an `Idempotency-Key` made once per task and reused only when retrying that same create.
- [ ] Results are read every 2 seconds, or received by a signed callback, and the wait stops at a deadline (the task's own `deadline`, 150 s by default, plus a margin).
- [ ] Both the HTTP status and the body are checked on every reply; a non-JSON reply is an error, not a result.
- [ ] 429, 5xx and `idempotency_key_in_use` are retried after `Retry-After`, else with exponential backoff from 1 s to 16 s with jitter; nothing else is retried as is.
- [ ] `failed` and `expired` tasks are reported with their `errorCode` and never retried in a loop; a new task is created only when the caller still needs a token.
- [ ] A Turnstile token is used at once: it works once, for 300 seconds.
- [ ] A Turnstile task sends the widget's action and cData whenever the widget sets them, exactly as it sets them (`data-action` and `data-cdata`, or the `action` and `cData` options of `turnstile.render()`): `action` and `cdata` on REST, `metadata.action` and `metadata.cdata` in the createTask format, `action` and `data` in 2Captcha's. Many sites refuse a token solved without them.
- [ ] A challenge page's `cf_clearance` cookie is sent with `solution.userAgent` as the User-Agent, through the same proxy the task used.
- [ ] Callbacks, if used, are checked with the HMAC-SHA256 signature over the raw body, in constant time, refusing timestamps more than 300 seconds away, and each task is handled once (by `ZeroCaptcha-Delivery` or task ID).
- [ ] Money is handled as the decimal strings the API sends (`price`, `cost`, `available`), never as floats.
- [ ] Tests run against a stand-in for the API, not the real one: every real task is charged.
