Python Client 2.0 · API v2

API Guide

Submit BPX models, follow a run from queue to completion, inspect logs, and load only the result fields your analysis needs.

Client Version
2.0
Python Version
3.10–3.13
API URL
https://api.dandeliion.com

01

Quickstart

You need a DandeLiion API token and a valid BPX 0.5 file. Keep the token in an environment variable so it never appears in source code or notebooks.

Terminal
python -m pip install "dandeliion-client>=2,<3"
Python
import os

import dandeliion.client as dandeliion

simulator = dandeliion.Simulator(
    "https://api.dandeliion.com",
    os.environ["DANDELIION_API_KEY"],
)

solution = dandeliion.solve(
    simulator=simulator,
    params="cell.bpx.json",
    is_blocking=True,
)

print(solution.status)
print(f"Final voltage: {solution['Voltage [V]'][-1]:.3f} V")
Example output
succeeded
Final voltage: 3.680 V

02

Installation

Client 2.0 supports Python 3.10 through 3.13. A virtual environment keeps its packages separate from the rest of your system.

Terminal
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install "dandeliion-client>=2,<3"

Optional PyBaMM validation

Install the PyBaMM extra if you want PyBaMM to validate experiment instructions and construct drive-cycle steps. It is not required for normal DandeLiion experiments.

Terminal
python -m pip install "dandeliion-client[pybamm]>=2,<3"

Confirm the installed client before submitting a run:

Python
import dandeliion.client as dandeliion

print(dandeliion.__version__)
# 2.0.0

03

Authentication

Create or obtain a token through the DandeLiion Token Portal. API v2 tokens contain exactly 64 lowercase hexadecimal characters. Treat the token like a password: do not commit, log, print, or place it directly in a notebook.

Python
import os

import dandeliion.client as dandeliion

simulator = dandeliion.Simulator(
    api_url="https://api.dandeliion.com",
    api_key=os.environ["DANDELIION_API_KEY"],
)

The service root, /api/v2, or the complete /api/v2/runs collection URL are accepted. Client 2.0 rejects legacy /v1 URLs and requires HTTPS except for localhost.

Transport settings

The defaults make three bounded attempts and back off polling from one to ten seconds. Override them only when your network or expected result size requires it.

Python
simulator = dandeliion.Simulator(
    "https://api.dandeliion.com",
    os.environ["DANDELIION_API_KEY"],
    request_timeout=(3.05, 30),
    result_timeout=(3.05, 300),
    poll_interval=1,
    max_poll_interval=10,
    max_attempts=3,
)

04

Simulation parameters

Pass a BPX filename or pathlib.Path, a valid BPX dictionary, or an in-memory BPX object. The client validates BPX 0.5 input before submission.

Python
params = "cell.bpx.json"

extra_params = {
    "Mesh": {"x_n": 16, "x_s": 16, "x_p": 16, "r_n": 16, "r_p": 16},
    "Initial SOC": 1.0,
    "extra_resources": False,
}
Parameter Purpose
Mesh Mesh points for x_n, x_s, x_p, r_n, and r_p. Each defaults to 16 and must be at least 4.
Initial SOC Initial state of charge from 0 to 1. The default is 1.
Initial voltage [V] Initial cell or pack voltage between the BPX voltage limits. Initial SOC takes precedence when both are set.
Time series input Current or power values indexed by time for a drive cycle.
Lumped thermal model Enables lumped thermal behaviour; required material properties come from the BPX cell section.
Heat transfer coefficient [W.m-2.K-1] Non-negative coefficient required by the lumped thermal model.
Module Battery-pack geometry and model parameters.
Safe mode Uses tighter convergence conditions at a potential performance cost.
extra_resources Use the Fargate backend for runs expected to exceed 15 minutes or 3 GB of memory.

Initial conditions

  • Set initial temperature in the BPX Cell section.
  • Set initial electrolyte concentration in the Electrolyte section.
  • Set DandeLiion: Initial SOC or DandeLiion: Initial voltage [V] in User-defined, or pass the unprefixed key through extra_params.

05

Experiments and drive cycles

Use dandeliion.Experiment without PyBaMM, or pybamm.Experiment when the optional dependency is installed.

Python
experiment = dandeliion.Experiment(
    [
        (
            "Discharge at 1C until 3.8 V",
            "Hold at 3.8 V for 10 minutes (5 second period)",
            "Rest for 300 seconds",
            "Charge at 2000 mA for 1 hour or until 4.0 V",
        )
    ],
    period="10 s",
)

Drive cycles without PyBaMM

Supply a two-column time series and reference it with a Time series experiment instruction.

Python
import numpy as np
import pandas as pd

data = pd.read_csv("US06.csv", comment="#", header=None).to_numpy()
drive_cycle = np.column_stack([data[:, 0], -data[:, 1]])

experiment = dandeliion.Experiment(
    [("Discharge at 1C until 3.8 V",), ("Time series",) * 2],
    period="10 s",
)
extra_params["Time series input"] = {
    "Time [s]": drive_cycle[:, 0].tolist(),
    "Current [A]": drive_cycle[:, 1].tolist(),
}

Drive cycles with PyBaMM

A PyBaMM current or power step embeds the time series, so it does not need to be repeated in extra_params.

Python
import pybamm

experiment = pybamm.Experiment(
    [
        ("Discharge at 1C until 3.8 V",),
        (pybamm.step.current(drive_cycle),) * 2,
    ],
    period="10 s",
)

06

Run a simulation

dandeliion.solve() converts validated BPX input and the experiment to the API request. Blocking mode waits for a terminal state; non-blocking mode returns immediately.

Python
solution = dandeliion.solve(
    simulator=simulator,
    params=params,
    experiment=experiment,
    extra_params=extra_params,
    is_blocking=False,
)

print("Run:", solution.run_id)
print("Status:", solution.status)
solution.join()
print("Status:", solution.status)
Example output
Status: running
Status: succeeded

Safe retries and token uses

Every accepted, distinct simulation consumes exactly one Token Portal use. The client automatically generates an idempotency key and reuses it for bounded transport retries. Supply a stable key when your application may repeat the same logical submission later.

Python
solution = dandeliion.solve(
    simulator=simulator,
    params=params,
    experiment=experiment,
    is_blocking=False,
    idempotency_key="analysis-batch-0001",
)

Replaying the same token, canonical input, and key returns the original run without consuming another use. Reusing the key for different input raises DandeliionAPIException with code == "idempotency_conflict". Polling, logs, result downloads, and cancellation do not consume additional uses.

Validation metadata

Python
validation = solution.token_validation
if validation is not None:
    print(validation.status)
    print(validation.expires_at)
    print(validation.uses_remaining)

07

Status, logs, and cancellation

Reading solution.status refreshes non-terminal runs. solution.join(timeout=...) waits with bounded polling and raises DandeliionTimeoutError if the local wait expires.

queued

Accepted and waiting for backend capacity.

running

Executing on Lambda or Fargate.

cancel_requested

Cancellation is being applied.

succeeded

Completed with a result available.

failed

Stopped with an error; inspect the log.

cancelled

Cancellation reached a terminal state.

timed_out

Exceeded the backend runtime limit.

Incremental logs

solution.log retrieves new bounded log chunks and returns all text collected by that solution object so far. It can be called while the run is active.

Python
print(solution.log)
Show representative runtime log
Your simulation is now running on AWS Lambda.
[2026-07-26 00:07:54.529] [info] Starting dandeliion-models v0.3.6...
[2026-07-26 00:07:54.553] [info] BPX Model: DFN
[2026-07-26 00:07:54.731] [info] Thermal model: Isothermal
[2026-07-26 00:07:54.731] [info] Number of instructions to parse: 11
[2026-07-26 00:07:54.743] [info] Starting the simulation...
...
[2026-07-26 00:08:00.245] [info] Simulation completed successfully
[2026-07-26 00:08:00.479] [info] Total time: 5.950 s

Cancellation

Python
status = solution.cancel()
print(status)  # "cancel_requested" or "cancelled"

Cancellation is idempotent. A running Lambda invocation may continue at the provider after the public run becomes cancelled, but its later output is hidden and discarded.

08

Work with results

Available field names come from result metadata. Accessing one field lazily streams that field—and Time [s] when needed—rather than downloading the complete result into memory.

Python
for key in sorted(solution.keys()):
    print(key)
Show fields from the example run
Charge [A.h]
Current [A]
Electrolyte concentration [mol.m-3]
Electrolyte potential [V]
Electrolyte x-coordinate [m]
Temperature [K]
Time [s]
Voltage [V]
X-averaged negative electrode exchange current density [A.m-2]
X-averaged negative electrode potential [V]
X-averaged negative electrode surface concentration [mol.m-3]
X-averaged positive electrode exchange current density [A.m-2]
X-averaged positive electrode potential [V]
X-averaged positive electrode surface concentration [mol.m-3]

Inspect values

Python
print(f"Final time [s]: {solution['Time [s]'][-1]}")
print(f"Final voltage [V]: {solution['Voltage [V]'][-1]}")
print(f"Final temperature [K]: {solution['Temperature [K]'][-1]}")
Example output
Final time [s]: 6596.0361328125
Final voltage [V]: 3.6802161457188816
Final temperature [K]: 298.15

Interpolate timeline data

One-dimensional fields aligned with Time [s] are callable. The client performs linear interpolation with constant extrapolation.

Python
import numpy as np

t_eval = np.arange(0, 5, 1)
for time, voltage in zip(t_eval, solution["Voltage [V]"](t=t_eval)):
    print(f"{time}\t{voltage}")
Example output
0    4.080929637658874
1    4.07556477797119
2    4.070638964445616
3    4.065947609011761
4    4.061984287623227

Plot scalar fields

Python
import matplotlib.pyplot as plt

fig, axes = plt.subplots(2, 1, figsize=(10, 8))
axes[0].plot(solution["Time [s]"], solution["Current [A]"])
axes[0].set(title="Current [A]", xlabel="time [s]")
axes[1].plot(solution["Time [s]"], solution["Voltage [V]"])
axes[1].set(title="Voltage [V]", xlabel="time [s]")
plt.tight_layout()
plt.show()
Example current and voltage traces over a 6,596 second simulation
Current and voltage from the API v2 example notebook.

Plot a spatial field

Python
plt.plot(
    solution["Electrolyte x-coordinate [m]"] * 1e6,
    solution["Electrolyte concentration [mol.m-3]"][-1],
)
plt.xlabel(r"x [$\mu$m]")
plt.title("Electrolyte concentration at the end of the experiment")
plt.show()
Example electrolyte concentration across the cell at the end of the experiment
Electrolyte concentration across the cell at the final time.

09

Save and restore solutions

A successful solution bundle contains metadata, collected logs, and a complete result streamed directly to disk. The client writes it atomically without materialising the complete result in memory.

Python
solution.dump("solution.json")
restored_solution = dandeliion.Simulator.restore("solution.json")

print(restored_solution["Voltage [V]"][-1])

Restored result fields are parsed lazily. A completed bundle works offline and contains no API token, origin, or reusable server URL.

Reconnect an incomplete bundle

An active run is saved with result: null. Reconnection requires an explicit destination and token so a modified file cannot redirect credentials to an attacker-controlled server.

Python
solution.dump("incomplete-solution.json")

restored_solution = dandeliion.Simulator.restore(
    "incomplete-solution.json",
    api_url="https://api.dandeliion.com",
    api_key=os.environ["DANDELIION_API_KEY"],
)
restored_solution.join()
Keep your own copy. API v2 initially retains run artifacts for 14 days. Save completed results locally before the artifact expiry time when you need long-term access.

10

Errors and troubleshooting

Token rejections have their own exception type. Other API failures expose structured context that can be recorded safely for support.

Python
try:
    solution = dandeliion.solve(
        simulator=simulator,
        params=params,
        is_blocking=False,
        idempotency_key="analysis-batch-0001",
    )
except dandeliion.DandeliionTokenValidationError as exc:
    print(exc.validation.status)
    print(exc.validation.expires_at)
    print(exc.validation.uses_remaining)
except dandeliion.DandeliionAPIException as exc:
    print(exc.status_code)
    print(exc.code)
    print(exc.request_id)
    print(exc.authorization_request_id)
    print(exc.retry_after)
    print(exc.idempotency_key)
Symptom Action
Token invalid, expired, inactive, or exhausted Inspect DandeliionTokenValidationError.validation and manage the token in the Token Portal.
idempotency_conflict Do not reuse a logical submission key for different parameters. Generate a new key for a distinct simulation.
authorization_unknown Do not resubmit automatically. Give authorization_request_id to support so the possibly consumed use can be reconciled.
HTTP 429 Wait for retry_after. Rate limiting does not consume a portal use.
DandeliionTimeoutError The local wait expired; the remote run may still be active. Check solution.status or cancel explicitly.
Missing result field Inspect solution.keys(); requesting an unknown direct Solution field raises KeyError.
Offline incomplete restore Restore again with both an explicit API URL and API key.

11

Migrate from client 1.x

  1. Change https://api.dandeliion.com/v1 to https://api.dandeliion.com.
  2. Use Python 3.10–3.13; Python 3.9 is no longer supported.
  3. Expect succeeded instead of success, plus cancellation and timeout states.
  4. Remove WebSocket assumptions; status and join() use bounded REST polling.
  5. Retain the idempotency key when manually replaying an uncertain request.
  6. Recreate saved results with client 2.0; v1 restore files and server runs cannot be reconnected.

12

Resources