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

# Python API

> Run, monitor, list, update, and cancel optimizations programmatically with the ionworks-api Python client

The [`ionworks-api`](https://github.com/ionworks/ionworks-api) Python package provides a sub-client for running and managing [optimizations](/optimize/overview) programmatically. For installation and authentication, see the [Python API client](/api-client) page.

## Running an optimization

```python theme={null}
from ionworks import Ionworks

# Reads IONWORKS_API_KEY and IONWORKS_PROJECT_ID from the environment.
# project_id is auto-injected into payloads that don't specify it.
client = Ionworks()

optimization = client.optimization.run({
    "name": "Electrode thickness optimization",
    "parameterized_model_id": "your-parameterized-model-id",
    "protocol_experiment": {
        "protocol": "...",
        "name": "1C Discharge",
    },
    "design_parameters": {
        "Positive electrode thickness [m]": {
            "bounds": [50e-6, 100e-6],
        },
    },
    "objectives": {
        "Discharge capacity [A.h]": {"type": "maximize"},
    },
})

print(f"Optimization ID: {optimization.id}")
print(f"Job ID: {optimization.job_id}")
```

## Waiting for completion

```python theme={null}
result = client.optimization.wait_for_completion(
    "your-optimization-id",
    timeout=600,        # seconds (default: 600)
    poll_interval=3,    # seconds between polls (default: 3)
    verbose=True,       # print status updates (default: True)
)

print(result["status"])   # "succeeded", "failed", or "canceled"
print(result.get("metrics"))
```

`wait_for_completion` polls until the optimization reaches a terminal
status (`succeeded`, `failed`, or `canceled`) and returns the optimization
resource. By default it raises [`IonworksError`](/api-client#error-handling)
on `failed` or `canceled`; set `raise_on_failure=False` to get the result
dict back instead.

## Listing optimizations

```python theme={null}
# List optimizations in the default project
# (set IONWORKS_PROJECT_ID or pass project_id= to Ionworks(...))
optimizations = client.optimization.list()

# Override the project for a single call
optimizations = client.optimization.list(project_id="other-project-id")

# Paginate results
optimizations = client.optimization.list(limit=10, offset=0)
```

<Note>
  When `project_id` is omitted, `client.optimization.run` and `.list`
  use the [default project](/api-client#default-project) configured on
  the Ionworks client.
</Note>

## Getting an optimization

Returns the optimization resource as a flat dictionary. The lifecycle
`status` is one of `queued`, `running`, `succeeded`, `failed`, or
`canceled`. Result `metrics` and `error` are populated once the
optimization reaches a terminal state.

```python theme={null}
result = client.optimization.get("your-optimization-id")

print(result["id"], result["status"])
print(result.get("metrics"))   # populated when status == "succeeded"
print(result.get("error"))     # populated when status == "failed"
```

<Note>
  The response used to be split into separate `optimization` and `job`
  keys. The job is no longer exposed — `status`, `metrics`, and `error`
  now live on the optimization resource itself. Update any code that
  reads `result["job"]["status"]` to read `result["status"]`.
</Note>

## Updating an optimization

```python theme={null}
optimization = client.optimization.update("your-optimization-id", {
    "name": "Electrode optimization v2",
    "description": "Updated bounds",
})
```

## Inspecting the parameter trace

Design optimizations and [data fits](/pipelines/data-fitting/overview) log the
optimizer's per-iteration progress to the job's metadata. This is the same data
that powers the parameter and cost-convergence plots in Studio. Use
`client.job.get_parameter_trace` to pull it down as a list of dicts (one per
saved iteration, oldest first):

```python theme={null}
trace = client.job.get_parameter_trace(optimization.job_id)

for entry in trace:
    print(entry["inputs_unscaled"], entry["cost"], entry["best_cost"])
```

Each entry contains:

| Key                        | Description                                                                                    |
| -------------------------- | ---------------------------------------------------------------------------------------------- |
| `cost`                     | Objective value at this iteration.                                                             |
| `best_cost`                | Best (lowest) objective value seen up to this iteration.                                       |
| `inputs`                   | Scaled parameter values at this iteration.                                                     |
| `inputs_unscaled`          | Unscaled (physical) parameter values, keyed by parameter name. Use these for parameter traces. |
| `multistart_job_id`        | Index of the multistart this entry belongs to, when applicable.                                |
| `outputs` / `best_outputs` | Model outputs at this iteration. Present for design-objective runs.                            |

<Note>
  Saves are throttled (roughly every 100 iterations or every 5 seconds), so the
  trace is a sampled subset of the optimizer's evaluations rather than every
  single one. The list is empty when live progress updates were disabled for
  the run, and there is no per-iteration wall-clock timing.
</Note>

## Canceling an optimization

Cancels a queued or running optimization and returns the updated
optimization resource.

```python theme={null}
result = client.optimization.cancel("your-optimization-id")
print(result["status"])   # "canceled"
```

<Tip>
  You can find the ID for any resource from the Ionworks Studio web app. The
  ID is displayed in the URL when you navigate to a resource's detail page.
</Tip>
