Skip to main content
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:
Never commit API keys to version control. Use environment variables or a secrets manager for credential management.
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) before constructing the client, or pass api_key= explicitly to Ionworks(...).

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.
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:
  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.
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 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) before constructing the client.

DataFrame backend

By default, the client returns data as polars DataFrames. You can switch to pandas if your workflow requires it.
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:

Sub-clients

The Ionworks client exposes domain-specific sub-clients:

Discovering the API

client.capabilities() and client.schema(name) return the same content the discover-api agent skill 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.
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.
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.

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).
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 configured on the client.

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:
Otherwise the helper fetches the simulation to look it up:
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.
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(...):
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 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:
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.