# Codes

Job statuses, attention reasons, error codes with their HTTP status, warnings, events and the other closed sets.

Every closed set of values the API returns, generated from `packages/contracts/src/codes.ts`. A client may switch on these values: a new one is added in a new API version and announced in the [changelog](https://docs.kaab.ist/changelog).

## Job statuses

A job's `status`. `succeeded`, `failed` and `canceled` are terminal; no further transition happens.

| Status | Terminal | Meaning |
|---|---|---|
| `queued` |  | The job was accepted and is waiting for a worker. A queued job the dispatcher is holding back carries `meta.blocked_reason`: `insufficient_credits` or `session_locked`. |
| `running` |  | A worker is executing the job's program. A parked job returns to `running` when it is resumed or receives its input. |
| `awaiting_input` |  | The job reached an `await_input` step and is paused until a value arrives through `POST /jobs/{id}/input` or the hosted resolve page. `input_request` names the input and its deadline. |
| `needs_attention` |  | The job hit something it could not resolve on its own and is parked with a checkpoint until somebody decides. `attention` carries the reason, evidence, proposals and the resume actions available. |
| `succeeded` | yes | The job finished and its result can be read: `data`, `raw`, `warnings` and `meta.steps`. This status is terminal. |
| `failed` | yes | The job ended without a complete result; `error` says why. This status is terminal. |
| `canceled` | yes | The job was canceled, or aborted from a parked state, before it finished. This status is terminal. |

## Attention reasons

Why a job is `needs_attention` (`attention.reason`). See [human in the loop](https://docs.kaab.ist/concepts/human-in-the-loop).

| Value | Meaning |
|---|---|
| `obstacle` | Something covers the page, such as a dialog, a consent banner or a tour, and Kaabist could not dismiss it. The attention carries dismiss proposals you can apply, optionally remembering them for the site. |
| `drift` | A target moved and the healer's best match scored below the 0.90 confidence threshold. Review the relocation proposal, apply it to this job, and promote it to a new program version if it is right. |
| `unknown_page` | The job landed on a page it does not recognise, or a target or expectation failed on the step's own page. The attention's `detail` names the selector when there is one. |
| `session_expired` | The session the job uses stopped working and could not be refreshed automatically. Sign in again, through takeover or the session's refresh program, and resume the job. |
| `side_effect_ambiguous` | The job was interrupted inside a step marked `side_effect: true`, so Kaabist cannot tell whether the action happened. It will not repeat the step; check the target system, then skip the step or abort. |
| `captcha` | The page shows a captcha and the program's `policies.captcha` is `park`, or a solver could not finish it. A person can solve it through takeover or the hosted resolve page. |

## Errors

The `code` of the error envelope, on an API answer and on a failed job's `error`. **Retryable** means the same request or job can succeed later without a change on your side.

| Code | HTTP | Retryable | Meaning |
|---|---|---|---|
| `blocked` | 422 | yes | The site refused Kaabist, for example an anti-bot challenge the allowed tiers could not pass, or a captcha under `policies.captcha: fail`. Running the job again later, or allowing a higher `max_tier`, may get through. |
| `timeout` | 422 | yes | A step outlived its own budget, the job ran past `max_duration_ms`, or a job parked in `needs_attention` was never resolved before its deadline. Check `error.step` and the snapshot to see where it stopped. |
| `session_expired` | 422 | yes | The job's session was no longer signed in, and there was no refresh program to restore it (or the program does not park on an expired session). Refresh the session and run the job again. |
| `validation_failed` | 422 | no | Extracted data broke the program's constraints under `on_invalid: fail` or `policies.strict: true`, or a request named a value that is well formed but not acceptable here. The message and `details` name what did not hold. |
| `input_expired` | 410 | no | The `await_input` deadline passed before a value arrived, so the job stopped waiting. Posting the input afterwards returns this code; start a new job to try again. |
| `side_effect_ambiguous` | 422 | no | The job could not establish whether a step marked `side_effect: true` completed, and ended rather than risk doing it twice. Check the target system before you run the job again. |
| `insufficient_credits` | 402 | no | The organisation has no credits available for a new job. Buy credits or enable auto-recharge; jobs already queued wait instead of failing. |
| `max_credits_exceeded` | 422 | no | Continuing would have spent more than the job's `max_credits` ceiling, so it stopped. Raise `max_credits` if the job legitimately needs more. |
| `max_tier_exceeded` | 422 | no | The page needs a tier above the job's `max_tier`, so the job failed instead of escalating. Raise `max_tier` to let it use the heavier tier. |
| `max_steps_exceeded` | 422 | no | The job reached its `max_steps` limit (200 by default and at most). Look for a loop or pagination that runs longer than intended. |
| `ssrf_blocked` | 422 | no | A URL resolves to a private, loopback, link-local or metadata address, which Kaabist never connects to. This applies to job targets, webhook endpoints and BYO provider hosts, and is not configurable. |
| `target_disallowed` | 422 | no | The target hostname is on Kaabist's blocklist or on your organisation's own blocklist, so no connection was made. |
| `byo_proxy_unreachable` | 422 | no | Your own proxy failed and the job's proxy `fallback` is `none`, so the job failed rather than use an exit you did not choose. `details.byo_id` names the proxy; fix it, or set `fallback: platform`. |
| `byo_proxy_blocked` | 422 | yes | The target refused traffic that went through your own proxy. Try again later, or route the job through a different proxy. |
| `byo_llm_auth_failed` | 422 | no | Your own model provider refused its API key. It never fails a job: it appears in a proposal's evidence. Update the key on the BYO provider. |
| `byo_llm_rate_limited` | 422 | yes | Your own model provider rate-limited Kaabist's request. It appears in a proposal's evidence and does not fail the job. |
| `byo_llm_quota_exceeded` | 422 | no | Your own model provider's account has run out of quota. It appears in a proposal's evidence and does not fail the job; top up the account with the vendor. |
| `byo_captcha_auth_failed` | 422 | no | Your own captcha solver refused its API key, so a job with `policies.captcha: solve` failed. Update the key on the BYO provider. |
| `byo_captcha_balance` | 422 | no | Your own captcha solver account has no balance left, so a job with `policies.captcha: solve` failed. Top up the account with the vendor. |
| `byo_provider_failing` | 422 | no | The job names a BYO provider whose health checks are failing, so it was refused. `details.byo_id` and `details.path` say which; fix the provider or choose another. |
| `byo_in_use` | 409 | no | The BYO provider cannot be deleted while an organisation default or an active schedule still names it. `details.org_defaults` and `details.schedules` list them. |
| `program_invalid` | 422 | no | The program failed lint. `details.errors[]` lists what blocks it and `details.warnings[]` what is advisory, with lint codes from the DSL reference. |
| `request_invalid` | 400 | no | The request body, query or headers did not match the schema. `details.issues[]` lists each problem as `{path, message}`. |
| `idempotency_conflict` | 409 | no | The `Idempotency-Key` was already used in the last 24 hours with a different request body. Use a new key for a different request. |
| `session_locked_timeout` | 422 | yes | The job waited for its session's exclusive lease and the job holding it was still running a minute later. Run it again once the other job finishes, or use `shared` concurrency for sites that tolerate it. |
| `session_locked` | 409 | yes | A job currently holds the session's lease, so the session cannot be changed or deleted now. Try again when that job has finished. |
| `session_engine_mismatch` | 409 | no | The job's session was made on one stealth browser (Chromium or Camoufox) and the job would run on the other. A site that ties its sign-in to the browser would refuse it, so the job is refused instead. Drop `options.engine`, or sign in again on the engine you want. |
| `job_not_waiting` | 409 | no | The action needs a parked job, but the job is not waiting for it, for example input posted to a job that is not `awaiting_input`, or a resolve page or live session that has ended. |
| `fence_open` | 409 | no | The job is inside a side-effect fence, so it cannot be canceled without risking a half-done action. Wait until the step completes. |
| `loop_exhausted` | 422 | no | A `loop_until` reached its `max` iterations without its condition becoming true. Mark the step `optional` or raise `max` if that is expected. |
| `target_ambiguous` | 422 | no | A locator matched more than one element. Make the locator more specific or add `nth` to pick one; `details.matches` lists the candidates when available. |
| `canceled` | 422 | no | The job was canceled, or aborted from a parked state, before it finished. |
| `not_found` | 404 | no | The resource does not exist, or belongs to another organisation; the two are deliberately indistinguishable. Check the ID and the key's organisation. |
| `program_not_found` | 404 | no | The job's `program` reference named a program, version or alias that resolves to nothing. Check the name and the alias or version number. |
| `program_has_schedules` | 409 | no | The program still has schedules that point at it. Delete or move those schedules, then delete the program. |
| `secret_storage_disabled` | 422 | no | Secret inputs for a session's refresh program can be stored only when the organisation has enabled secret storage. Enable it, or collect the secret each time through `await_input`. |
| `not_implemented` | 501 | no | The operation is in the API reference but has not shipped yet. `details.owner` names the work that will deliver it. |
| `two_factor_required` | 403 | no | An owner or admin without two-factor authentication asked for a privileged action. Enrol a TOTP authenticator at `details.enrol_url`, then try again. |
| `reauth_required` | 403 | no | A key, organisation or payment-method change needs a password or two-factor code entered within the last 15 minutes. Confirm your identity in the panel and repeat the request. |
| `rate_limited` | 429 | yes | Too many requests in the current window. Wait for `Retry-After` (also `details.retry_after_seconds`); `details.scope` names the bucket (`key`, `org` or `ip`), and `details.reason: org_disabled` means an operator has disabled the organisation. |
| `payment_method_required` | 422 | no | The request needs a payment method on file: auto-recharge, the customer portal, or a job that uses the browser or stealth tier, `evaluate`, a BYO provider or a program that signs in. `details.rule` and `details.upgrade_url` say what to add when present. |
| `billing_unavailable` | 503 | yes | The billing provider is not configured on this deployment or did not answer, and nothing was charged. Try again later. |
| `test_quota_exceeded` | 429 | yes | The organisation's `sk_test_` keys have created their daily allowance of jobs. `details.limit`, `details.used` and `details.resets_at` describe the quota, which resets at midnight UTC. |
| `plan_required` | 403 | no | The organisation's plan does not include what the request needs, such as a tier above the plan's maximum, `evaluate`, BYO providers or a region. `details.rule` names it and `details.upgrade_url` links to a plan that does. |
| `plan_managed` | 409 | no | Kaabist manages this organisation's plan, so no subscription can be bought or changed through checkout. Credit packs can still be bought; contact support to change the plan. |
| `login_flow_rate_limited` | 429 | yes | Programs that fill a password field or a secret input run at most 60 times an hour per organisation. Wait for `Retry-After` before starting another. |
| `schedule_interval_too_small` | 422 | no | The schedule's cron fires more often than the plan allows. `details.min_interval_seconds`, `details.requested_seconds`, `details.plan` and `details.upgrade_url` show the limit and how to raise it. |
| `live_unavailable` | 503 | yes | No browser capacity accepted the live session within 10 seconds. Try again shortly. |
| `pdf_not_pdf` | 422 | no | A file served or downloaded as a PDF does not start like one: there is no `%PDF-` marker in its first 1024 bytes. Nothing was stored. |
| `pdf_too_large` | 422 | no | The PDF is larger than 64 MB, the limit for any downloaded file. Kaabist stops reading at the limit and stores nothing. |
| `pdf_malware` | 422 | no | Kaabist's malware scanner found a known signature in the PDF. The file was not stored, parsed or shown to any model. |
| `pdf_scanner_unavailable` | 503 | yes | Kaabist's malware scanner did not answer, and a PDF is never passed on unscanned. Nothing was stored; try again shortly. |
| `pdf_encrypted` | 422 | no | The PDF is encrypted or needs a password. Kaabist does not open protected documents. |
| `pdf_too_complex` | 422 | no | The PDF went past a structure limit (pages, objects, decompressed size, nesting) or the time and memory its parsing may use. Nothing was stored. |
| `pdf_malformed` | 422 | no | The PDF could not be parsed, even with the reader's recovery. Nothing was stored. |
| `pdf_active_content` | 422 | no | The PDF launches a program or embeds an executable file, which Kaabist never accepts. Scripts and open actions are only flagged; this code is for launch actions and executables. |
| `pdf_source_missing` | 422 | no | A `read_pdf` step had no PDF to read: without `artifact` the page it is on is not a PDF, and with `artifact` the job stored no artifact by that name. Put a `goto` to the PDF, or a `download` of it, before the step. |
| `internal` | 500 | yes | Something failed inside Kaabist. A job that fails this way has its credits refunded; if it persists, contact support with the `request_id`. |

## Warnings

Entries of `warnings[]` on a job, a step or a field. A warning never changes `status`. See [validation and warnings](https://docs.kaab.ist/concepts/validation-and-warnings).

| Value | Meaning |
|---|---|
| `null_marker` | The page printed a null marker such as `-`, an em dash character or `N/A` where the value should be, so the field is `null`. |
| `parse_failed` | The value was found but could not be parsed as the field's type. The field follows its `on_invalid` policy and `raw` keeps the original text. |
| `out_of_range` | The value parsed but broke a constraint, such as `min`, `max`, `pattern`, `values`, `max_items` or a cross-field assert. The field follows its `on_invalid` policy. |
| `required_missing` | A field marked `required` had no value because no locator matched. |
| `list_short` | A list returned fewer items than its `min_items`. |
| `fill_rate_drop` | Across the program version's recent runs, this field has stopped filling as often as it used to. `detail` describes the finding; a healing proposal is probably waiting in the review queue, even if this job's data looks fine. |
| `escalated` | The job moved to a heavier tier during the run. `meta.escalations` records each move and its reason. |
| `healed_locator_used` | A target had moved, and Kaabist used a relocated locator it matched with high confidence for the rest of this job. A proposal to keep the new locator was filed for review. |
| `obstacle_dismissed` | Kaabist dismissed something covering the page, such as a dialog or consent banner, and continued. `detail` says how when available. |
| `field_empty` | The field has no value: no locator matched, or the page printed a null marker. |
| `step_skipped` | A step marked `optional` failed and the job continued without it. `message` says why it failed. |
| `cache_hit` | The result was served from the shared cache (`meta.cache: hit`) instead of a fresh fetch. |
| `byo_fallback_used` | Your own proxy was unreachable and the job ran on Kaabist's exits because its proxy `fallback` is `platform`. That job's proxy traffic is billed at the platform rate. |
| `pdf_source_not_extractable` | The page was a PDF, so an HTML extract schema found nothing in it and its fields are `null`. Read PDFs with a `read_pdf` step, which returns their tables. |
| `pdf_no_text` | The PDF passed the security gate but has no extractable text, which usually means scanned pages. Kaabist does not run OCR. |
| `pdf_pages_truncated` | A `read_pdf` step stopped at its `max_pages`: it read `count` of the document's `of` pages, so tables on later pages are missing and the reading says `truncated: true`. |
| `redirect_not_followed` | A field with `follow_redirects` kept some of its links as the page printed them: the http tier cannot ask a link where it redirects, and one step resolves at most 50. `count` says how many were kept. |
| `pagination_incomplete` | A `paginate` step could not move on to the next page after reading some, so it kept the `count` pages it had read instead of starting over. `detail` names what failed, such as `timeout`. |
| `captcha_inject_navigated` | A captcha was solved and injecting its token navigated the page, which is how many sites accept the answer. The step was run again on the page it landed on. |

## Events

The `type` of a webhook delivery and of an event-log entry; [events](https://docs.kaab.ist/reference/events) shows each payload.

| Event | Meaning |
|---|---|
| [`job.queued`](https://docs.kaab.ist/reference/events#jobqueued) | A job was accepted, re-queued after a worker lost it, resumed, or escalated to a heavier tier. |
| [`job.blocked`](https://docs.kaab.ist/reference/events#jobblocked) | A queued job is being held back because the organisation is out of credits; `reason` is `insufficient_credits`. Sent once per blocked stretch. |
| [`job.started`](https://docs.kaab.ist/reference/events#jobstarted) | A worker began running the job, sent on its first run only. |
| [`job.awaiting_input`](https://docs.kaab.ist/reference/events#jobawaiting_input) | The job paused at an `await_input` step and needs a value. |
| [`job.needs_attention`](https://docs.kaab.ist/reference/events#jobneeds_attention) | The job paused and needs a decision; `reason` is the attention reason. |
| [`job.succeeded`](https://docs.kaab.ist/reference/events#jobsucceeded) | The job finished and its result can be read. |
| [`job.failed`](https://docs.kaab.ist/reference/events#jobfailed) | The job ended without a result; `reason` is the error code. |
| [`job.canceled`](https://docs.kaab.ist/reference/events#jobcanceled) | The job was canceled or aborted; `reason` is `canceled`. |
| [`batch.completed`](https://docs.kaab.ist/reference/events#batchcompleted) | Every job of a batch has finished. `total` is the number of jobs in it. |
| [`proposal.created`](https://docs.kaab.ist/reference/events#proposalcreated) | Kaabist proposed a fix for review, such as a relocated locator or a dismiss recipe. `kind` says which. |
| [`proposal.decided`](https://docs.kaab.ist/reference/events#proposaldecided) | Somebody approved or rejected a proposal; `decision` says which. |
| [`schedule.run`](https://docs.kaab.ist/reference/events#schedulerun) | A scheduled job ended with nothing to report: the result matched the previous run (`changed: false`), or the job did not succeed (`status`). |
| [`schedule.changed`](https://docs.kaab.ist/reference/events#schedulechanged) | A scheduled run's watched fields differ from the previous run's. `changed_paths` lists up to 50 JSON Pointers and `diff_url` links to the diff. |
| [`schedule.paused`](https://docs.kaab.ist/reference/events#schedulepaused) | A schedule was paused, by a person or because the organisation ran out of credits; `reason` is `manual` or `credits`. |
| [`schedule.failing`](https://docs.kaab.ist/reference/events#schedulefailing) | Five runs of a schedule in a row did not succeed. The schedule keeps running; `failure_streak` counts the failures. |
| [`session.expired`](https://docs.kaab.ist/reference/events#sessionexpired) | A stored session stopped working and needs a new sign-in. |
| [`program.drift_detected`](https://docs.kaab.ist/reference/events#programdrift_detected) | A field of a published program version stopped filling, parsing or keeping its length, or the version's canary failed, before your next job notices. Sent once per version, field and `kind` while the finding stays open. |
| [`credits.low`](https://docs.kaab.ist/reference/events#creditslow) | Allowance consumption crossed 80 % or 95 % this period; `level` says which. Sent once per level per period. |
| [`credits.exhausted`](https://docs.kaab.ist/reference/events#creditsexhausted) | Nothing more can be consumed: schedules and canaries pause and new jobs are refused. Sent once, until credits arrive. |
| [`credits.recharged`](https://docs.kaab.ist/reference/events#creditsrecharged) | Credits arrived after the organisation ran out, or an auto-recharge completed. Paused schedules and canaries resume. |
| [`byo.unhealthy`](https://docs.kaab.ist/reference/events#byounhealthy) | One of your own providers (proxy, model or captcha solver) started failing its health checks. |
| [`byo.recovered`](https://docs.kaab.ist/reference/events#byorecovered) | A BYO provider that was `failing` or `degraded` passed two health checks in a row and is healthy again. |
| [`webhook.disabled`](https://docs.kaab.ist/reference/events#webhookdisabled) | Kaabist switched one of your webhook endpoints off after its last 100 deliveries failed over at least a day. Fix the receiver and set the endpoint's `status` to `active` again. |
| [`ping`](https://docs.kaab.ist/reference/events#ping) | A test delivery you asked for. It goes only to that endpoint, and no event pattern selects it. |

## Diagnose classes

What Kaabist concluded a page was when a step could not proceed (`attention.bundle.diagnose_class`, `error.details.diagnose.class`).

| Value | Meaning |
|---|---|
| `empty_shell` | The document is an empty shell, such as a mount point and a script bundle, with no content yet. Kaabist escalates to a heavier tier. |
| `placeholder_content` | The elements are there but print skeleton values such as a lone dash or `Loading`. At the http tier Kaabist escalates once; if the browser shows the same, the values are treated as genuinely empty. |
| `content` | The page loaded correctly and the values are genuinely absent, so there is nothing to fix; the program's field policies decide the outcome. |
| `login_redirect` | The page shows sign-in landmarks, so the session has expired. Kaabist runs the session's refresh program if there is one. |
| `challenge_page` | An anti-bot interstitial, such as Cloudflare, DataDome, PerimeterX, Akamai or Imperva, stood in the way. Kaabist escalates toward `stealth`. |
| `captcha` | The page shows a captcha puzzle. The job follows `policies.captcha`: park for a person, hand it to a solver, or fail. |
| `landmarks_ok_target_missing` | The page is the expected one but a target no longer matches, which is drift. The healer looks for the moved element. |
| `landmarks_missing_unknown` | The page lacks the landmarks the program expects, so it is not the page the program was written for. The job parks as `unknown_page`. |
| `covered_target` | The target exists but something covers it, such as a dialog or overlay. The obstacle layer tries to dismiss it. |
| `transient` | A passing failure such as a timeout, a server error, a dropped connection or a proxy error. Kaabist retries with backoff within the job's budget. |
| `assets_blocked` | A page that loads its code at runtime timed out while asset blocking had refused some of its requests. Set `options.assets: allow`; retrying will not help. |
| `target_missing` | A target matched nothing usable on the step's own page, which did load. The evidence names the selector. |
| `expect_failed` | An `expect` or `wait_for` did not hold, or an `evaluate` threw, on the step's own page. The evidence says which. |

## Tiers

| Value | Meaning |
|---|---|
| `http` | One HTTP request and a parsed document, with no JavaScript. The cheapest tier, used when the page's HTML already contains what you asked for. |
| `browser` | A real Chromium that runs the page's JavaScript. Needed when the page renders its content or the program clicks, types or scrolls. |
| `stealth` | A browser with a harder fingerprint on a residential address, for sites that refuse the other tiers. The most expensive tier, and it needs a paid plan. |

## Modes

| Value | Meaning |
|---|---|
| `live` | Created with an `sk_live_` key: jobs run for real and consume credits. |
| `test` | Created with an `sk_test_` key: jobs run for real but reserve no credits, are capped by a daily quota, keep their results for 24 hours, and produce events tagged `mode: test`. |

## Key scopes

| Value | Meaning |
|---|---|
| `run` | Read everything, create and act on jobs, write and publish programs, update aliases, decide proposals, and manage sessions and schedules. |
| `read` | Read-only access to the organisation's resources. |
| `admin` | Everything a `run` key can do, plus API keys, webhooks, BYO providers, billing and organisation settings. No key can manage members or delete the organisation. |

## Roles

| Value | Meaning |
|---|---|
| `owner` | Full control of the organisation, including deleting it. Owners must enable two-factor authentication for privileged actions. |
| `admin` | Everything an owner can do except delete the organisation, including members, keys, billing and BYO providers. Admins must enable two-factor authentication for privileged actions. |
| `developer` | Builds and runs automations: programs, versions and aliases, jobs, proposals, sessions, schedules and webhooks. Can read everything except billing, and cannot manage keys, members or BYO providers. |
| `reviewer` | Handles jobs that need a person: decides proposals, takes over, resumes and cancels jobs. Cannot see API keys, billing or the audit log. |
| `viewer` | Read-only access to programs, jobs and the organisation's other resources, except API keys, billing and the audit log. |
