<!-- Source: https://docs.biosimulant.com/developer-api/runs -->

# Run lifecycle

Every server execution is a durable job. The external states are:

```text
queued → running → completed
                 ↘ failed
                 ↘ timed_out
queued/running → cancelled
```

`POST /runs` returns `202 Accepted` and a `Location` header. The SDK's `runs.create()` maps that response to a `Run` or `AsyncRun` handle.

## Wait and refresh

`run.wait(timeout=300)` polls with bounded exponential backoff. The timeout applies to the caller only. If it expires, `RunTimeout.run` retains the handle and the cloud execution continues.

```python
from biosimulant import Client, RunTimeout

with Client() as client:
    run = client.runs.create(ref="demi/microbiology-hello-world-growth@1.0.0")
    try:
        result = run.wait(timeout=30)
    except RunTimeout as exc:
        print("still running", exc.run.id)
```

## Cancel

```python
run.cancel()
```

Cancellation is idempotent. A completed, failed, cancelled, or timed-out run remains in its terminal state.

## Events

Use `GET /runs/{id}/events?after=<cursor>` for progress and logs. Save `next_cursor` and pass it as `after` on the next request. Polling is the canonical SDK transport for the first release; public WebSocket and SSE connections are not required.

## Results and artifacts

Results become available after `completed`. `GET /runs/{id}/results` returns lab-level outputs, artifact descriptors, and provenance. Download an artifact only through its run-scoped URL; authorization is checked against the owning account.
