---
title: "Read a bulk job’s status and progress"
method: GET
path: "/v1/vat/bulk/{jobId}"
tags: ["VAT validation"]
---

# Read a bulk job’s status and progress

`GET /v1/vat/bulk/{jobId}`

## Request

- Base URL: `https://api.vatnode.dev`
- URL: `https://api.vatnode.dev/v1/vat/bulk/{jobId}`
- Auth: HTTP bearer

Progress counters are kept on the job itself, so this stays a single
cheap read however large the batch is. Poll it until `status` is
terminal – `finished`, `cancelled` or `failed` – then page through the
results. Every job reaches one of the three: there is a bound on how
many times a job may step aside from a degraded member state, and on
the last attempt the affected positions are answered rather than
postponed again.

## Path parameters

- `jobId` string, uuid, required

## Response `200`

The job.

- BulkJob
  - `jobId` string, uuid, required
  - `status` 'queued' | 'processing' | 'finished' | 'cancelled' | 'failed', required: `finished` means every position has an answer – including positions that answered with an error. It is not a claim that every VAT ID was successfully validated; read `summary`. `cancelled` means the job stopped early: positions it had already answered keep their results, the rest stay `pending` and unbilled. `failed` also means the job stopped early, for a reason of its own rather than a stop it was asked for; `errorCode` says which. Positions are treated exactly as under `cancelled`. All three are terminal. Branch on the terminal set, not on `finished` alone, so a job that ends any other way does not leave a poller running until the job expires.
  - `totalItems` integer, required
  - `processedItems` integer, required
  - `summary` object, required
    - `valid` integer, required
    - `invalid` integer, required
    - `errors` integer, required: Positions that carry an `error` object.
  - `createdAt` string, date-time, required
  - `startedAt` string, date-time, nullable
  - `finishedAt` string, date-time, nullable
  - `cancelRequestedAt` string, date-time, nullable: When a cancel was first requested; the job may still be stopping. A `cancelled` job can have this `null` – it was stopped by something other than a cancel request, and `errorCode` says what.
  - `expiresAt` string, date-time, required: When the job and its positions are deleted (30 days after submission). The underlying checks remain in your check history.
  - `errorCode` string, nullable: Why the job ended the way it did, when that needs saying. `null` for an ordinary run and for a cancel you requested. `API_KEY_REVOKED` means the API key the job was submitted with was revoked while it was running, so it stopped where it stood.

## Other responses

- `400`: The request body, query string or path parameters failed structural validation (wrong type, missing required field, out-of-range length, malformed enum, etc.). Returns the standard error envelope with code `INVALID_REQUEST`. This is distinct from a semantically well-formed request that is rejected later (e.g. `INVALID_FORMAT` for a VAT ID that parses but is not a valid VAT number).
- `401`: Missing or invalid API key.
- `404`: The addressed resource does not exist, or is not owned by the authenticated caller. Returns the standard error envelope with code `NOT_FOUND`. Ownership and existence are deliberately not distinguished, so a resource belonging to another account is reported as missing rather than forbidden.

---

[API](https://skmtc.dev/vatnode/apis/vatnode-api.md) · [All operations](https://skmtc.dev/vatnode/apis/vatnode-api/llms.txt) · [OpenAPI document](https://skmtc.dev/vatnode/apis/vatnode-api/revisions/c65828db2037?raw)
