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.

Uploading data to Ionworks Studio follows a three-step hierarchy: create the cell specification (the cell’s blueprint), create a cell instance (a specific physical cell), then upload measurements against that instance. For installation and authentication, see the Python API client page. For reading data back, see reading data.

Upload workflow

1

Create or get a cell specification

The spec defines the cell’s materials, electrical ratings, and components.
2

Create or get a cell instance

A specific physical cell, linked to a spec.
3

Upload measurements

Attach time series, properties, or file measurements to the instance.

Step 1: Create or get a cell specification

cell_spec = client.cell_spec.create_or_get(
    {
        "name": "NCM622/Graphite Coin Cell",
        "form_factor": "R2032",
        "manufacturer": "Custom Cells",
        "ratings": {
            "capacity": {"value": 0.002, "unit": "A*h"},
            "voltage_min": {"value": 2.5, "unit": "V"},
            "voltage_max": {"value": 4.2, "unit": "V"},
        },
        "cathode": {
            "properties": {"loading": {"value": 12.3, "unit": "mg/cm**2"}},
            "material": {"name": "NCM622", "manufacturer": "BASF"},
        },
        "anode": {
            "properties": {"loading": {"value": 6.5, "unit": "mg/cm**2"}},
            "material": {"name": "Graphite", "manufacturer": "Customcells"},
        },
    }
)
See Cells for the full set of cell specification fields.

Step 2: Create or get a cell instance

cell_instance = client.cell_instance.create_or_get(
    cell_spec.id,
    {
        "name": "NCM622-GR-001",
        "batch": "BATCH-2024-001",
        "date_manufactured": "2024-01-20",
        "measured_properties": {
            "cathode": {"loading": {"value": 12.1, "unit": "mg/cm**2"}},
            "anode": {"loading": {"value": 6.4, "unit": "mg/cm**2"}},
        },
    },
)
Cell instance fields:
FieldDescription
nameUnique identifier for this cell
batchBatch or lot number
date_manufacturedManufacturing date (optional)
measured_propertiesCell-specific measured values that differ from the spec (e.g., actual measured capacity, electrode loadings)

Step 3: Upload a measurement with time series

import pandas as pd

time_series = pd.DataFrame(
    {
        "Time [s]": [0, 1, 2, 3, 4, 5],
        "Voltage [V]": [3.0, 3.2, 3.5, 3.8, 4.0, 4.2],
        "Current [A]": [-0.002, -0.002, -0.002, -0.002, -0.002, -0.002],
        "Step count": [0, 0, 0, 1, 1, 1],
        "Cycle count": [0, 0, 0, 0, 0, 0],
        "Step from cycler": [1, 1, 1, 2, 2, 2],
        "Cycle from cycler": [0, 0, 0, 0, 0, 0],
    }
)

measurement_data = {
    "measurement": {
        "name": "Formation Cycle 1",
        "protocol": {
            "name": "CC-CV charge at C/10 to 4.2V",
            "ambient_temperature_degc": 25,
        },
        "test_setup": {
            "cycler": "Biologic VMP3",
            "operator": "Jane Smith",
        },
        "notes": "Formation cycle - first charge",
    },
    "time_series": time_series,
}

bundle = client.cell_measurement.create(cell_instance.id, measurement_data)

print(f"Created measurement: {bundle.measurement.name}")
print(f"Steps created: {bundle.steps_created}")
For properties and file measurement types (and the fields they accept), see measurements.

Upload validation

Before any data is sent to Ionworks Studio, the Python client validates your time series on your machine. The always-on checks catch the most common processing mistakes:
  • Positive current corresponds to discharge (voltage decreases); charging uses negative current
  • Time starts at 0 and is monotonically non-decreasing
  • Step count exists, starts at 0, and increments by 1
  • Cumulative columns (capacity, energy) reset at the start of each step
If any of these fail, create and create_or_get raise a MeasurementValidationError and no upload is initiated. See Troubleshooting below for how to fix each failure.

Strict validation

We recommend passing validate_strict=True on every upload. The strict checks catch subtle bugs introduced during data processing — for example, large unrecorded time gaps or a time series whose rows have been reordered by a faulty groupby or partition_by call — before the data lands in Ionworks Studio and contaminates downstream simulations and fits.
bundle = client.cell_measurement.create(
    cell_instance.id,
    measurement_data,
    validate_strict=True,
)
Strict mode adds the following checks on top of the always-on ones:
  • Each step contains at least 2 data points
  • Cycle count is constant within each step
  • No gap between consecutive time samples exceeds 5 hours
  • Voltage is continuous between consecutive rows (requires voltage_window)
  • No two consecutive same-direction steps deliver a full charge or discharge (requires rated_capacity and a steps dataframe in measurement_detail)
  • No single step exceeds 500 % of the rated capacity — emitted as a UserWarning rather than raising (requires rated_capacity and steps)

Providing cell context for richer checks

The voltage-continuity and step-capacity checks need to know the cell’s rated voltage window and capacity. Pass them as keyword arguments:
bundle = client.cell_measurement.create(
    cell_instance.id,
    measurement_data,
    validate_strict=True,
    rated_capacity=0.002,           # A.h — nominal capacity of the cell
    voltage_window=(2.5, 4.2),      # (V_min, V_max) from the cell spec
)
If you already have the cell spec object, you can read these values directly from it:
ratings = cell_spec.ratings
rated_capacity = ratings["capacity"]["value"]
voltage_window = (
    ratings["voltage_min"]["value"],
    ratings["voltage_max"]["value"],
)
Both arguments are optional. Omitting voltage_window skips only the voltage-continuity check; omitting rated_capacity skips only the consecutive-full-step and per-step capacity checks. All other strict-mode checks still run.
The consecutive-full-step and per-step capacity checks also require a pre-computed step summary under the steps key of measurement_detail. If you do not provide one, Ionworks Studio generates step summaries server-side after upload, but these two client-side checks are skipped.
create_or_get accepts the same rated_capacity and voltage_window arguments and forwards them to create.

Relaxing a single strict check

If a specific strict check is a known false positive for your dataset — for example, a legitimate multi-day rest period that exceeds the 5-hour time-gap threshold — relax only that check with skip_checks rather than turning strict mode off entirely. This keeps every other guardrail in place (least-privilege validation).
bundle = client.cell_measurement.create(
    cell_instance.id,
    measurement_data,
    validate_strict=True,
    skip_checks={"time_gaps"},  # everything else still enforced
)
Valid names (also exposed as ionworks.validators.STRICT_CHECK_NAMES):
  • minimum_points_per_step
  • cycle_constant_within_step
  • time_gaps
  • voltage_continuity
  • consecutive_same_direction_full_steps
  • step_capacity_within_rated
Unknown names raise ValueError. Avoid disabling strict mode wholesale unless you have a documented reason that applies to multiple checks at once.

Idempotent uploads with create_or_get

All three create methods have create_or_get variants that make upload scripts safely re-runnable — if a resource with the same name already exists, the client fetches and returns it instead of raising an error.
# cell_spec: data only
spec = client.cell_spec.create_or_get(data)

# cell_instance: requires cell_spec_id
instance = client.cell_instance.create_or_get(cell_spec_id, data)

# cell_measurement: requires cell_instance_id
measurement = client.cell_measurement.create_or_get(cell_instance_id, data)

Duplicate handling

When you call create() (not create_or_get) and a resource with the same name already exists, the API returns a 409 Conflict with the existing resource’s ID in the detail field:
{
  "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"
  }
}
The existing_id lets you fetch the existing record without a separate lookup if you want to implement your own create-or-get logic.

Inline time series size limit

Time series passed inline (e.g. inside a pipeline configuration) are capped at 1,000 rows — larger datasets must be uploaded as measurements first. See reading data — inline size limit for the error class, workaround, and db:<measurement_id> reference form.

Automatic payload compression

The Python client automatically compresses request payloads larger than 512 KB using gzip before sending them to the API. This happens transparently — no configuration is needed. For large serialized models or datasets, this can reduce upload sizes significantly.

Troubleshooting

Large time gap between consecutive rows

Problem: When uploading with validate_strict=True, you receive an error like:
Time gap of 6.12 h between rows 4201 and 4202 exceeds the maximum allowed gap of 5.0 h.
Solution: A gap longer than 5 hours between two consecutive time samples almost always means rows were dropped during processing — for example, a long rest period was recorded as elapsed time in the file but the intermediate rows were stripped. The capacity integral then counts current across the missing interval and reports inflated values. Re-read the raw cycler file without filtering and confirm the time axis is continuous. If your workflow intentionally decimates the data, downsample uniformly rather than removing whole sections, or split the recording into separate measurements at the gap.

Voltage continuity check failed

Problem: When uploading with validate_strict=True and a voltage_window, you receive an error like:
Voltage continuity check failed: 318/3599 (8.8%) of consecutive row pairs have a voltage jump greater than 80% of the rated voltage window.
Solution: Ionworks looks at the absolute voltage change between every pair of consecutive rows. If more than 5 % of pairs exceed 80 % of the rated voltage window (V_max - V_min), the time series is almost certainly out of chronological order — typically because of a grouping operation like partition_by("Cycle_raw") that interleaves pulse and rest rows from different cycles. Sort your DataFrame by Time [s] before uploading, and avoid any transform that breaks the natural row order:
import polars as pl

time_series = time_series.sort("Time [s]")

Consecutive full-capacity steps in the same direction

Problem: When uploading with validate_strict=True, a steps dataframe, and a rated_capacity, you receive an error like:
Consecutive full-capacity discharge steps detected: step 12 and step 15 each delivered more than 200% of the rated capacity.
Solution: “Consecutive” here means the two nearest same-direction steps in the sequence — so step 12 and step 15 are consecutive discharges even if charge steps sit between them. A single measurement should represent one continuous test on a single cell, so two back-to-back full-capacity discharges (or charges) usually mean the file was assembled by concatenating several independent experiments (for example, multiple CC discharges at different C-rates stitched together). Split the source data back into separate measurements before uploading.
If a legitimately long single experiment contains more than two full charges or discharges in a row — rare, but possible — drop the pre-computed steps entry from measurement_detail or omit rated_capacity so this specific check is skipped, and let Ionworks Studio regenerate step summaries server-side.

Step capacity exceeds rated capacity

Problem: When uploading with validate_strict=True, a steps dataframe, and a rated_capacity, you see a UserWarning like:
3 step(s) exceed 500% of the rated capacity. First: step 47 has ‘Discharge capacity [A.h]’ = 0.031 A.h.
Solution: This is a soft warning — the upload still proceeds — but it signals that something is probably wrong with your step boundaries or that the capacity integral was inflated across an unrecorded time gap. Inspect the flagged step, verify it represents a single charge or discharge, and re-check the Step count column for gaps. Fixing the underlying time-gap or step boundary issue usually clears the warning.

Next steps

Measurements

The three measurement types — time series, properties, file — in detail.

Reading data

List, filter, paginate, and retrieve measurement data.

Preparing data

Read cycler files into the Ionworks format before uploading.

Data format

Recognized columns, quantity format, and sign conventions.