ExtraltExtralt

Common patterns

The examples below use the Extract base URL. Use the generated OpenAPI document for route-specific statuses and schemas.

Polling a Run

Runs are asynchronous. After creating a Run, poll until it reaches a terminal state such as completed, failed, or stopped.

import time
import requests

def wait_for_run(run_id, *, timeout_seconds=3600):
    deadline = time.monotonic() + timeout_seconds

    while time.monotonic() < deadline:
        response = requests.get(
            f"https://api.extralt.com/v1/extract/runs/{run_id}",
            headers=HEADERS,
        )
        response.raise_for_status()
        run = response.json()

        if run["status"] in ("completed", "failed", "stopped"):
            return run

        time.sleep(10)

    raise TimeoutError(f"Run {run_id} did not finish before the client timeout")

Use a client-side timeout and a bounded polling interval. Completion time depends on source coverage, crawl scope, and queue state.

Handling errors

Check the status before decoding a success payload. API errors use the stable { "error": { "code", "message", "request_id" } } envelope; retain the request ID when contacting support.

response = requests.post(
    url,
    headers={**HEADERS, "Idempotency-Key": idempotency_key},
    json=payload,
)

if response.status_code == 429:
    # Apply bounded backoff, then retry only if the operation is safe to repeat.
    pass
elif response.status_code >= 400:
    raise RuntimeError(
        f"Extralt request failed ({response.status_code}): {response.text}"
    )

result = response.json()

For create and restart requests, retry the same payload with the original Idempotency-Key. This returns the original result instead of duplicating work. A key reused with a different payload returns 409 Conflict.

Pagination

List responses use cursor pagination. Pass next_cursor as the cursor query parameter while has_more is true.

def get_all_captures(run_id):
    rows = []
    cursor = None

    while True:
        params = {"run_id": run_id}
        if cursor is not None:
            params["cursor"] = cursor

        response = requests.get(
            "https://api.extralt.com/v1/extract/captures",
            headers=HEADERS,
            params=params,
        )
        response.raise_for_status()
        page = response.json()
        rows.extend(page["data"])

        if not page["has_more"]:
            return rows
        cursor = page["next_cursor"]

Do not use a Capture id from another filter scope as a cursor. Treat cursors as opaque and preserve the same filters across pages.

Rate-limit handling

A 429 response is authoritative. Honor Retry-After when present, then retry with exponential backoff, random jitter, and a maximum attempt count. Do not build against an observed request rate as if it were a permanent quota; operational limits can change.

Reads are generally safe to retry. Retry a mutation only with the same Idempotency-Key and payload, or when the operation is otherwise documented as idempotent.

Bulk Capture export

Use the Capture export route instead of paginating when you need all Captures from one Run or Import as JSONL or Parquet. Stream the response to disk rather than loading the entire file in memory. See Working with Captures.