> ## Documentation Index
> Fetch the complete documentation index at: https://jobo.world/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# The application loop

> Blocking create, synchronous answers, correction rounds, and recovery.

The whole Auto Apply integration is one loop with two blocking calls:

```text theme={null}
create ──▶ fields ──▶ answers ──▶ fields ──▶ answers ──▶ … ──▶ submitted
              ▲                      │                            failed
              └── correction round ──┘                            canceled
```

There is nothing else to build. No webhook endpoint, no HMAC verification, no
public HTTPS tunnel for local development, no event dedupe, no polling loop.
`curl` on localhost can drive an entire application.

## 1. Create blocks until the fields arrive

`POST /api/auto-apply/applications` holds the connection while Jobo validates
the target, acquires a browser, opens the form, and extracts its fields —
typically 10 seconds to 3 minutes. The response is the application with
`status: "awaiting_answers"` and the fields to answer in `current_step`:

```json theme={null}
{
  "id": "6f9227f6-b704-4b58-a553-79773a041933",
  "status": "awaiting_answers",
  "current_step": {
    "id": "9a2f…",
    "sequence": 1,
    "correction_round": 0,
    "answers_expire_at": "2026-08-12T10:03:07Z",
    "fields": [
      { "field_id": "first_name", "type": "text", "label": "First name", "requires_answer": true }
    ]
  }
}
```

Applications that cannot be worked (login walls, CAPTCHAs, unsupported
fields) come back from the same call as `status: "failed"` with a
[failure code](/docs/api-reference/auto-apply/schema#failure-codes).

Keep your HTTP client timeout **above 540 seconds**: the server holds a
blocking call for up to \~9 minutes before answering `202` with a snapshot
(see [Recovery](#4-recovery) for what to do then).

## 2. Answers are validated synchronously — mistakes are free

`POST /api/auto-apply/applications/{id}/answers` takes the complete answer
snapshot for the current step:

```json theme={null}
{
  "answers": [
    { "field_id": "first_name", "value": "Ada" },
    { "field_id": "email", "value": "ada@example.com" }
  ],
  "correction_round": 0
}
```

Every answer is validated against the step's typed fields **before anything
touches the employer's form**. A wrong type, an unknown option, a missing
required field — each returns `400 validation_failed` immediately, with
per-field detail in `errors`:

```json theme={null}
{
  "code": "validation_failed",
  "errors": [
    { "field_id": "email", "code": "invalid_type", "message": "Expected a string." }
  ]
}
```

Nothing is consumed by a validation failure. Fix the listed answers and submit
again. (`correction_round` in the request is an optional guard: when present
and stale, the API refuses with `409 stale_correction_round` rather than
answering the wrong round.)

Once the answers are accepted, Jobo fills the page and advances the form —
intermediate pages **auto-continue**, the final page **auto-submits**; the
browser agent decides which from the page itself. The call then blocks until
one of three outcomes:

| Outcome             | What the response looks like                                                     |
| ------------------- | -------------------------------------------------------------------------------- |
| Next page of fields | `awaiting_answers`, `current_step.sequence` incremented                          |
| Correction round    | `awaiting_answers`, same step, `correction_round` +1, `command_errors` populated |
| Done                | `submitted`, or `failed` with a failure code                                     |

## 3. Correction rounds

Client-side validation cannot predict everything the employer's ATS enforces.
When the ATS rejects a value after filling, the same step comes back with
`correction_round` incremented and `command_errors` describing what was
rejected:

```json theme={null}
{
  "status": "awaiting_answers",
  "current_step": {
    "sequence": 1,
    "correction_round": 1,
    "command_errors": [
      { "field_id": "salary", "code": "ats_validation_error", "message": "Enter a number below 1,000,000." }
    ],
    "fields": [ … ]
  }
}
```

Answer it exactly like any other step. After **3** failed rounds the
application fails with `correction_limit_exceeded`.

## 4. Deadlines

While a step is awaiting answers, a real browser is holding the employer's
form open. Each answerable step carries `answers_expire_at` — about **3
minutes**, capped at **60 seconds** for one-time verification-code steps.
Past the deadline the application fails with `answers_timeout`
(`verification_timeout` for code steps). Answer promptly; if your answer
source is slow, compute answers before creating the application.

## 5. Recovery

Every blocking call can lose its connection — a deploy, a proxy timeout, a
crashed process. Recovery is built into the contract:

* **Create dropped?** Repeat the same `POST` with the **same
  `Idempotency-Key`**. The replay re-attaches to the in-flight application and
  resumes blocking on the same wait. (A different body with a reused key is
  `409 idempotency_key_reuse`.)
* **Answers dropped?** Repeat the same `POST`. Answers are accepted exactly
  once per (step, correction round); a duplicate post attaches to the
  in-flight wait and its payload is ignored.
* **Got a `202`?** The server's hold budget (\~9 minutes) expired while the
  application was still working. The body is a snapshot; continue with
  `GET /api/auto-apply/applications/{id}?wait_seconds=540`, which long-polls
  until the application is answerable or terminal.

The [TypeScript SDK](https://www.npmjs.com/package/@jobo-ai/autoapply) does
all three automatically.

## 6. Local development

Run your integration anywhere — localhost, CI, a notebook. Nothing about the
loop requires inbound connectivity.

The one exception: `file` field answers (resumes) are supplied as a **public
HTTPS URL** that Jobo downloads. Host the file anywhere Jobo can reach — object
storage with a signed URL works well. A localhost URL is rejected with
`unsafe_file_url`.

## 7. Cancel

`POST /api/auto-apply/applications/{id}/cancel` requests cancellation at any
point. Queued applications cancel immediately; executing ones stop at the next
safe checkpoint. A submission the ATS already confirmed wins the race — the
application ends `submitted`, with `cancel_requested: true` recording the
attempt.
