# Read a job

> Where one job is: `lifecycle`, `outcome` (`null` until it ends), `progress`, and the `error` of a job that failed — cheap enough to poll.

`GET https://api.refabric.com/v1/jobs/{job_id}`

**Expansions**

- `input` — the request body that started the job, as you sent it (masked, cut at the kept size). Requests are kept 30 days; after that, and for a job started in the app, `input` is `null`.

**Authentication.** A key with `jobs:read`: a job and its files are your account's.

**Common use cases**

- Poll a job until it ends, then read its result.
- See which key started a job and what it sent.

**See also**

- `GET /v1/jobs/{job_id}/result`
- `PUT /v1/jobs/{job_id}/cancel`

Authentication: `Authorization: Key $REFABRIC_API_KEY`, scope `jobs:read`.

## Path parameters

- `job_id` (string, _required_)

## Query parameters

- `logs` (boolean, _optional_, default: `false`) — `1`: add `events[]` — the job's log `{at, phase, message, file?, field?}`.
- `expand` (array<string> | null, _optional_) — `input`: add `input` — the request body that started the job, as you sent it (masked, and cut at the kept size, which the answer says). `null` when the job was started in the app or its request is no longer kept.

## Header parameters

- `Refabric-Version` (string, _optional_, format: date) — The contract version you wrote against (a date). Absent: the current version.
- `X-Request-ID` (string, _optional_, max length 128) — Your own id for this request; we answer it back under X-Client-Request-ID.

## Response 200

Done: the answer is in the body.

- `job_id` (string, _required_) — The job.
  Example: `9b2f4c1d0e8a`
- `task` (string, _required_) — The task the job runs.
  Example: `image.expand`
- `lifecycle` (string, _required_) — Where the job is.
  Values: `queued` (Accepted and waiting to start.); `running` (Being made.); `terminal` (Ended. `outcome` says how.)
  Example: `running`
- `outcome` (string | null, _required_) — How the job ended — `null` until its `lifecycle` is `terminal`.
  Values: `succeeded` (It finished; its files are ready.); `failed` (It ended without its result; `error` says why.); `cancelled` (You cancelled it.)
- `progress` (object, _required_) — How far the job got.
  Example: `{"done":0,"phase":"","total":1}`
  - `done` (integer, _required_) — Files delivered so far.
    Example: `2`
  - `total` (integer | null, _required_) — Files the job plans to deliver; `null` until it is known.
    Example: `4`
  - `phase` (string, _required_) — What the job is doing now; may be empty.
    Example: ``
- `metrics` (object, _required_) — When it was queued and started, and how long it ran.
  Example: `{"queued_at":"2026-10-05T09:30:00Z"}`
  - `queued_at` (string | null, _required_) — When it was accepted, ISO-8601 in UTC.
    Example: `2026-10-05T09:30:00Z`
  - `started_at` (string | null, _required_) — When it started; `null` while it is queued.
    Example: `2026-10-05T09:30:02Z`
  - `duration_ms` (integer | null, _required_) — How long it ran, once it has ended; `null` before.
    Example: `21000`
- `error` (object | null, _required_) — Why it failed or that it was cancelled, in the shape of every error; `null` otherwise.
  - `code` (string, _required_) — The catalogued code.
  - `type` (string, _required_) — What kind of failure.
    Values: `invalid_request` (The request cannot be used as sent; fix it and send again.); `authentication` (No valid API key was sent.); `permission` (Your key or your plan does not allow this.); `not_found` (Nothing of yours has this address, or it was removed.); `conflict` (The request conflicts with the current state of what it names.); `insufficient_credits` (Your balance does not cover this request.); `rate_limited` (Too many requests; wait and retry.); `content_refused` (The inputs were refused.); `processing_failed` (The job ran and failed.); `internal` (Something went wrong on our side.); `warning` (Not an error: a note on a job that succeeded.)
  - `message` (string, _required_) — What happened, in words.
  - `retryable` (boolean, _required_) — Whether the same request may succeed later.
  - `field` (string, _optional_) — The request field it is about, when one is.
  - `docs` (string, _optional_) — The code's page; absent while the docs have no address.
  - `request_id` (string, _optional_) — The request's id, to quote to support.
  - `ctx` (object, _optional_) — Facts about this error, by the keys its code declares (`GET /v1/errors`); absent when there are none. Ignore a key you do not know.
    - `required` (integer, _optional_) — The credits this request needs.
    - `balance` (integer, _optional_) — The credits your balance holds now.
    - `retry_after` (integer, _optional_) — Seconds to wait before the next call.
  - `input` (any, _optional_) — What you sent for `field`, shortened; absent when it is not echoed (a file, an object or a secret never is).
  - `job_id` (string, _optional_) — The job that failed, when a run waited for it (`Prefer: wait`) and it ended in this error. Read it again at `GET /v1/jobs/{job_id}`; absent on every other error.
  - `required` (integer, _optional_, deprecated) — Deprecated: read `ctx.required` (same value).
  - `balance` (integer, _optional_, deprecated) — Deprecated: read `ctx.balance` (same value).
- `api_key_id` (string | null, _required_) — The key that started it; `null` for work started in the Refabric app.
  Example: `key_8f3a`
- `charge` (object | null, _required_) — What it costs and whether that is final; `null` for a job that costs nothing.
  Example: `{"credit_type":"refabric_credits","credits":12,"state":"charged"}`
  - `credits` (integer, _required_) — The credits: the most the job can take while `reserved`, what it took once `charged`, `0` once `refunded`.
    Example: `12`
  - `credit_type` (string | null, _required_) — Which kind of credit an amount is counted in.
    Values: `refabric_credits` (Spent by tasks.); `model_credits` (Credits for model training.)
    Example: `refabric_credits`
  - `state` (string, _required_) — Whether the amount a job costs is held or final.
    Values: `reserved` (Held while the job runs: `credits` is the most it can cost. It settles to `charged` or `refunded` once the job has ended.); `charged` (Final: `credits` is what the job cost.); `refunded` (Final: the hold was returned; the job cost nothing.)
    Example: `charged`
- `events` (array<object> | null, _optional_) — With `logs=1`: the job's log, oldest first.
  Example: `[{"at":"2026-10-05T09:30:00Z","message":"Accepted.","phase":"queued"}]`
  - `at` (string | null, _required_) — When it happened, ISO-8601 in UTC.
    Example: `2026-10-05T09:30:02Z`
  - `phase` (string, _required_) — What happened.
    Values: `queued` (The job was accepted and queued.); `running` (The job started.); `delivered` (The job delivered a file; `file` names it.); `warning` (A note on what was not made; `field` names the input.); `succeeded` (The job finished.); `failed` (The job failed; the message is its error's.); `cancelled` (The job was cancelled.)
    Example: `running`
  - `message` (string, _required_) — What happened, in words.
    Example: `Started.`
  - `file` (string | null, _optional_) — The file delivered (`delivered`).
    Example: `art:3f2a`
  - `field` (string | null, _optional_) — The input it is about, when one is.
    Example: `poses[1]`
- `input` (object | null, _optional_) — With `expand=input`: the request that started the job; `null` when it was started in the app or its request is no longer kept.
  Example: `{"body":{"prompt":"A linen summer dress"},"truncated":false}`
  - `body` (object | string | null, _required_) — The request body you sent to start the job, masked: an object when it is JSON, its text when it was cut.
    Example: `{"prompt":"A linen summer dress"}`
  - `truncated` (boolean, _required_) — Whether the body was cut at the kept size.
    Example: `false`

```json
{
  "job_id": "9b2f4c1d0e8a",
  "task": "image.expand",
  "lifecycle": "running",
  "outcome": null,
  "progress": {
    "done": 0,
    "phase": "",
    "total": 1
  },
  "metrics": {
    "queued_at": "2026-10-05T09:30:00Z"
  },
  "error": null,
  "api_key_id": "key_8f3a",
  "charge": {
    "credit_type": "refabric_credits",
    "credits": 12,
    "state": "charged"
  },
  "events": [
    {
      "at": "2026-10-05T09:30:00Z",
      "message": "Accepted.",
      "phase": "queued"
    }
  ],
  "input": {
    "body": {
      "prompt": "A linen summer dress"
    },
    "truncated": false
  }
}
```

## Response 400

The request cannot be read as it was sent (a header, the URL or the body's form).

```json
{
  "error": {
    "code": "string",
    "type": "invalid_request",
    "message": "string",
    "field": "string",
    "retryable": true,
    "docs": "string",
    "request_id": "string",
    "ctx": {
      "required": 0,
      "balance": 0,
      "retry_after": 0
    },
    "input": null,
    "job_id": "string",
    "required": 0,
    "balance": 0
  }
}
```

## Response 401

No valid API key was sent.

```json
{
  "error": {
    "code": "string",
    "type": "invalid_request",
    "message": "string",
    "field": "string",
    "retryable": true,
    "docs": "string",
    "request_id": "string",
    "ctx": {
      "required": 0,
      "balance": 0,
      "retry_after": 0
    },
    "input": null,
    "job_id": "string",
    "required": 0,
    "balance": 0
  }
}
```

## Response 403

Your key or your plan does not allow this.

```json
{
  "error": {
    "code": "string",
    "type": "invalid_request",
    "message": "string",
    "field": "string",
    "retryable": true,
    "docs": "string",
    "request_id": "string",
    "ctx": {
      "required": 0,
      "balance": 0,
      "retry_after": 0
    },
    "input": null,
    "job_id": "string",
    "required": 0,
    "balance": 0
  }
}
```

## Response 404

Nothing has this address.

```json
{
  "error": {
    "code": "string",
    "type": "invalid_request",
    "message": "string",
    "field": "string",
    "retryable": true,
    "docs": "string",
    "request_id": "string",
    "ctx": {
      "required": 0,
      "balance": 0,
      "retry_after": 0
    },
    "input": null,
    "job_id": "string",
    "required": 0,
    "balance": 0
  }
}
```

## Response 422

A field is missing or has a value this operation cannot use.

```json
{
  "error": {
    "code": "string",
    "type": "invalid_request",
    "message": "string",
    "field": "string",
    "retryable": true,
    "docs": "string",
    "request_id": "string",
    "ctx": {
      "required": 0,
      "balance": 0,
      "retry_after": 0
    },
    "input": null,
    "job_id": "string",
    "required": 0,
    "balance": 0
  }
}
```

## Response 429

Too many requests: wait for the number of seconds in the Retry-After header.

```json
{
  "error": {
    "code": "string",
    "type": "invalid_request",
    "message": "string",
    "field": "string",
    "retryable": true,
    "docs": "string",
    "request_id": "string",
    "ctx": {
      "required": 0,
      "balance": 0,
      "retry_after": 0
    },
    "input": null,
    "job_id": "string",
    "required": 0,
    "balance": 0
  }
}
```

## Response 500

Something went wrong on our side; retry, and quote the request id if it keeps happening.

```json
{
  "error": {
    "code": "string",
    "type": "invalid_request",
    "message": "string",
    "field": "string",
    "retryable": true,
    "docs": "string",
    "request_id": "string",
    "ctx": {
      "required": 0,
      "balance": 0,
      "retry_after": 0
    },
    "input": null,
    "job_id": "string",
    "required": 0,
    "balance": 0
  }
}
```

## Request

```python
import os
import requests

url = "https://api.refabric.com/v1/jobs/{job_id}"

headers = {"Authorization": f"Key {os.environ['REFABRIC_API_KEY']}"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.refabric.com/v1/jobs/{job_id}';
const options = {method: 'GET', headers: {Authorization: `Key ${process.env.REFABRIC_API_KEY}`}};

try {
    const response = await fetch(url, options);
    const data = await response.json();
    console.log(data);
} catch (error) {
    console.error(error);
}
```

```bash
curl --request GET \
    --url https://api.refabric.com/v1/jobs/{job_id} \
    --header "Authorization: Key $REFABRIC_API_KEY"
```
