---
title: "Read a review's progress"
summary: "One progress frame for a review that is still running, without collecting it or settling its bill."
category: "developer"
tags: ["attest", "reference", "draft", "job", "progress", "polling"]
updated: "2026-09-14T04:25:16.484Z"
url: "https://support.quorumtech.ch/articles/attest-draft-progress"
markdown: "https://support.quorumtech.ch/articles/attest-draft-progress.md"
---

# Read a review's progress

`GET https://api.quorumtech.ch/attest/v1/attest/draft/jobs/{jobId}/progress`

Read a review's progress without collecting it.

One progress frame, and nothing else. Unlike the job route this neither hands over the
finished review nor settles its bill, so a dashboard can show a review's counts moving
without consuming it. Free.

Use this where something needs to show a review moving, such as a dashboard or a progress bar, and must not consume it. The job route is the only route that hands the finished review over and settles what it cost; this one answers with the counts as they stand and nothing else, whatever state the review is in.

Poll every few seconds. A frame carries the claims extracted and checked so far, how many passages of the draft are done, and the phase (`extracting`, `checking`, `finished`). A 404 means the job was never known or has been forgotten, which happens five minutes after a finished review is collected and on any restart.

## Request

Path parameters:

| Parameter | Description |
| --- | --- |
| `jobId` | The `jobId` from a `202` response to `POST /attest/v1/attest/draft`. |

## Example request

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

## Responses

### 200

The review's counts as they stand.

| Field | Type | Description |
| --- | --- | --- |
| `jobId` | string |  |
| `state` | `running` or `done` or `failed` |  |
| `progress` | JobProgress |  |
| `progress.extracted` | integer | Claims found so far. It grows as each passage is read, and is final once `phase` leaves `extracting`. |
| `progress.checked` | integer | Claims checked so far |
| `progress.passagesDone` | integer | Passages read so far |
| `progress.passagesTotal` | integer |  |
| `progress.phase` | `extracting` or `checking` or `finished` |  |
| `progress.failedPassages` | FailedPassage[] |  |
| `progress.failedPassages[].chunk` | integer |  |
| `progress.failedPassages[].excerpt` | string |  |
| `progress.failedPassages[].reason` | string |  |
| `updatedAt` | string (date-time) | When the counts last changed. |
| `error` | object | Present on a `failed` frame. |
| `error.code` | string |  |
| `error.message` | string |  |

## 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 |  | The key is not scoped to Attest. |
| 404 |  | No such job. |
| 503 | `not_configured` | The evaluator is not configured (`not_configured`). |

## Cost

Free. Reading progress never settles a review, so the credits stay held until the job route hands the finished result over.
