# GET /v1/content/:containerId/progress (/docs/api/reference/content/get-progress)



<Endpoint method="GET" path="/v1/content/:containerId/progress" scope="content:read" phase="1" />

Returns the container's generation progress at a finer grain than `GET /v1/content/:containerId`. Meant for progress bars in end-customer UIs where you want to update a percentage or a status line every couple seconds without repeatedly pulling the whole container record.

For most programmatic work, [`GET /v1/jobs/:jobId`](/docs/api/reference/jobs/get-job) is what you want. Use this endpoint when you don't have a `jobId` handy — for example, you're reading from a list of containers and need per-container status without a separate lookup.

## Path parameters [#path-parameters]

<Parameters
  rows="[
  { name: 'containerId', type: 'string (uuid)', required: true, description: 'The container id.' },
]"
/>

## Request [#request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```sh title="terminal"
    curl https://api.layers.com/v1/content/{containerId}/progress \
      -H "X-Api-Key: $LAYERS_API_KEY"
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts title="progress.ts"
    const res = await fetch(
      `https://api.layers.com/v1/content/${containerId}/progress`,
      { headers: { 'X-Api-Key': process.env.LAYERS_API_KEY! } },
    );
    const progress = await res.json();
    ```
  </Tab>

  <Tab value="Python">
    ```py title="progress.py"
    import os, httpx

    r = httpx.get(
        f"https://api.layers.com/v1/content/{container_id}/progress",
        headers={"X-Api-Key": os.environ["LAYERS_API_KEY"]},
    )
    progress = r.json()
    ```
  </Tab>
</Tabs>

## Responses [#responses]

<Response status="200" description="Current progress snapshot.">
  ```json
  {
    "containerId": "cnt_01HXZ9...",
    "status": "generating",
    "stage": "generating_visuals",
    "stageProgress": 0.42,
    "etaSeconds": 38,
    "updatedAt": "2026-04-18T09:30:04Z"
  }
  ```
</Response>

<Response status="200" description="Terminal — completed.">
  ```json
  {
    "containerId": "cnt_01HXZ9...",
    "status": "completed",
    "stage": "finalizing",
    "stageProgress": 1.0,
    "etaSeconds": null,
    "updatedAt": "2026-04-18T09:32:11Z"
  }
  ```
</Response>

<Response status="200" description="Terminal — failed. The container's lastError field has the code.">
  ```json
  {
    "containerId": "cnt_01HXZ9...",
    "status": "failed",
    "stage": "generating_visuals",
    "stageProgress": 0.3,
    "etaSeconds": null,
    "updatedAt": "2026-04-18T09:30:41Z"
  }
  ```
</Response>

## Field semantics [#field-semantics]

* **`stageProgress`** is a 0.0–1.0 float. For multi-stage jobs, it resets to 0 at each stage transition rather than advancing monotonically across stages.
* **`etaSeconds`** is a best-effort estimate. May be `null` when the stage doesn't have a historical baseline (fresh project, unusual format).
* **`status`** mirrors the container status — `queued`, `generating`, `completed`, `failed`, or `canceled`.

## Polling cadence [#polling-cadence]

Two seconds is a sane default for user-facing progress bars. Back off to five seconds if the container sits on the same stage for more than 30 seconds — this is usually the `generating_visuals` stage, which can take minutes. Don't poll faster than once per second.

If you're running an agent that just needs to know "done or not", poll [`GET /v1/jobs/:jobId`](/docs/api/reference/jobs/get-job) at the job level instead. It's the same data shape for every job kind, so one poll loop covers every async call in the API.

## Errors [#errors]

| Code              | When                          |
| ----------------- | ----------------------------- |
| `NOT_FOUND`       | Container id not in this org. |
| `FORBIDDEN_SCOPE` | Key lacks `content:read`.     |

## See also [#see-also]

* [The jobs envelope](/docs/api/concepts/jobs)
* [Read the full container](/docs/api/reference/content/get-container)
