Skip to main content
The whole Auto Apply integration is one loop with two blocking calls:
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:
Applications that cannot be worked (login walls, CAPTCHAs, unsupported fields) come back from the same call as status: "failed" with a failure code. 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 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:
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:
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:

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:
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 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.