---
title: "Watch an extraction"
summary: "Server-sent events for one job: its state now, every change after it, and the finished page. Closes when the job does."
category: "developer"
tags: ["kerf", "reference", "events", "sse", "streaming"]
updated: "2026-09-14T04:25:19.731Z"
url: "https://support.quorumtech.ch/articles/kerf-extraction-events"
markdown: "https://support.quorumtech.ch/articles/kerf-extraction-events.md"
---

# Watch an extraction

`GET https://api.quorumtech.ch/kerf/v1/jobs/{jobId}/events`

Watch an extraction as it runs.

Server-sent events: the job's state now, then one event per change, ending with the finished
job, which carries the Markdown. The stream closes when the job does. Free.

Open it straight after the `202` and you will be told the moment the page is ready, with no polling loop to write. The first event is the job's state now, so a job that finished before you connected still delivers its result rather than hanging.

Each event's `data` is the job. The last one carries `result`, and then the stream ends. A dropped connection loses nothing: the job is still there to collect.

## Request

Path parameters:

| Parameter | Description |
| --- | --- |
| `jobId` | The `id` from the answer to `POST /kerf/v1/extract/url`, for example `6f1ec0d2-...`. |

## Example request

```bash
curl -X GET "https://api.quorumtech.ch/kerf/v1/jobs/<jobId>/events" \
  -H "Authorization: Bearer $QUORUM_API_KEY"
```

## Responses

### 200

The stream. It ends when the extraction does.

## Errors

The body is `{ "error": { "code", "message" } }`; the full catalogue with what to do about each code is on the Errors page.

| Status | Codes | When |
| --- | --- | --- |
| 401 |  | The key is missing, not recognised, or revoked. |
| 403 | `insufficient_scope`, `url_not_allowed`, `robots_disallowed` | The key is not scoped to Kerf (`insufficient_scope`), the address is one this service does not fetch (`url_not_allowed`: a private or loopback address, or a redirect to one), or the site's robots.txt disallows the page and you asked for it to be followed (`robots_disallowed`). |
| 404 | `job_not_found` | No extraction with that id (`job_not_found`). Either it was never started here, it has been forgotten, or it belongs to another workspace, which reads the same way on purpose. |
| 429 | `rate_limited`, `claims_rate_limited`, `claim_budget_exhausted`, `monthly_cap_reached`, `cap` | Over the key's requests-per-minute limit (`rate_limited`), its claims-per-minute ceiling (`claims_rate_limited`), its claims-per-day budget (`claim_budget_exhausted`), or, for a postpaid workspace, its monthly credit cap (`monthly_cap_reached`, with `cap` and `resetsAt`). `Retry-After` says when to try again; the daily budget resets at midnight UTC and the cap on the first of the month. |

## Cost

Free.
