> ## 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.

# Protocol API

> Save, validate, parse, and convert UCP protocols programmatically with the ionworks-api Python client

Use `client.protocol` to manage [UCP protocols](/simulate/universal-cycler-protocol) programmatically: save them to a project, validate them, parse a vendor cycler file into UCP, and convert UCP back out to a cycler's native format.

For installation and authentication, see the [Python API client](/api-client) page. To run a saved protocol, see the [Python API](/simulate/api) page.

## Saving a protocol to a project

Saved protocols are project-scoped and content-addressed: two protocols with
the same body are the same record, so saving one that already exists returns
the existing record instead of creating a duplicate.

```python theme={null}
protocol = client.protocol.create(
    name="1C discharge",
    protocol=ucp_yaml,
    description="Baseline discharge to 2.5 V",
)
print(protocol.id)
```

Use `create_or_get` when you do not care whether this is the first time:

```python theme={null}
protocol = client.protocol.create_or_get(name="1C discharge", protocol=ucp_yaml)
```

## Listing and finding saved protocols

```python theme={null}
page = client.protocol.list(name="discharge", order_by="created_at", order="desc")
print(page.total)  # every matching protocol, not just this page

protocol = client.protocol.find_by_name("1C discharge")
```

`list` also accepts `name_exact`, `created_by_email`, and the
`created_after` / `created_before` / `updated_after` / `updated_before` date
filters, plus `limit` and `offset`. Filtering and ordering are applied by the
database, so `total` counts every match.

<Note>
  Protocols are deduplicated on their body, not their name, so a project can
  hold several protocols sharing one name. `find_by_name` raises `ValueError`
  in that case rather than returning an arbitrary one — use `list` when you
  need to choose between them.
</Note>

## Reading, renaming, and deleting

```python theme={null}
print(client.protocol.human_readable(protocol.id))  # readable step-by-step text
print(client.protocol.source_protocol(protocol.id))  # original text as saved

client.protocol.update(protocol.id, name="1C discharge (baseline)")
client.protocol.delete(protocol.id)
```

## Parsing a vendor protocol file into UCP

`parse_file` turns a commercial cycler's protocol file into UCP — the reverse of
[converting UCP to a vendor file](#converting-ucp-to-a-vendor-protocol-file).

```python theme={null}
parsed = client.protocol.parse_file("path/to/schedule.sdu")
print(parsed.ucp)            # the parsed protocol as UCP YAML
print(parsed.cycler_type)    # detected vendor, when identifiable
```

A parsed protocol is not saved — pass `parsed.ucp` to `create` to store it.

Drive cycles and subroutines the file referenced but did not embed are reported
separately: those the parser recovered appear in `available_drive_cycles` /
`available_subroutines`, and those still outstanding in `required_drive_cycles`
/ `required_subroutines`. Supply the required ones before simulating. If the
file parsed only partially, `parsed.error` is set and `parsed.ucp` may be
incomplete.

## Validating a protocol

```python theme={null}
result = client.protocol.validate("""
global:
  initial_soc: 1
  temperature: 25
steps:
  - Discharge:
      mode: C-rate
      value: 1
      ends:
        - "Voltage < 2.5"
""")
print(result["valid"])  # True or False
if not result["valid"]:
    print(result["error"])
```

## Finding input references

Find `input[...]` placeholders in a protocol string, useful for building experiment parameter forms.

```python theme={null}
refs = client.protocol.find_input_references("""
global:
  initial_soc: input["Initial SOC"]
steps:
  - Discharge:
      mode: C-rate
      value: input["C-rate"]
""")
print(refs)  # ["Initial SOC", "C-rate"]
```

## Converting UCP to a vendor protocol file

Use `client.protocol.convert` to translate a UCP YAML protocol into the native file format used by a commercial cycler. This is the reverse of the [commercial protocol upload flow](/simulate/simulating-commercial-protocols) — start from a protocol designed in Ionworks and produce a file you can run on hardware.

Supported targets: `maccor`, `arbin`, `neware`, `biologic_bttest`, `novonix`.

<Note>
  For the `neware` target, pass `nominal_capacity_ah` (the rated cell capacity in
  amp-hours) whenever the protocol uses C-rate steps or cutoffs. Neware sets
  current in absolute mA and has no C-rate mode, so the rate cannot be resolved
  without a capacity. The other targets express C-rate natively and ignore this
  argument.
</Note>

```python theme={null}
result = client.protocol.convert(
    protocol="""
global:
  initial_temperature: 25
  initial_state_type: soc_percentage
  initial_state_value: 100
steps:
  - CC Charge:
      - Charge:
          mode: C-rate
          value: 1
          ends:
            - "Voltage > 4.2"
  - CV Charge:
      - Charge:
          mode: Voltage
          value: 4.2
          ends:
            - "Current < 0.05"
  - Discharge:
      - Discharge:
          mode: C-rate
          value: 1
          ends:
            - "Voltage < 2.7"
""",
    target="maccor",
)

# Inspect the converted protocol as text
print(result.text())

# Or write every output file (primary protocol plus any sidecars) to disk
paths = result.save("./converted")
print(paths)  # [PosixPath('converted/protocol.000'), ...]
```

`ConvertResult` exposes:

* `primary_bytes` — raw bytes of the primary protocol file.
* `text(encoding="utf-8")` — decode `primary_bytes` to a string.
* `save(dir)` — write every output file (primary plus any sidecars) into `dir` and return the list of paths written.

<Note>
  Maccor protocols that include drive-cycle steps produce one or more `.MWF`
  waveform files alongside the primary `.000` file. Use `result.save(dir)` so
  every sidecar lands next to the primary protocol — handling only
  `primary_bytes` drops the waveform files and the protocol will not run on
  the cycler.
</Note>

<Note>
  Some UCP features map cleanly across all formats, but each vendor has its own
  syntax and limitations (see [Differences between commercial
  protocols](/simulate/simulating-commercial-protocols#differences-between-commercial-protocols)).
  If a UCP construct can't be expressed in the target format, the conversion
  returns an error naming the unsupported step (see [Export-time
  validation](/simulate/simulating-commercial-protocols#export-time-validation)
  for the specific features each target format rejects). Validate the output
  by reuploading it through the [commercial protocol
  flow](/simulate/simulating-commercial-protocols) before running it on a real
  cycler.
</Note>

<Tip>
  You can find the ID for any resource from the Ionworks Studio web app. The
  ID is displayed in the URL when you navigate to a resource's detail page.
</Tip>

## Next steps

<CardGroup cols={2}>
  <Card title="Universal Cycler Protocol" icon="file-lines" href="/simulate/universal-cycler-protocol">
    The UCP format reference — steps, modes, and end conditions.
  </Card>

  <Card title="Python API" icon="code" href="/simulate/api">
    Run simulations and pipelines against a saved protocol.
  </Card>

  <Card title="Commercial protocols" icon="upload" href="/simulate/simulating-commercial-protocols">
    Upload a vendor protocol file and simulate it directly.
  </Card>

  <Card title="Protocol builder" icon="wrench" href="/simulate/protocol-builder">
    Build a protocol visually in Ionworks Studio.
  </Card>
</CardGroup>
