> ## 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

> Submit, monitor, list, and retrieve results for pipeline jobs with the ionworks-api Python client.

The [`ionworks-api`](https://github.com/ionworks/ionworks-api) Python package exposes `client.pipeline` for running and managing [pipelines](/pipelines/overview). For installation and authentication, see the [Python API client](/api-client) page.

## Submitting a pipeline

```python theme={null}
import ionworks_schema as iws
from ionworks import Ionworks

# Reads IONWORKS_API_KEY and IONWORKS_PROJECT_ID from the environment.
client = Ionworks()

pipeline = iws.Pipeline(
    {
        "known": iws.direct_entries.DirectEntry(
            parameters={"Ambient temperature [K]": 298.15},
        ),
        "Q_pos": iws.calculations.ElectrodeCapacity(electrode="positive"),
    },
    name="Capacity pipeline",
)

submission = client.pipeline.create(pipeline)
print(f"Pipeline ID: {submission.id}")
print(f"Status: {submission.status}")
```

`client.pipeline.create()` accepts either an `iws.Pipeline` schema instance or the dict returned by `.to_config()`. Schema instances are validated locally before submission, so shape errors surface immediately.

### Serializing a pipeline to JSON

Use `.to_config()` when you want to inspect, cache, or transport the pipeline payload as JSON — for example to review it before submission, commit it to version control, or hand it off to another process.

```python theme={null}
import json

config = pipeline.to_config()

# Inspect or save
with open("pipeline_config.json", "w") as f:
    json.dump(config, f, indent=2)

# Submit later, or from another process
submission = client.pipeline.create(config)
```

<Warning>
  `.to_config()` is the only supported serializer. It emits the discriminators pipeline elements and objectives need (top-level elements are keyed on `element_type`, and nested schemas carry their own `type`), and applies the schema's field-name mappings (for example, `data_input` → `data`).

  Do **not** use Pydantic's `model_dump()` to build API payloads. `model_dump()` drops these discriminators and skips field-name mappings, so it produces a dict the API may reject.
</Warning>

### Overriding submission metadata

`create()` accepts optional `project_id`, `name`, `description`, and `options` kwargs that override any values carried on the schema:

```python theme={null}
submission = client.pipeline.create(
    pipeline,
    project_id="your-project-id",
    name="Custom run name",
    options={"live_progress_updates": True},
)
```

When `project_id` is omitted, the client falls back to the default configured on `Ionworks(...)` or the `IONWORKS_PROJECT_ID` environment variable.

## Waiting for completion

```python theme={null}
submission = client.pipeline.wait_for_completion(
    submission.id,
    timeout=600,        # seconds (default: 600)
    poll_interval=2,    # seconds between polls (default: 2)
    verbose=True,       # print status updates (default: True)
)
```

Pass `raise_on_failure=False` to get the failed submission response back instead of raising when the pipeline errors out.

## Retrieving results

```python theme={null}
result = client.pipeline.result(submission.id)

# Final parameter values produced by the pipeline
print(result.result)

# Per-element output keyed by the element name used in the Pipeline
print(result.element_results["Q_pos"])
```

`result.element_results` mirrors the keys you passed to `iws.Pipeline(elements=...)`.

### Element metadata

Some elements (notably `Validation`) write extra metadata that isn't included in `element_results`. Fetch it with:

```python theme={null}
metadata = client.pipeline.get_element_metadata(submission.id, "validate")
```

### Data-fit parameter trace

Data-fit elements log the optimizer's per-iteration progress to the element's
job metadata. Pull it down with `client.job.get_parameter_trace` using the
element's job ID — see [Inspecting the parameter trace](/optimize/api#inspecting-the-parameter-trace)
for the full schema.

The element's `job_id` lives in the pipeline's elements list rather than in
`element_results`, so fetch the list and pick out the data-fit element by name:

```python theme={null}
elements = client.get(f"/pipelines/{submission.id}/elements")
fit_job_id = next(e["job_id"] for e in elements if e["name"] == "fit")

trace = client.job.get_parameter_trace(fit_job_id)
```

## Listing pipelines

```python theme={null}
# Defaults to the project on Ionworks(...) or IONWORKS_PROJECT_ID
pipelines = client.pipeline.list()

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

# Limit the number of results
pipelines = client.pipeline.list(limit=10)
```

## Getting a single submission

```python theme={null}
submission = client.pipeline.get(submission.id)
print(submission.status)  # "pending", "running", "completed", or "failed"
```

## SimplePipeline

A [`SimplePipeline`](/guide/pipelines/simple-pipelines) is a lightweight alternative to `Pipeline` for workflows with **at most one expensive element** — a single `DataFit` or `Validation`. It runs fire-and-forget and returns a flat result containing `parameter_values`, `cost`, and (for validation) `summary_stats`. Submit and poll it through `client.simple_pipeline`.

### Building the config

`SimplePipeline` inherits everything from `Pipeline` and adds client-side validation that rejects configs with more than one expensive element:

```python theme={null}
import ionworks_schema as iws

objective = iws.objectives.CurrentDriven(data_input="path/to/discharge.csv")

pipeline = iws.SimplePipeline(
    elements={
        "initial_params": iws.direct_entries.DirectEntry(
            parameters={"Negative particle diffusivity [m2.s-1]": 2e-14},
        ),
        "fit": iws.DataFit(
            objectives={"cycle": objective},
            parameters={
                "Negative particle diffusivity [m2.s-1]": iws.Parameter(
                    "Negative particle diffusivity [m2.s-1]",
                    bounds=(1e-14, 1e-13),
                    initial_value=2e-14,
                )
            },
            cost=iws.costs.RMSE(),
            optimizer=iws.parameter_estimators.ScipyDifferentialEvolution(
                maxiter=10
            ),
        ),
    },
    name="My Fit",
)

config = pipeline.to_config()
```

<Note>
  If you pass more than one `DataFit` or `Validation` element, `SimplePipeline` raises a `ValueError` immediately — no need to wait for a server-side rejection.
</Note>

### Submitting and polling

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

client = Ionworks()

# Submit the config
sp = client.simple_pipeline.create(config, name=pipeline.name)
# sp.status == "pending"

# Wait for completion (polls automatically)
result = client.simple_pipeline.wait_for_completion(sp.id, timeout=600)

# Read results
print(result.result["parameter_values"])
# {"Negative particle diffusivity [m2.s-1]": 5.3e-14}
print(result.result["cost"])
```

### Validation pipelines

`SimplePipeline` also supports a single `Validation` element. The result includes `summary_stats` alongside `parameter_values`.

```python theme={null}
objective = iws.objectives.CurrentDriven(data_input="path/to/cycle.csv")

validation_pipeline = iws.SimplePipeline(
    elements={
        "validate": iws.Validation(
            objectives={"cycle": objective},
        ),
    },
    name="Validate fitted model",
)

sp = client.simple_pipeline.create(
    validation_pipeline.to_config(), name=validation_pipeline.name
)
result = client.simple_pipeline.wait_for_completion(sp.id, timeout=600)
print(result.result["summary_stats"])
```

## End-to-end example

```python theme={null}
import pybamm
import ionworks_schema as iws
from ionworks import Ionworks

client = Ionworks()

pipeline = iws.Pipeline(
    {
        "known": iws.direct_entries.DirectEntry(
            parameters={"Ambient temperature [K]": 298.15},
        ),
        "fit": iws.DataFit(
            objectives={
                "1C": iws.objectives.CurrentDriven(
                    data_input="file:examples/data/chen_synthetic_1C/time_series.csv",
                    options={"model": pybamm.lithium_ion.SPMe()},
                ),
            },
            parameters={
                "Negative particle diffusivity [m2.s-1]": iws.Parameter(
                    "Negative particle diffusivity [m2.s-1]",
                    initial_value=2e-14,
                    bounds=(1e-14, 1e-13),
                ),
            },
            cost=iws.costs.RMSE(),
            optimizer=iws.optimizers.DifferentialEvolution(),
        ),
    },
    name="SPMe diffusivity fit",
)

submission = client.pipeline.create(pipeline)
client.pipeline.wait_for_completion(submission.id)

result = client.pipeline.result(submission.id)
print(result.element_results["fit"])
```

<Note>
  For more end-to-end examples (entry-only, calculation-only, datafit, validation), see [`packages/ionworks-api/examples/pipeline/`](https://github.com/ionworks/ionworks-api/tree/main/examples/pipeline) in the SDK repo.
</Note>
