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

# Get Job

> Check the status and progress of a background job

Returns the current state of a job created by [`POST /remove`](/api-reference/remove-background#video) for a video, including its progress and, once it has completed, the `result_url` to download the output from.

This endpoint is free: polling does not consume credits, and `GET` requests under `/jobs/` are exempt from your plan's per-organisation rate limit.

## Request

<ParamField path="id" type="string" required>
  The job ID returned as `id` in the `202` response to `POST /remove` (starts with `job_`).
</ParamField>

```bash theme={null}
curl https://api.poof.bg/v1/jobs/job_8f3a1c2e9b7d4a10 \
  -H "x-api-key: pk_your_api_key"
```

## Response

<ResponseField name="id" type="string">
  Job ID, prefixed with `job_`.
</ResponseField>

<ResponseField name="status" type="string">
  One of `queued`, `processing`, `completed` or `failed`. See [Status lifecycle](#status-lifecycle).
</ResponseField>

<ResponseField name="progress" type="number">
  Fraction of the video processed so far, from `0` to `1`. It is `0` while `queued` and `1` once `completed`.
</ResponseField>

<ResponseField name="created_at" type="string">
  ISO 8601 timestamp of when the job was accepted.
</ResponseField>

<ResponseField name="started_at" type="string | null">
  When processing began. `null` while the job is still `queued`.
</ResponseField>

<ResponseField name="completed_at" type="string | null">
  When the job reached `completed` or `failed`. `null` until then.
</ResponseField>

<ResponseField name="duration_seconds" type="number">
  Source video duration in seconds.
</ResponseField>

<ResponseField name="fps" type="number">
  Source frame rate.
</ResponseField>

<ResponseField name="width" type="integer">
  Source video width in pixels.
</ResponseField>

<ResponseField name="height" type="integer">
  Source video height in pixels.
</ResponseField>

<ResponseField name="frames" type="integer">
  Number of frames in the source video.
</ResponseField>

<ResponseField name="credits" type="integer">
  Credits charged for this job. Refunded automatically if the job fails.
</ResponseField>

<ResponseField name="format" type="string">
  The requested output format: `webm`, `mov` or `mp4`.
</ResponseField>

<ResponseField name="channels" type="string">
  The requested output channels: `rgba`, `rgb` or `alpha`.
</ResponseField>

<ResponseField name="status_url" type="string">
  URL of this job, the same one returned as `status_url` when the job was created.
</ResponseField>

<ResponseField name="result_url" type="string | null">
  Direct download URL of the result on the Poof CDN (`https://cdn.poof.bg/...`). `null` until the job is `completed`. The link needs no API key, so it can be handed straight to a browser or a `<video>` tag; anyone with the link can download the file until `expires_at`. It is served with `Content-Disposition: attachment; filename=result.<ext>`.
</ResponseField>

<ResponseField name="expires_at" type="string | null">
  When the result is deleted, 24 hours after completion. `null` until the job is `completed`. After this time `result_url` stops working and the job itself returns `404 job_not_found`.
</ResponseField>

<ResponseField name="error" type="object | null">
  Set only when `status` is `failed`: an object with `code` and `message` describing why the job failed. `null` otherwise.
</ResponseField>

<ResponseField name="stats" type="object | null">
  Processing statistics. Set once `status` is `completed`; `null` otherwise. `output_width`, `output_height` and `processing_seconds` are guaranteed; the remaining keys are informational and may change or be removed without notice, so do not build logic on them.

  <Expandable title="properties">
    <ResponseField name="output_width" type="integer">
      Output video width in pixels: the source size capped at 1920 px on the longest side (or smaller with `size`), aspect ratio kept.
    </ResponseField>

    <ResponseField name="output_height" type="integer">
      Output video height in pixels, capped in the same way.
    </ResponseField>

    <ResponseField name="processing_seconds" type="number">
      Wall-clock time spent processing the job, from `started_at` to `completed_at`.
    </ResponseField>

    <ResponseField name="matting_seconds" type="number">
      Time spent running the matting model on the GPU. Informational, subject to change.
    </ResponseField>

    <ResponseField name="decode_wait_seconds" type="number">
      Time the pipeline spent waiting for the decoder to deliver frames. Informational, subject to change.
    </ResponseField>

    <ResponseField name="encode_wait_seconds" type="number">
      Time the pipeline spent waiting for the encoder to accept frames. Informational, subject to change.
    </ResponseField>

    <ResponseField name="scene_cuts" type="integer">
      Number of scene cuts detected. Each cut resets the temporal stabiliser's history. Informational, subject to change.
    </ResponseField>

    <ResponseField name="output_bytes" type="integer">
      Size of the result file in bytes, handy for showing download progress. Informational, subject to change.
    </ResponseField>
  </Expandable>
</ResponseField>

Every response carries an `X-Request-ID` header.

<ResponseExample>
  ```json Processing theme={null}
  {
    "id": "job_8f3a1c2e9b7d4a10",
    "status": "processing",
    "progress": 0.42,
    "created_at": "2026-10-12T10:12:00Z",
    "started_at": "2026-10-12T10:12:04Z",
    "completed_at": null,
    "duration_seconds": 12.4,
    "fps": 29.97,
    "width": 1920,
    "height": 1080,
    "frames": 372,
    "credits": 65,
    "format": "webm",
    "channels": "rgba",
    "status_url": "https://api.poof.bg/v1/jobs/job_8f3a1c2e9b7d4a10",
    "result_url": null,
    "expires_at": null,
    "error": null,
    "stats": null
  }
  ```

  ```json Completed theme={null}
  {
    "id": "job_8f3a1c2e9b7d4a10",
    "status": "completed",
    "progress": 1,
    "created_at": "2026-10-12T10:12:00Z",
    "started_at": "2026-10-12T10:12:04Z",
    "completed_at": "2026-10-12T10:12:41Z",
    "duration_seconds": 12.4,
    "fps": 29.97,
    "width": 1920,
    "height": 1080,
    "frames": 372,
    "credits": 65,
    "format": "webm",
    "channels": "rgba",
    "status_url": "https://api.poof.bg/v1/jobs/job_8f3a1c2e9b7d4a10",
    "result_url": "https://cdn.poof.bg/1d/jobs/job_8f3a1c2e9b7d4a10/result.webm",
    "expires_at": "2026-10-13T10:12:41Z",
    "error": null,
    "stats": {
      "output_width": 1920,
      "output_height": 1080,
      "processing_seconds": 37.2,
      "matting_seconds": 28.9,
      "decode_wait_seconds": 1.4,
      "encode_wait_seconds": 3.1,
      "scene_cuts": 2,
      "output_bytes": 18734211
    }
  }
  ```

  ```json Failed theme={null}
  {
    "id": "job_8f3a1c2e9b7d4a10",
    "status": "failed",
    "progress": 0.42,
    "created_at": "2026-10-12T10:12:00Z",
    "started_at": "2026-10-12T10:12:04Z",
    "completed_at": "2026-10-12T10:12:20Z",
    "duration_seconds": 12.4,
    "fps": 29.97,
    "width": 1920,
    "height": 1080,
    "frames": 372,
    "credits": 65,
    "format": "webm",
    "channels": "rgba",
    "status_url": "https://api.poof.bg/v1/jobs/job_8f3a1c2e9b7d4a10",
    "result_url": null,
    "expires_at": null,
    "error": {
      "code": "processing_failed",
      "message": "The model or encoder failed on this video."
    },
    "stats": null
  }
  ```
</ResponseExample>

## Status lifecycle

```
queued ──▶ processing ──▶ completed
                      └─▶ failed
```

| Status | Meaning |
| - | - |
| `queued` | Accepted and waiting for a worker. `progress` is `0`. |
| `processing` | Frames are being processed. `progress` increases as batches finish. |
| `completed` | Done. `result_url` is set and the file is available until `expires_at`. |
| `failed` | Processing failed. `error` says why, and the job's credits have been refunded. |

`completed` and `failed` are terminal: a job never leaves them. Every job produces exactly one terminal state.

## Polling

Poll every 2 to 5 seconds. Short clips typically finish within a minute; a 5 minute clip can take several minutes. Polling is free and does not count towards your credits. `GET` requests under `/jobs/` are exempt from your plan's [per-organisation rate limit](/errors/rate-limit-exceeded); only the global per-IP limit applies. Polling every 2 to 5 seconds is therefore safe on every plan, but there is no benefit in polling faster than that.

<CodeGroup>
  ```bash cURL theme={null}
  until curl -s https://api.poof.bg/v1/jobs/job_8f3a1c2e9b7d4a10 \
      -H "x-api-key: YOUR_API_KEY" | grep -qE '"status": ?"(completed|failed)"'; do
    sleep 3
  done
  ```

  ```python Python theme={null}
  import time
  import requests

  headers = {"x-api-key": "YOUR_API_KEY"}

  def wait_for_job(job_id: str, interval: float = 3.0) -> dict:
      while True:
          r = requests.get(f"https://api.poof.bg/v1/jobs/{job_id}", headers=headers)
          r.raise_for_status()
          job = r.json()
          if job["status"] in ("completed", "failed"):
              return job
          print(f"{job['status']} {job['progress']:.0%}")
          time.sleep(interval)

  job = wait_for_job("job_8f3a1c2e9b7d4a10")
  if job["status"] == "failed":
      raise RuntimeError(f"{job['error']['code']}: {job['error']['message']}")
  print("Download from", job["result_url"], "before", job["expires_at"])
  ```

  ```typescript TypeScript theme={null}
  const headers = { 'x-api-key': 'YOUR_API_KEY' };

  async function waitForJob(jobId: string, intervalMs = 3000) {
    while (true) {
      const res = await fetch(`https://api.poof.bg/v1/jobs/${jobId}`, { headers });
      if (!res.ok) throw new Error(await res.text());
      const job = await res.json();
      if (job.status === 'completed' || job.status === 'failed') return job;
      console.log(job.status, `${Math.round(job.progress * 100)}%`);
      await new Promise((r) => setTimeout(r, intervalMs));
    }
  }

  const job = await waitForJob('job_8f3a1c2e9b7d4a10');
  if (job.status === 'failed') throw new Error(`${job.error.code}: ${job.error.message}`);
  console.log('Download from', job.result_url, 'before', job.expires_at);
  ```
</CodeGroup>

## Downloading the result

`result_url` is a direct link to the file on the Poof CDN. It needs no API key, so you can download it from a server, hand it to a browser, or use it as the `src` of a `<video>` element. Results can be large (a minute of ProRes 4444 at 1080p is several hundred megabytes), so stream the response to disk rather than buffering it in memory.

The link is valid until `expires_at`, 24 hours after completion. Copy the file to your own storage if you need it for longer; after expiry the job and its result are deleted and must be submitted again (and billed again).

## Error Responses

| Status | Code | Description |
| - | - | - |
| 401 | `authentication_error` | `x-api-key` missing or not recognised |
| 404 | `job_not_found` | No job with this ID, the job belongs to another account, or the job has expired |

See [API Error Codes](/errors/list) for the error body format.

<RequestExample>
  ```bash cURL theme={null}
  curl https://api.poof.bg/v1/jobs/job_8f3a1c2e9b7d4a10 \
    -H "x-api-key: YOUR_API_KEY"
  ```
</RequestExample>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.