The ionworks-api Python package
provides a programmatic interface for managing resources, running simulations,
submitting pipelines, and uploading data in Ionworks Studio.
Driving Ionworks from a coding agent? The
Ionworks Agentic Toolkit ships SDK-aware skills for Claude Code,
Codex, and other agents — including an install skill that runs through
this page’s setup for you.
Installation
Install the package from the repository:
Authentication
Get your API key from the Ionworks account settings and configure it:
from ionworks import Ionworks
# Option 1: Environment variable (recommended)
# Set IONWORKS_API_KEY in your environment or .env file
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)
)
Never commit API keys to version control. Use environment variables or a
.env file for credential management.
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.
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.
Default project
Most sub-clients (pipelines, studies, optimizations, cell specifications, …)
operate within a project. 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:
- The
project_id= argument passed to Ionworks(...).
- The
IONWORKS_PROJECT_ID environment variable.
- Otherwise, no default is set — methods that need a project will raise
ValueError unless project_id is passed at the call site.
# 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.
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.
Environment variables
The client loads .env files automatically via
python-dotenv.
| 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 DataFrames. You can
switch to pandas if your workflow requires it.
# 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:
# 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 | Core Concepts API |
| Models | client.model | Build API |
| Parameterized models | client.parameterized_model | Build API |
| Studies | client.study | Simulate API |
| Protocols | client.protocol | Simulate API |
| Simulations | client.simulation | Simulate API |
| Pipelines | client.pipeline | Simulate API |
| Simple pipelines | client.simple_pipeline | Simulate API |
| Optimizations | client.optimization | Optimize API |
| Cell specifications | client.cell_spec | Uploading data |
| Cell instances | client.cell_instance | Uploading data |
| Cell measurements | client.cell_measurement | Measurements |
| Jobs | client.job | Canceling jobs |
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.
# 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.
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:
# 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.
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.