Skip to main content

Errors

Every failure carries a stable machine-readable code. Branch on the code, never on the message; messages are written for humans and will change. The TypeScript client raises one OperatorError subclass per class below. Only six codes are retryable, in the sense that waiting and re-sending the same request can succeed: rate_limited, quota_exceeded, device_busy, device_not_ready, provider_error, browser_unavailable. Everything else is a verdict.
A 404 means “no such row in your organization”. A row in another organization answers identically, so a 404 is never evidence that an id does not exist.

Idempotency

Exactly two routes accept an Idempotency-Key header: POST /v1/devices and POST /v1/devices/{id}/tasks. A create that times out and is retried with the same key replays the first result instead of provisioning a second browser or submitting a second task. Derive your key from the work, not from a clock; a generated key dies with the process that made it. provision:${orderId} survives a crash, ${Date.now()} does not. Reusing a key with a different body is 409 idempotency_conflict. No other route accepts the header.

Pagination

Lists are keyset-paginated with an opaque cursor.
  • next_cursor is server state. Do not parse it, construct one, or persist it across deploys; a tampered cursor is 400 invalid_cursor.
  • Page size defaults to 20 and is clamped to 100.
  • The TypeScript client exposes lists as async iterators (per item, per page, or collect(n)), so most code never touches a cursor.