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.
// MCP tool call — server "twin-browser"
{
"tool": "get_job",
"arguments": {
"id": "…"
}
}curl -X GET "https://twin-browser.com/api/v1/jobs/JOB_ID" \
-H "Authorization: Bearer $TWIN_API_KEY"| Parameter | Type | What it does |
|---|---|---|
| id* | string | The job id returned by submit_run, submit_crawl, deep_search or record_job. |
Returns
{
"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 cardWhich one
When a different tool is the right call.
The honest answer is often the neighbouring tool. These are the trades.
cancel_jobIf 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_jobQuestions
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.
Keep going
The rest of the tool set.
submit_runRun something that will outlive your request timeout, or run twenty things at once.
cancel_jobChange your mind about a job that is still running, without paying for it.
submit_crawlRead a defined slice of a site, once, without either missing it or over-reading it.
deep_searchDo the research pass an agent would otherwise fake from search snippets.
record_jobCapture a flow that is too long to sit and wait for.