CAPABILITY · JOBS

Work that outlives the request, with a signed callback

Submit a run as a background job and get an id immediately — then poll it, stream its status, or receive an HMAC-signed webhook when it completes.

POST /api/v1/jobs → 202 { jobId }

Capability

Inside async jobs & webhooks

A synchronous run holds the connection for its whole duration, which is right when your agent is waiting on the answer and wrong when the work is a ten-minute crawl. Jobs are the same execution with a different cadence: submit, get an id, and pick how you find out it finished — poll, stream, or a signed webhook you can verify.

Three ways to learn it finished

Poll GET /jobs/{id}, subscribe to GET /jobs/{id}/stream for status frames as the engine transitions, or set callbackUrl and receive a POST on completion.

Webhooks you can actually verify

With callbackSecret set, the delivery carries x-twin-signature: sha256=<hex HMAC of the raw body>, plus x-twin-event (job.completed | job.failed | job.cancelled) and x-twin-job-id. Delivery retries a couple of times and never blocks the job.

Cancellation is a full refund

POST /jobs/{id}/cancel best-effort aborts the in-flight work, flips the run to cancelled and refunds the reservation in full. Cancelling an already-terminal job is a no-op that returns its state.

Jobs park too

A sign-in job that hits a 2FA wall parks like a synchronous run: GET /jobs/{id} reports status "paused" with a sessionId, and POST /runs/{id}/resume continues it.

How it works

The mechanism, in execution order

4 stages, in the order the runtime performs them — not a summary of them.

SYNC · ASYNC · SCHEDULEDPOST /runRuns nowblocks until doneResult+ credits chargedPOST /jobsJob idreturned instantlyPoll or streamstatus · SSE framesPOST /monitorsOn a schedulechecks the pageSigned webhookHMAC, on changethe same run — driven now, in the background, or on a clock
  1. 1SubmitPOST /api/v1/jobs with a run body plus optional callbackUrl / callbackSecret. The response is 202 with a jobId.
  2. 2ReserveThe worst-case credit cost is reserved up front, so a job cannot start work it cannot pay for.
  3. 3ObservePoll, stream status frames, or wait for the callback. Billing settles once on the first terminal status.
  4. 4SettleComplete charges, failed and cancelled refund. A client that disconnected from the stream settles on its next poll.

In practice

A call you can paste and run

Verify the signature over the RAW body before you trust the payload — that is what callbackSecret is for.

jobs.shbash
curl -X POST https://twin-browser.com/api/v1/jobs \
  -H "Authorization: Bearer $TWIN_API_KEY" \
  -H "content-type: application/json" \
  -d '{"url":"https://portal.example.com/reports",
      "prompt":"download every statement for Q2",
      "success":{"kind":"extracted"},
      "callbackUrl":"https://your.app/hooks/twin",
      "callbackSecret":"whsec_REPLACE_ME"}'
# → 202 { "jobId": "job_…" }

# The callback your endpoint receives:
#   x-twin-event: job.completed
#   x-twin-job-id: job_…
#   x-twin-signature: sha256=<hex hmac of the raw body>
api.twin-browser.com
  1. Submitdone
  2. Reserverunning
  3. Observequeued
  4. Settlequeued

What it costs, how to switch it on

Priced from the same rate card the API serves

Every credit figure on this page is read from the rate card behind GET /api/v1/pricing — it is not typed into the copy, so it cannot drift from what you are billed.

10-credit floor per job

A job is priced like a run: higher-of(10-credit floor, metered model + compute + egress), reserved at submit and settled once on the first terminal status. Failed and cancelled jobs are refunded in full. A crawl submitted as a job is priced per page instead (3 credits each), and a deep search per scraped page.

Full rate card

Turning it on

  1. 1Swap /run for /jobsThe body is the same. What changes is that you get an id instead of a result.
  2. 2Add a callbackcallbackUrl plus callbackSecret turns polling into a push. Always set the secret — without it you cannot authenticate the delivery.
  3. 3Verify the signatureHMAC-SHA256 the RAW request body with your secret and compare, in constant time, against the hex after `sha256=`.
  4. 4Handle the paused statusA sign-in job can come back paused. Keep the sessionId and resume it the same way you would a synchronous run.

At a glance

The contract, in the fewest rows that say it

PropertyTwin Browser
SubmitPOST /api/v1/jobs → 202 { jobId }
PollGET /api/v1/jobs/{id}
StreamGET /api/v1/jobs/{id}/stream (SSE)
CancelPOST /api/v1/jobs/{id}/cancel (full refund)
Webhook headersx-twin-event · x-twin-job-id · x-twin-signature
Signaturesha256=<hex HMAC of the raw body>

Limits

Where it stops, and what it deliberately does not do

A capabilities page with no limits section is a brochure. These are the ceilings, the defaults that will surprise you, and the things this capability is not.

Limits and defaults

  • Callback delivery is best-effort with a couple of retries; it is fire-and-forget from the job's perspective and never blocks completion.
  • callbackSecret requires callbackUrl — a secret with nowhere to send is not a configuration.
  • Billing settles once, on the first terminal status. A disconnected stream client settles on its next poll.
  • Cancellation is best-effort against in-flight engine work: it stops the job and refunds, it does not undo side effects already performed on the target site.

What it does not do

  • It does not queue indefinitely for you. A job is a background execution, not a scheduler — for recurring work, use a monitor.
  • It does not sign a callback you did not give a secret for. Without callbackSecret there is no x-twin-signature header.
  • It does not deliver partial results. The callback fires on a terminal status.
  • It does not retry a failed run automatically. A failure is refunded and returned, not silently re-attempted.

FAQ

Questions about async jobs & webhooks

When should I use a job instead of a run?
When the work may outlive your request timeout, or when you want several executions in flight at once. The trade is that you now own a job id and a way of finding out it finished.
How do I verify the completion webhook?
Set callbackSecret when you submit. The delivery carries x-twin-signature: sha256=<hex>, computed as an HMAC-SHA256 of the RAW body with that secret. Recompute it over the raw bytes — not the re-serialized JSON — and compare in constant time.
What does cancelling cost?
Nothing. POST /jobs/{id}/cancel aborts the in-flight work best-effort, flips the run to cancelled and refunds the reservation in full. Cancelling an already-terminal job is a no-op.
Can a job pause for a human?
Yes. A sign-in job parks exactly like a synchronous run — GET /jobs/{id} reports status "paused" with a sessionId, and POST /api/v1/runs/{id}/resume continues it on the same parked browser.

Put your agent to work. Keep the decision.

Start free. Hand your agent a goal on a site you authorize, set the guardrails, and let the first successful run compile the skill every run after it replays.