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

> Install and configure the ionworks-api Python package: authentication, dataframe backend, retries, and sub-clients reference

The [`ionworks-api`](https://github.com/ionworks/ionworks-api) Python package
provides a programmatic interface for managing resources, running simulations,
submitting pipelines, and uploading data in Ionworks Studio.

<Tip>
  Driving Ionworks from a coding agent? The
  [Ionworks Agentic Toolkit](/agents) ships SDK-aware skills for Claude Code,
  Codex, and other agents — including an `install` skill that runs through
  this page's setup for you.
</Tip>

## Installation

Install the package from the repository:

```bash theme={null}
pip install ionworks-api
```

## Authentication

Get your API key from the Ionworks account settings and configure it:

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

# Option 1: Environment variable (recommended)
# Set IONWORKS_API_KEY in your shell environment before constructing the client
client = Ionworks()

# Option 2: Direct configuration
client = Ionworks(api_key="your_key")

# Option 3: Custom timeout and retry settings
client = Ionworks(
    timeout=30,       # Request timeout in seconds (default: 10)
    max_retries=3     # Max retries on failure (default: 5)
)
```

<Warning>
  Never commit API keys to version control. Use environment variables or a
  secrets manager for credential management.
</Warning>

<Note>
  Starting in `ionworks-api` 0.10.0, importing `ionworks` no longer
  auto-loads a `.env` file. Set `IONWORKS_API_KEY` in your shell
  environment, load the `.env` file yourself (for example with
  [python-dotenv](https://pypi.org/project/python-dotenv/)) before
  constructing the client, or pass `api_key=` explicitly to `Ionworks(...)`.
</Note>

### Verify your API key

Use `client.whoami()` to confirm which user and organization the configured
API key resolves to. This is the recommended way to debug `401`/`403` errors,
or to check why you're seeing data from the wrong organization.

```python theme={null}
me = client.whoami()
print(me["email"], me["authorized_organization"])
# alice@example.com {'id': 'org_abc123', 'name': 'Acme Battery'}
```

The response has two organization fields and the distinction matters:

* `authorized_organization` — the org this request is **authorized as**. For
  SDK calls, this is the org the configured API key was issued for, and is
  the source of truth for permission checks on every request the client
  makes. It's `None` if no org context could be resolved.
* `organizations` — the user's full membership list (every org they belong
  to). This is a different question and is **not** what permission checks
  run against.

If the `id` or `name` in `authorized_organization` doesn't match what you
expect, the wrong key is in use — regenerate one for the correct org from
your [account settings](https://app.ionworks.com/dashboard/settings).

## Default project

Most sub-clients (pipelines, studies, optimizations, cell specifications, ...)
operate within a [project](/core-concepts/projects-studies). Rather than
threading a `project_id` through every call, you can configure a default once
on the client. Sub-client methods that take a `project_id` argument fall back
to this default when one isn't passed explicitly.

The client resolves the default in this order:

1. The `project_id=` argument passed to `Ionworks(...)`.
2. The `IONWORKS_PROJECT_ID` environment variable.
3. Otherwise, no default is set — methods that need a project will raise
   `ValueError` unless `project_id` is passed at the call site.

```python theme={null}
# Option 1: Environment variable (recommended)
# Set IONWORKS_PROJECT_ID in your environment or .env file
client = Ionworks()

# Option 2: Pass to the client constructor
client = Ionworks(project_id="your-project-id")

# Override on a per-call basis when needed
client.study.list(project_id="other-project-id")
```

You can find your project ID in the URL of the project settings page:
`https://app.ionworks.com/dashboard/projects/<project-id>/settings`.

<Note>
  The `PROJECT_ID` environment variable is still accepted for backwards
  compatibility but is deprecated. Set `IONWORKS_PROJECT_ID` instead — using
  the old name emits a `DeprecationWarning` and will stop working in a future
  release.
</Note>

### Environment variables

The client reads the following variables from your shell environment when
constructed. `.env` files are **not** loaded automatically — populate the
environment yourself (for example by sourcing the file, or by calling
[python-dotenv](https://pypi.org/project/python-dotenv/)) before
constructing the client.

| Variable                     | Required                 | Default                    | Description                                |
| ---------------------------- | ------------------------ | -------------------------- | ------------------------------------------ |
| `IONWORKS_API_KEY`           | Yes                      | —                          | API key from your account settings.        |
| `IONWORKS_API_URL`           | No                       | `https://api.ionworks.com` | API base URL.                              |
| `IONWORKS_PROJECT_ID`        | For project-scoped calls | —                          | Default project ID for sub-client methods. |
| `IONWORKS_DATAFRAME_BACKEND` | No                       | `polars`                   | DataFrame backend: `polars` or `pandas`.   |

## DataFrame backend

By default, the client returns data as [polars](https://pola.rs/) DataFrames. You can
switch to [pandas](https://pandas.pydata.org/) if your workflow requires it.

```python theme={null}
# Option 1: Set via constructor
client = Ionworks(dataframe_backend="pandas")

# Option 2: Set via environment variable
# IONWORKS_DATAFRAME_BACKEND=pandas

# Option 3: Set at runtime
from ionworks import set_dataframe_backend, get_dataframe_backend

set_dataframe_backend("pandas")
print(get_dataframe_backend())  # "pandas"
```

All methods that return DataFrames (time series, steps, cycles) respect this setting.

## Timeout and retry behavior

The client automatically retries failed requests on connection errors, timeouts, and server errors (5xx). By default:

* Requests time out after **10 seconds**
* Failed requests retry up to **5 times** with exponential backoff
* **Dropped connections** (the server closed the socket before the request reached the application — common on reused keep-alive connections) are retried for **all methods**, including POST and PATCH. The request never reached the server, so resending is safe.
* **Read timeouts** and **5xx responses** are only retried for idempotent methods (**GET** and **DELETE**). The server may have already processed a POST or PATCH, so resubmitting could duplicate the operation.

You can customize these settings:

```python theme={null}
# Longer timeout for large uploads
client = Ionworks(timeout=60)

# Disable retries
client = Ionworks(max_retries=0)
```

## Sub-clients

The `Ionworks` client exposes domain-specific sub-clients:

| Sub-client                 | Access                             | Documentation                                                  |
| -------------------------- | ---------------------------------- | -------------------------------------------------------------- |
| Projects                   | `client.project`                   | [Projects API](/core-concepts/api)                             |
| Models                     | `client.model`                     | [Build API](/build/api)                                        |
| Parameterized models       | `client.parameterized_model`       | [Build API](/build/api)                                        |
| Studies                    | `client.study`                     | [Simulate API](/simulate/api)                                  |
| Protocols                  | `client.protocol`                  | [Simulate API](/simulate/api)                                  |
| Simulations                | `client.simulation`                | [Simulate API](/simulate/api)                                  |
| Pipelines                  | `client.pipeline`                  | [Simulate API](/simulate/api)                                  |
| Simple pipelines           | `client.simple_pipeline`           | [Simulate API](/simulate/api#running-simple-pipelines)         |
| Optimizations              | `client.optimization`              | [Optimize API](/optimize/api)                                  |
| Cell specifications        | `client.cell_spec`                 | [Uploading data](/data/uploading)                              |
| Cell instances             | `client.cell_instance`             | [Uploading data](/data/uploading)                              |
| Cell measurements          | `client.cell_measurement`          | [Measurements](/data/measurements)                             |
| Sites                      | `client.site`                      | [Lab view](/data/lab)                                          |
| Cyclers                    | `client.cycler`                    | [Lab view](/data/lab)                                          |
| Channels                   | `client.channel`                   | [Lab view](/data/lab)                                          |
| Lab occupancy              | `client.lab`                       | [Lab view](/data/lab#querying-occupancy-from-the-sdk)          |
| Analyses                   | `client.analysis`                  | [Analyses](/data/analyses)                                     |
| Materials                  | `client.material`                  | [Materials & property datasets](/data/materials#python-client) |
| Material property datasets | `client.material_property_dataset` | [Materials & property datasets](/data/materials#python-client) |
| Jobs                       | `client.job`                       | [Canceling jobs](#canceling-jobs)                              |
| Web app URLs               | `client.urls`                      | [Web app URL helpers](#web-app-url-helpers)                    |

## Discovering the API

`client.capabilities()` and `client.schema(name)` return the same content
the `discover-api` [agent skill](/agents) is built on. Use them from a
notebook or script to introduce yourself — or a coding agent you're
scripting against — to the platform's data hierarchy and to fetch the
authoritative JSON Schemas for measurements and UCP protocols.

```python theme={null}
caps = client.capabilities()
print(caps["domain_context"]["hierarchy"])
# organization -> project -> cell_specification -> cell_instance
# -> cell_measurement -> [time_series, steps, cycles, analysis] ...

# Standard time-series column names, required fields, sign conventions
data_schema = client.schema("data")

# Full UCP JSON Schema plus runnable examples
protocol_schema = client.schema("protocol")
```

`capabilities()` also returns pointers to the OpenAPI spec
(`/openapi.json`) and per-resource JSON Schema endpoints under
`caps["schemas"]`, so agents can fetch the exact shape of any create/update
payload before calling it.

<Tip>
  Coding agents driving Ionworks should call `client.capabilities()` and
  `client.schema(name)` before generating request bodies — the responses
  reflect the running server, so agents never guess at endpoint shapes or
  column names that may have drifted.
</Tip>

## Web app URL helpers

`client.urls` builds links to resource pages in the Ionworks web app
(Ionworks Studio) without requiring you to hand-construct URLs from entity
IDs. Use it to surface clickable links from notebooks, scripts, dashboards,
or Slack/email reports so collaborators can jump straight to the resource in
the web app.

Every helper runs entirely locally — no network call — except
`client.urls.simulation()`, which fetches the simulation once when
`parameterized_model_id` isn't supplied (see below).

```python theme={null}
client = Ionworks(project_id="proj_abc")

client.urls.study("study_xyz")
# https://app.ionworks.com/dashboard/projects/proj_abc/studies/study_xyz

client.urls.parameterized_model("pm_123")
# https://app.ionworks.com/dashboard/projects/proj_abc/parameterized-models/pm_123
```

Each helper takes the resource's own ID plus whatever parent IDs the route
requires. `project_id` is optional — it falls back to the
[default project](#default-project) configured on the client.

| Helper                                                                                | Returns a link to                                              |
| ------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `client.urls.project(project_id=None)`                                                | Project landing page (its studies list).                       |
| `client.urls.study(study_id, project_id=None)`                                        | Study detail page.                                             |
| `client.urls.model(model_id, project_id=None)`                                        | Model detail page.                                             |
| `client.urls.parameterized_model(parameterized_model_id, project_id=None)`            | Parameterized model detail page.                               |
| `client.urls.simulation(simulation_id, parameterized_model_id=None, project_id=None)` | Simulation detail page (nested under its parameterized model). |
| `client.urls.protocol(protocol_id, project_id=None)`                                  | Protocol detail page.                                          |
| `client.urls.pipeline(pipeline_id, project_id=None)`                                  | Pipeline detail page.                                          |
| `client.urls.optimization(optimization_id, project_id=None)`                          | Optimization detail page.                                      |
| `client.urls.material(material_id, project_id=None)`                                  | Material detail page.                                          |
| `client.urls.cell_specs(project_id=None)`                                             | Cell specifications list page.                                 |
| `client.urls.cell_instances(spec_id, project_id=None)`                                | Cell instances list page nested under a spec.                  |
| `client.urls.measurement(measurement_id, project_id=None)`                            | Measurement detail page.                                       |

### Simulation URLs

A simulation's web-app route is nested under its parameterized model. If you
already know the `parameterized_model_id`, pass it in to avoid a network
call:

```python theme={null}
client.urls.simulation("sim_1", parameterized_model_id="pm_123")
```

Otherwise the helper fetches the simulation to look it up:

```python theme={null}
# One GET to client.simulation.get(simulation_id), then builds the URL.
client.urls.simulation("sim_1")
```

If the simulation has no `parameterized_model_id` (e.g. a study-only
simulation), the helper raises `ValueError` and you must pass the parent
explicitly.

## Canceling jobs

You can cancel running jobs (simulations, pipelines, and optimizations) using
the Python API. Each job has a unique ID that you can use to cancel it.

```python theme={null}
# Cancel a job by ID
client.job.cancel(job_id="job_abc123")
```

Cancelling a parent job automatically cancels all of its child jobs. For
example, cancelling a pipeline cancels all of its running elements.

## Resubmitting a failed pipeline

If a pipeline stops because its first failed element could not be submitted
(`error_code = SUBMISSION_FAILED`), you can resubmit it without rebuilding the
configuration. Completed elements are preserved; execution resumes from the
failed element.

The Python client does not yet expose a resubmit method, so call the REST
endpoint directly as a temporary workaround. Resubmission goes through the
generic jobs endpoint, and a pipeline is identified by its job ID — the same
ID you pass to `client.pipeline.get(...)`:

```bash theme={null}
curl -X POST \
  -H "Authorization: Bearer $IONWORKS_API_KEY" \
  https://api.ionworks.com/jobs/{pipeline_id}/resubmit
```

Resubmission only applies to `SUBMISSION_FAILED` errors. For execution
timeouts or configuration mistakes, create a new pipeline with the corrected
configuration. For internal errors, wait briefly and create a new pipeline if
the issue persists. See [Handling pipeline
failures](/guide/pipelines/overview#handling-pipeline-failures) for details.

## Retrieving job metadata

Standard job-detail responses strip fields that can grow large — submitted
configs, full result payloads, and serialized PyBaMM models — so listing
and polling stay fast even for pipelines or optimizations with heavy inline
data. When you actually need those fields, fetch them with
`client.job.get_metadata`:

```python theme={null}
# Fetch the full, untruncated metadata for a job
metadata = client.job.get_metadata(job_id="job_abc123")

# Validation payloads, when present
results = metadata.get("validation_results")
plot_config = metadata.get("validation_plot_config")
```

Use this when you need to:

* Re-run a job from its original submitted config without keeping a local
  copy.
* Audit the exact inputs that produced a result.
* Read large result blobs — for example, optimization traces, or the
  `validation_results` and `validation_plot_config` produced by pipeline
  validations — that aren't included in the default job response.

The returned object is a plain `dict` parsed from the job's
`metadata.json.gz` blob. If a job has no metadata file (for example, it
failed before writing one) the call raises an error — wrap it in a
`try`/`except` if you're iterating over many jobs.

<Tip>
  For pipelines, `client.pipeline.result(pipeline_id)` is still the easiest
  way to get parsed, fitted parameter values. Use `client.job.get_metadata`
  when you need the raw, unparsed payload backing a job.
</Tip>
