# Async jobs & polling

Generation on Layer is **asynchronous**. Both direct model runs (forge) and workflow runs return immediately with `202 Accepted` and a run identifier; you then **poll** for the result.

Note

There are **no webhooks** — polling is the only way to observe run completion. Poll on the cadence the API suggests rather than in a tight loop.

## Lifecycle

1. **Submit** — `POST` to an execute endpoint. The response includes an id, an initial status, and `poll_interval_seconds` (currently `10`).
2. **Poll** — `GET` the run by id every `poll_interval_seconds` until it reaches a terminal status.
3. **Read** — on success, read the outputs; on failure, read `error` / `error_code`.

```bash
# Submit
curl -X POST https://api.app.layer.ai/api/v1/workspaces/$WORKSPACE_ID/inferences \
  -H "Authorization: Bearer $LAYER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "model_id": "MODEL_ID", "parameters": { "prompt": "a mossy stone golem" } }'
# → { "inference_id": "…", "status": "IN_PROGRESS", "poll_interval_seconds": 10 }


# Poll
curl https://api.app.layer.ai/api/v1/workspaces/$WORKSPACE_ID/inferences/INFERENCE_ID \
  -H "Authorization: Bearer $LAYER_TOKEN"
```

## Forge (single model) runs

`POST .../inferences` returns an `inference_id`. Poll `GET .../inferences/{id}`.

| Status       | Terminal | Meaning                                                                  |
| ------------ | -------- | ------------------------------------------------------------------------ |
| IN\_PROGRESS | no       | Running.                                                                 |
| COMPLETE     | yes      | Done — response includes outputs\[\] and actual\_price\_creative\_units. |
| FAILED       | yes      | Failed — response includes error and error\_code.                        |
| CANCELLED    | yes      | Cancelled.                                                               |
| DELETED      | yes      | The run was deleted.                                                     |

## Workflow runs

`POST .../workflows/{id}/runs` returns a `run_id`. Poll `GET .../workflows/{id}/runs/{run_id}`. The response includes a per-step `steps[]` array (each with `name`, `status`, and `error`/`error_code`), so you can see where a run is or why it failed.

| Status    | Terminal | Meaning                                      |
| --------- | -------- | -------------------------------------------- |
| PENDING   | no       | Queued.                                      |
| RUNNING   | no       | Executing.                                   |
| SUCCESS   | yes      | Done — response includes outputs.            |
| FAILURE   | yes      | Failed — see steps\[\] for the failing step. |
| CANCELLED | yes      | Cancelled.                                   |

## Cancellation

The REST API does **not** expose a cancel endpoint. A run can be cancelled from the Layer app, or via the [MCP server](/docs/mcp/tools/workflows/cancel%5Fworkflow%5Frun) (`cancel_workflow_run`), which releases any reserved Creative Units.

Caution

A terminal `FAILED`/`FAILURE` can carry `error_code: INSUFFICIENT_BALANCE` even though submission returned `202` — balance is not enforced at submit. Always inspect the terminal status and `error_code`. See [Errors](/docs/errors).
