Skip to main content
The ionworks Python package exposes client.pipeline for running and managing pipelines. For installation and authentication, see the Python API client page.

Submitting a pipeline

client.pipeline.create() accepts either an iw.pipeline.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.
.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 emits each field under its wire name (for example, data_input → data).Do not use Pydantic’s model_dump() to build API payloads. model_dump() drops these discriminators and emits Python attribute names instead of wire names, so it produces a dict the API may reject.

Overriding submission metadata

create() accepts optional project_id, name, description, and options kwargs that override any values carried on the schema:
When project_id is omitted, the client falls back to the default configured on iw.api.Client(...) or the IONWORKS_PROJECT_ID environment variable.

Data references in pipelines

Use these prefixes to reference data sources in pipeline configs:
For inline DataFrames in pipeline configs, there is a 1,000-row limit. Upload larger datasets as measurements first, then reference them with db:measurement-id.
A db: measurement that is still being recorded grows as rows are appended, so an unpinned config reads more data on a later run than it did on the first. Add a time_range alongside data to pin the run to a fixed window of elapsed seconds:
Give duration instead of end to fix the window’s length from start: {"start": 0, "duration": 86400} is the first day of the test, however long it eventually runs. iw.pipeline.TimeRangeSpec(start=0, duration=86400) builds the same window. It is stored as the equivalent end, and passing both end and duration is rejected.See pinning a measurement to a time window.
The folder: scheme expects a directory containing time_series and steps files. Both .parquet and .csv are supported, and parquet is preferred when both are present. For example, a folder with time_series.parquet and steps.parquet (or .csv) loads correctly.

PyBaMM model support

You can hand an objective a PyBaMM model object directly instead of naming a built-in model. It is serialized for you when the config is built — there is no registration step and no string name to look up.
Model options travel with the object, so a configured model arrives configured:

Waiting for completion

Returns once the pipeline reaches a terminal status — completed, failed, or canceled. A cancelled pipeline is terminal, so it returns immediately rather than polling to the timeout. Pass raise_on_failure=False to get the terminal submission response back instead of raising when the pipeline fails or is cancelled. Otherwise a failed or cancelled pipeline raises an IonworksError whose error_code is "JOB_FAILED"; its data["detail"] carries the failure’s job_error_code and job_error to branch on; see Failed jobs.

Retrieving results

Each element returns a typed result object. Ask the pipeline for an element by the name you gave it, index the fitted parameters, and plot:
The class you get back reflects what the element did — OptimizationResult, PosteriorResult, ValidationResult and so on, all sharing a BaseResults base — so parameter_values, to_config() and the plots work the same way on any of them. result.results returns every element at once, keyed by name.
The overlay and the optimizer trace are fetched on first access, not when the result is built, so reading parameter_values costs nothing extra.
The raw payloads are still there if you want them — result.result for the pipeline’s final parameter values and result.element_results for per-element dicts, keyed exactly as you passed them to iw.pipeline.Pipeline(elements=...).

Element metadata

Elements write large fields (a validation’s full per-point comparison, for example) to a metadata blob rather than into element_results. The result object already reads what it needs from there; fetch the blob yourself only for fields it does not expose:

Element configs

list_elements returns a pipeline’s elements in run order, each with what it was run with (job_config) and what it produced (result, including its parameter_values):
Each element’s job_config leaves out parameters, optimizer and cost. get_element_job_config returns them for one element, by its id: the fixed parameter values, the fitted parameters and their bounds, and the optimizer settings.
It returns {} for an element that does not exist or that you cannot see.

Data-fit parameter trace

fit.plot_trace() draws the optimizer’s per-iteration progress, and fit.trace gives the raw records if you would rather plot them yourself — see Inspecting the parameter trace for the schema. Neither needs the element’s job ID; the result object resolves that for you.

Data-fit model-vs-data plot data

fit.plot_fit_results() above covers the common case. Drop to client.job.get_plot_data when you want the raw traces — to feed your own plotting stack, or to refetch detail as a user zooms. It returns the model-vs-data traces for a data-fit job — the same overlay Studio renders on the fit’s results page. A data-fit re-runs a validation on its best-fit parameters and stores the overlay in its own metadata, so you can fetch it directly from the fit’s job ID without adding a separate Validation element or parsing the raw metadata blob. The endpoint is keyed by job ID, which the elements listing carries:
Traces are decimated server-side to at most max_points points per series (default 2000, range 100–80000). For semantic zoom — refetching more detail as a user zooms in — pass x_min and x_max set to the current viewport:

Validation series

get_plot_data above reads a data fit’s stored overlay. For a validation job — or to read one objective of a fit without pulling the whole job’s metadata — use client.job.get_series. Reading one objective costs what that objective costs, rather than what the whole fit costs, so it stays usable on large runs where get_metadata does not. Ask what the job recorded first. get_series_channels returns names only, no samples:
Each objective reports which of the two solves it carries: "optimal" (the fitted parameters) and, when the job ran one, "baseline". Only genuine per-sample channels are listed, so a scalar recorded alongside the series does not appear. Then read one objective:
The window is applied before the row budget, so the points you get are spent on the range you asked for rather than thinned across the whole run. An objective, source, x_column, or column the job does not carry raises IonworksError — which is why it is worth calling get_series_channels first rather than guessing a name.

Listing pipelines

list() accepts id, name, description, status, created_by_email, created_at and updated_at (plus their _gt / _lt range variants), order_by, order, limit, and offset. The result is a PaginatedList: it behaves like a list — iterate it, index it, call len() on it — and also carries .items, .count, and .total, where .total counts every matching pipeline rather than the rows in this page.

Getting a single submission

End-to-end example

For more end-to-end examples (entry-only, calculation-only, datafit, validation), see packages/ionworks/examples/api/pipeline/ in the SDK repo.

Migrating from SimplePipeline

SimplePipeline has been removed. Calls to client.simple_pipeline or the /simple_pipelines REST endpoints now fail: the API answers every one with 410 Gone, and past SimplePipeline runs can no longer be fetched. Releases of ionworks made after the removal no longer include iw.pipeline.SimplePipeline or client.simple_pipeline; accessing either raises an AttributeError that links to this section. Use Pipeline instead, mapping each call as shown below.
iw.pipeline.Pipeline takes the same elements that iw.pipeline.SimplePipeline did, without its one-expensive-element limit, and each former client.simple_pipeline method has a client.pipeline method of the same name. Most code ports by swapping the class and the client. The differences are in what comes back and in a few arguments, listed below. A Pipeline run also appears in Studio, and its data fit stores its own model-vs-data overlay. The SDK handles these REST listing differences: client.pipeline.list(offset=...) still returns a PaginatedList with items, count, and total. The elements are identical before and after. The class, the client, and how you read the result change:
Before (removed)
After
For a validation, ask for the Validation element by name the same way and read .summary_stats from the ValidationResult it returns. See Retrieving results for everything else a typed result exposes.