Async jobs

Poll an async job for its status and result

The single read that covers every async path — background runs, deep searches and crawls all land here — and the point at which credits settle.

get_job

What it is

The job: Find out whether the thing you started has finished, and get what it produced.

Every asynchronous entry point on the platform — submit_run, submit_crawl, deep_search, record_job — hands back a job id, and get_job is the one call that reads them all. It returns the job envelope: status, success, steps, credits charged, timestamps, error, and the result payload once there is one.

It is also load-bearing for billing. A job that is still running with a live engine handle is settled here on the first terminal poll — complete charges, failed or cancelled refunds. That is why polling a finished job is free and idempotent: the settle happens once and every later read returns the same numbers.

A paused job is not an error. A sign-in job that hit a 2FA wall reports status "paused", and the parked session id comes back on the job so you can continue it with submit_verification or drive it with control_run.

The call

Call it exactly like this.

Copied from the tool's registration and the route handler — not paraphrased.

MCPget_job.mcp.jsonjson
// MCP tool call — server "twin-browser"
{
  "tool": "get_job",
  "arguments": {
    "id": "…"
  }
}
GET /api/v1/jobs/{id}request.shbash
curl -X GET "https://twin-browser.com/api/v1/jobs/JOB_ID" \
  -H "Authorization: Bearer $TWIN_API_KEY"
Parameters accepted by get_job
ParameterTypeWhat it does
id*stringThe job id returned by submit_run, submit_crawl, deep_search or record_job.

Returns

response.jsonjson
{
  "jobId": "…",
  "status": "complete",         // running | complete | failed | cancelled | paused
  "target": "https://app.example.com",
  "success": true,
  "steps": 7,
  "credits_charged": 10,
  "llm_tokens": 8134,
  "llm_cost_micro": 41200,
  "error": null,
  "created_at": "…",
  "finished_at": "…",
  "result": { /* the job's payload — a run result, a crawl's pages, a deep search */ }
}

What it costs

Free

Polling costs nothing. What it does is SETTLE the parent job’s reservation on the first terminal read — the charge belongs to the job, never to the poll, so a tight polling loop cannot cost you anything.

See the full rate card

Which one

When a different tool is the right call.

The honest answer is often the neighbouring tool. These are the trades.

cancel_job

If the answer to “is it done?” is “no, and I no longer want it”, cancel_job stops it and refunds the reservation in full.

cancel_job

Questions

get_job, answered.

Is there a rate limit on polling?
Poll at a sane interval — a couple of seconds is plenty, since a browser run does not change state faster than that. If you want to avoid the loop entirely, stream the job at GET /api/v1/jobs/{id}/stream or give the job a callbackUrl at submit time.
The result came back null. Why?
A result is only fetched once the job is terminal, and the engine — not the web app — holds the payload. If the engine has rolled since the job finished, the envelope is still correct but the blob may be gone. Persist what matters via a completion webhook if you need it durably.