Skip to main content

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.

Once data is uploaded, you can read it back using the ionworks-api Python client. This page covers listing and filtering resources, retrieving full measurement detail, local caching, in-Python plotting, and error handling. For installation and authentication, see the Python API client page. For uploading, see uploading data.
You can find the ID for any cell specification, instance, or measurement from the data visualization pages in Ionworks Studio. The ID is displayed in the URL and in the detail panels.

Listing resources

# List cell specifications (first page)
specs = client.cell_spec.list()
for spec in specs[:5]:
    print(f"  - {spec.name} (form_factor: {spec.form_factor})")

# Get a specific cell spec with full nested data
full_spec = client.cell_spec.get(spec_id)
print(f"Capacity: {full_spec.ratings['capacity']['value']} "
      f"{full_spec.ratings['capacity']['unit']}")

# List instances for a spec and pick the first
instances = client.cell_instance.list(spec_id)
instance = instances[0]

# List measurements for an instance
measurements = client.cell_measurement.list(instance.id)

Filtering and ordering

All list() methods accept keyword-only filter parameters so you can narrow results server-side instead of fetching everything and filtering in Python.
# Search cell specs by name (case-insensitive substring match)
specs = client.cell_spec.list(name="graphite")

# Exact name match
specs = client.cell_spec.list(name_exact="NCM622/Graphite Coin Cell")

# Filter by form factor (cell specs only)
specs = client.cell_spec.list(form_factor="R2032")

# Filter by creator
specs = client.cell_spec.list(created_by_email="jane")

# Date range filters
specs = client.cell_spec.list(
    created_after="2026-01-01T00:00:00Z",
    created_before="2026-04-01T00:00:00Z",
)

# Sort results
specs = client.cell_spec.list(order_by="created_at", order="desc")
Filters work the same way across all three resource types and can be combined with pagination and ordering in a single call:
instances = client.cell_instance.list(
    spec_id,
    limit=50,
    offset=0,
    name="batch-A",
    order_by="updated_at",
    order="desc",
)

measurements = client.cell_measurement.list(
    instance_id,
    measurement_type="time_series",
    created_after="2026-03-01T00:00:00Z",
    order_by="created_at",
    order="asc",
)
Cell measurements support additional date filters for the measurement start time:
measurements = client.cell_measurement.list(
    instance_id,
    started_after="2026-03-01T00:00:00Z",
    started_before="2026-03-31T23:59:59Z",
)

Filter parameters

ParameterTypeDescriptionAvailable on
namestrCase-insensitive substring match on name.All
name_exactstrExact match on name. Takes precedence over name.All
form_factorstrExact match on form factor.cell_spec only
measurement_typestrFilter by measurement type ("time_series", "properties", "file").cell_measurement only
created_by_emailstrCase-insensitive substring match on creator email.All
created_afterstrISO datetime; records created after this time.All
created_beforestrISO datetime; records created before this time.All
updated_afterstrISO datetime; records updated after this time.All
updated_beforestrISO datetime; records updated before this time.All
started_afterstrISO datetime; measurements started after this time.cell_measurement only
started_beforestrISO datetime; measurements started before this time.cell_measurement only
order_bystrColumn to sort by ("name", "created_at", "updated_at", or "start_time" for measurements).All
orderstrSort direction: "asc" or "desc".All
Filter parameters can be combined freely with each other and with the limit/offset pagination parameters. The .total property on the returned PaginatedList reflects the total count after filters are applied.

Pagination

All list() calls return a PaginatedList. The limit and offset parameters control which page is fetched.
page = client.cell_spec.list(limit=50, offset=0)
print(f"Showing {page.count} of {page.total} specs")

next_page = client.cell_spec.list(limit=50, offset=50)
ParameterTypeDefaultDescription
limitintServer default (1000)Maximum number of items to return (1 to 1000).
offsetint0Number of items to skip before returning results.
The returned PaginatedList behaves like a regular Python list (iterate, index, check length) and also exposes:
PropertyDescription
.itemsThe list of results for the current page.
.totalTotal number of matching records across all pages.
.countNumber of items in the current page.
To iterate through all results:
all_specs = []
offset = 0
limit = 100
while True:
    page = client.cell_spec.list(limit=limit, offset=offset)
    all_specs.extend(page.items)
    if len(all_specs) >= page.total:
        break
    offset += limit

Measurement detail

client.cell_measurement.detail() retrieves the full measurement and adapts its response based on the measurement type.
measurement_detail = client.cell_measurement.detail(measurement_id)
Returns time series data, step statistics, and cycle metrics:
FieldDescription
measurementMeasurement metadata (name, protocol, test setup, notes)
time_seriesFull time series data as a DataFrame (polars by default)
stepsStep-level statistics as a DataFrame
cyclesCycle-level metrics (capacity, efficiency, etc.) as a DataFrame
specification_idID of the parent cell specification (use client.cell_spec.get(id) to fetch)
instance_idID of the parent cell instance (use client.cell_instance.get(spec_id, id) to fetch)
detail = client.cell_measurement.detail(measurement_id)
print(f"Time series shape: {detail.time_series.shape}")
print(detail.cycles.head())

Local caching

The ionworks-api client automatically caches measurement data to disk so repeated reads are fast and avoid unnecessary API calls. Caching is enabled by default and applies to the steps, cycles, steps_and_cycles, and time_series methods on cell_measurement. When you call a method like client.cell_measurement.steps(measurement_id), the client checks a local cache directory before making an API request. If a cached copy exists and hasn’t expired, it’s returned directly. Otherwise, the client fetches from the API, caches the result, and returns it. Cached data is stored as Parquet files in ~/.ionworksdata_cache by default and expires after one hour.

Skipping the cache

Every data-fetching method accepts a use_cache parameter. Set it to False to force a fresh API call without reading from or writing to the local cache:
steps = client.cell_measurement.steps(measurement_id, use_cache=False)
time_series = client.cell_measurement.time_series(measurement_id, use_cache=False)

Configuring the cache

import ionworks

ionworks.set_cache_directory("/path/to/custom/cache")
ionworks.set_cache_ttl(7200)              # 2 hours
ionworks.set_cache_ttl(None)              # never expire
ionworks.set_cache_enabled(False)
ionworks.set_cache_enabled(True)
deleted_count = ionworks.clear_cache()
FunctionDescription
set_cache_enabled(bool)Enable or disable caching globally.
get_cache_enabled()Return whether caching is currently enabled.
set_cache_directory(path)Set the directory for cache files. Default: ~/.ionworksdata_cache.
set_cache_ttl(seconds)Set the TTL in seconds. Pass None to disable expiration. Default: 3600.
get_cache_directory()Return the current cache directory path.
get_cache_ttl()Return the current TTL value.
clear_cache()Delete all cached files and return the number deleted.
Cache configuration is global. Changes affect all subsequent API calls in the same Python process.

Plotting from Python

DataLoader includes a plot_data() method for quick matplotlib-based visualization of measurement data. The plot displays voltage and current over time, with an additional temperature subplot when temperature data is available.
from ionworksdata import DataLoader

loader = DataLoader.from_db("measurement-id-here")
fig, ax = loader.plot_data()
The method returns a matplotlib (Figure, Axes) tuple so you can customize the plot further. Pass show=True to display the plot immediately:
fig, ax = loader.plot_data(show=True)
plot_data() automatically loads time series data from the server if it hasn’t been fetched yet.
For the interactive in-browser viewer (with filters, step overlays, SQL), see visualizing data.

Inline time series size limit

When you pass a pandas or polars DataFrame directly in an API call (for example, as part of a pipeline configuration), the client enforces a maximum of 1,000 rows for inline time series data. Larger datasets should be uploaded as measurements first, then referenced by ID.
from ionworks import MeasurementValidationError, IonworksError

try:
    client.pipeline.run(config_with_large_inline_df)
except MeasurementValidationError as e:
    # Specifically handle the size limit violation
    print(e)
    # "Time series has 5000 rows, which exceeds the maximum of 1000 rows
    #  for inline data. Upload the data as a measurement using
    #  client.cell_measurement.create() and reference it with
    #  'db:<measurement_id>' or iwdata.DataLoader.from_db(MEASUREMENT_ID)
    #  instead."
except IonworksError as e:
    print(e)
To work with larger datasets, upload first and reference by ID:
bundle = client.cell_measurement.create(instance_id, measurement_data)

from ionworksdata import DataLoader
loader = DataLoader.from_db(bundle.measurement.id)

Exporting DataLoader configurations

If you have a DataLoader that references a database measurement and you want to export a self-contained configuration (for example, to share with a colleague), use to_local() to embed the data inline:
from ionworksdata import DataLoader

loader = DataLoader.from_db("measurement-id-here")
local_loader = loader.to_local()

# Now to_config() returns the full data instead of a DB reference
config = local_loader.to_config()
to_local() fetches all time series and step data from the server immediately. For very large measurements, this may take a moment.

Error handling

The client raises exceptions for common error cases:
  • Missing or invalid API credentials
  • API request errors (raises IonworksError with details)
  • Inline time series exceeding 1,000 rows (raises MeasurementValidationError, a subclass of IonworksError)
from ionworks import IonworksError

try:
    client.cell_spec.list()
except IonworksError as e:
    print(f"API error: {e}")
See inline time series size limit above for the MeasurementValidationError handling pattern.

API error format

All API errors return a consistent JSON structure:
{
  "error_code": "CONFLICT",
  "message": "Cell specification with this name already exists",
  "detail": {
    "resource_type": "cell_specification",
    "resource_name": "My Cell Spec",
    "existing_id": "abc-123"
  }
}
FieldTypeDescription
error_codestringMachine-readable error code (e.g. NOT_FOUND, CONFLICT, BAD_REQUEST).
messagestringHuman-readable description of what went wrong.
detailobject | nullOptional additional context about the error. Contents vary by error type; may be absent for some errors.
Common HTTP status codes:
StatusError codeDescription
400BAD_REQUESTThe request is invalid or missing required fields.
403FORBIDDENYou don’t have permission to access this resource. Also returned for missing or invalid API credentials (the API does not use 401).
404NOT_FOUNDThe requested resource does not exist.
409CONFLICTA resource with the same name or identifier already exists. The detail field includes the existing_id when available.
429USAGE_LIMIT_REACHEDYour organization has exceeded its usage quota for this billing cycle.

Full API reference

For the complete Python API reference, see the ionworks-api documentation.

Next steps

Visualize data

Explore uploaded data with the interactive in-browser viewer.

Uploading data

End-to-end upload workflow for specs, instances, and measurements.

Measurements

The three measurement types in detail.

Simulation API

Run simulations and pipelines via the Python API.