# 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 status line 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 "Authorization: Bearer $LAYERS_API_KEY"
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts title="progress.ts"
    const res = await fetch(
      `https://api.layers.com/v1/content/${containerId}/progress`,
      { headers: { 'Authorization': `Bearer ${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={"Authorization": f"Bearer {os.environ[\'LAYERS_API_KEY\']}"},
    )
    progress = r.json()
    ```
  </Tab>
</Tabs>

## Responses [#responses]

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

<Response status="200" description="Terminal - completed.">
  ```json
  {
    "containerId": "cnt_7d18b9a1...",
    "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_7d18b9a1...",
    "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`, `processing`, `completed`, `failed`, or `canceled`.

## Polling pattern [#polling-pattern]

Poll often enough for your UI, and back off when the stage is not changing. 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)
