↑ ↓ to move ↵ to open

Long-running calls

Submit, wait, check back, and stop at a deadline you choose.

Most calls finish within the 30-second wait. Some take longer: a crawl of many pages, a large search. This guide is a complete loop that sends a call, waits, checks back and stops at a deadline you choose, in Python and Node.js.

The loop

Both versions do the same five things:

  1. Send the call with Prefer: wait=30, so a fast one finishes right away.

  2. Send an Idempotency-Key, so that if the request has to be sent again, it returns the same job.

  3. If the job is still running, wait poll_after_ms, then fetch poll_url.

  4. Wait twice as long each time, up to 10 seconds between checks.

  5. Stop at your deadline. The job keeps running, and you can fetch it later with its job_id.

import osimport timeimport uuidimport requestsAPI = "https://data.upscrape.com"HEADERS = {"Authorization": f"Bearer {os.environ['UPSCRAPE_API_KEY']}"}def run(capability, input, deadline_seconds=300):    # One key per piece of work: a retry after a lost answer gets the same job.    key = str(uuid.uuid4())    response = requests.post(        f"{API}/execute",        headers={**HEADERS, "Prefer": "wait=30", "Idempotency-Key": key},        json={"capability": capability, "input": input},    )    job = response.json()    if "error" in job and "job_id" not in job:        raise RuntimeError(job["error"]["code"])    deadline = time.monotonic() + deadline_seconds    delay = job.get("poll_after_ms", 2000) / 1000    while job["state"] not in ("completed", "failed"):        if time.monotonic() > deadline:            raise TimeoutError(f"job {job['job_id']} is still {job['state']}")        time.sleep(delay)        delay = min(delay * 2, 10)        job = requests.get(job["poll_url"], headers=HEADERS).json()    if job["state"] == "failed":        raise RuntimeError(job["error"]["code"])    return job["results"][0]["data"]data = run("web.crawl", {"url": "https://docs.stripe.com/payments", "max_pages": 10})print(len(data["pages"]), "pages")

Both print 10 pages; we ran them on 16 October 2026.

Make it survive a restart

In a real app, a process can stop while it waits. Store the job_id with your own record as soon as you have it. After a restart, fetch the stored job instead of sending the call again: a second call would start, and pay for, a second job.

If you don't know whether the first request arrived, send it again with the same Idempotency-Key. That returns the first job, whatever state it is in.

Many calls at once

To run a batch, send every call without Prefer, so each answers 202 at once, and store the job_ids. Then check on them together. Your account runs a set number of calls at once, and the rest wait in line; Credits and limits gives the numbers.

Over MCP

An AI agent's calls work the same way: a slow call hands back a job_id, and the agent fetches it with upscrape_get_job_result. MCP jobs and results covers large results too.

Something missing or unclear? Ask the team that builds the endpoints.