> ## Documentation Index
> Fetch the complete documentation index at: https://scrinly.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Async jobs and webhooks

> Queue renders, poll account-scoped job status, and receive signed completion webhooks.

Use an async job when a caller should not hold an HTTP connection open while Chromium renders the page. `POST /render` is always asynchronous. Snapshot and screenshot requests become asynchronous when `async` is `true` or a `webhookUrl` is supplied.

## Submit a job

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST 'https://api.scrinly.com/render' \
    --header 'Authorization: Bearer sk_live_your_key' \
    --header 'Content-Type: application/json' \
    --data '{"url":"https://example.com","markdown":true,"store":true,"storage_path":"renders"}'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.scrinly.com/render', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.SCRINLY_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      url: 'https://example.com',
      markdown: true,
      store: true,
      storage_path: 'renders',
    }),
  });
  const job = await response.json();
  ```

  ```python Python theme={null}
  import os, requests

  job = requests.post(
      "https://api.scrinly.com/render",
      headers={"Authorization": f"Bearer {os.environ['SCRINLY_API_KEY']}"},
      json={
          "url": "https://example.com",
          "markdown": True,
          "store": True,
          "storage_path": "renders",
      },
  ).json()
  ```
</CodeGroup>

Async jobs always store their screenshot, so include `storage_path` when requesting one.

## Poll job status

Call `GET /status/{jobId}` with the same account key that created the job. Status is `queued`, `in_progress`, `completed`, `failed`, or `not_found`, and progress ranges from 0 to 100.

```bash theme={null}
curl 'https://api.scrinly.com/status/6a8bc66a-0000-4000-8000-000000000000' \
  --header 'Authorization: Bearer sk_live_your_key'
```

The endpoint returns `401` for a missing, unknown, or revoked API key. A nonexistent job and a job owned by another account both return `404`; ownership is never disclosed.

Completed results stored in job state are capped at about 1.5 MB. If necessary, Scrinly drops the largest optional fields and lists their names in `result.truncated`. Stored media URLs remain available.

## Receive a webhook

Set `webhookUrl` to receive the final job result instead of polling. It implies async mode. If you also set `webhookSecret`, Scrinly signs the raw request body with HMAC-SHA256. Verify the signature before parsing or trusting the payload.

Your webhook should return a successful response quickly and move expensive processing to its own queue. Delivery failures do not change the completed render result.

## Retry failures

* `POST /retry/{jobId}` re-enqueues one failed job.
* `POST /retry-failed` retries failed jobs in bulk.
* `GET /dead-letter-queue` lists jobs that exhausted normal delivery attempts.

These routes are customer-authenticated and account-scoped. A retry can consume credits because it performs Browser Rendering again.
