> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/dsx-air/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/dsx-air/_mcp/server.

# Working with ZTP Scripts

```python
# Imports (run once)
from air_sdk import AirApi
from air_sdk.endpoints import Simulation, ZTPScript
```

```python
# Authentication (run once)
api = AirApi.with_ngc_config()
# OR api = AirApi.with_api_key(api_key="...")
# OR api = AirApi.with_device_login(email="...", org_num="...")
#    ^ use in terminal only — not supported in Jupyter notebooks
```

### Basic Info

`Simulation` instances have a `ztp_script` property which accesses any `ZTPScript` associated with the simulation.

A `ZTPScript` holds one entry per configured OS type in `ztp_configs`: `cumulus` is the provisioning script and `nvos` the ZTP JSON object. `content` is the Cumulus script, or `None` when only NVOS is configured.

```python
sim_without_ztp_script: Simulation = api.simulations.get(
    '...'
)  # replace with actual sim id
assert sim_without_ztp_script.ztp_script is None

sim_with_ztp_script: Simulation = api.simulations.get('...')  # replace with actual sim id
ztp_script = sim_with_ztp_script.ztp_script
assert ztp_script is not None
assert ztp_script.ztp_configs  # e.g. {'cumulus': ..., 'nvos': {...}}
```

### Adding ZTP Scripts during Import

```python
ztp_script_path = '../files/ztp/basic_script.sh'
with open(ztp_script_path, 'r') as f:
    ztp_script_content = f.read()

json_data = {
    'format': 'JSON',
    'name': 'JSON Sim',
    'content': {
        'nodes': {
            'node1': {
                'cpu': 2,
                'memory': 1024,
                'storage': 10,
                'os': 'generic/ubuntu2204',
                'cpu_arch': 'x86',
            },
        },
        'oob': False,
    },
    'ztp': ztp_script_content,
}

sim: Simulation = api.simulations.import_from_data(**json_data)
assert isinstance(sim.ztp_script, ZTPScript)
assert sim.ztp_script.content == ztp_script_content
```

### Adding ZTP Scripts after Creation

```python
sim: Simulation = api.simulations.create(name='Blank Sim')

assert sim.ztp_script is None

ztp_script_path = '../files/ztp/basic_script.sh'
with open(ztp_script_path, 'r') as f:
    ztp_script_content = f.read()
    sim.create_ztp_script(content=ztp_script_content)

assert isinstance(sim.ztp_script, ZTPScript)
assert sim.ztp_script.content == ztp_script_content
```

### Updating ZTP Script Content

```python
sim_id = '...'  # Replace with your simulation ID
sim_with_ztp_script: Simulation = api.simulations.get(sim_id)

ztp_script = sim_with_ztp_script.ztp_script
assert ztp_script is not None

ztp_script_path = '../files/ztp/basic_script.sh'
with open(ztp_script_path, 'r') as f:
    ztp_script_content = f.read()  # New content
    ztp_script.update(content=ztp_script_content)
```

### Configuring ZTP Per Os Type

A simulation can carry a Cumulus script, an NVOS ZTP configuration, or both. Updates change only the OS types they are given, each replaced whole (nothing is merged inside an OS type's configuration), and remove an OS type set to `None`; removing the last one needs `delete_ztp_script()`. Changes need the simulation to be `INACTIVE`. These cells need an Air release with per-OS ZTP support.

Saving doesn't provision anything by itself: Air serves the configuration from the simulation's OOB server, so the simulation needs automatic OOB with DHCP (`enable_auto_oob()`, DHCP on by default). The OOB server rebuilds its ZTP files each time the simulation starts, and a switch gets the change the next time it runs ZTP, as on a clean start (`start(checkpoint=None)`). A switch restored from a checkpoint resumes from its snapshot and may not run ZTP again.

```python
sim_id = '...'  # Replace with your simulation ID
sim: Simulation = api.simulations.get(sim_id)

cumulus_script = '#!/bin/bash\n#CUMULUS-AUTOPROVISIONING\n'
nvos_config = {
    'ztp': {'01-image': {'install': {'url': 'http://<server>/images/<nvos-image>.bin'}}}
}

# Configure both OS types (use update_ztp_script() if the simulation has a configuration)
sim.create_ztp_script(ztp_configs={'cumulus': cumulus_script, 'nvos': nvos_config})

# Change only the NVOS configuration; the Cumulus script is kept
sim.update_ztp_script(ztp_configs={'nvos': {'ztp': {'01-image': {'uninstall': True}}}})

# Read one OS type
assert sim.ztp_script.ztp_configs['nvos'] == {'ztp': {'01-image': {'uninstall': True}}}

# Remove one OS type; the Cumulus script is kept
sim.update_ztp_script(ztp_configs={'nvos': None})
assert set(sim.ztp_script.ztp_configs) == {'cumulus'}
```

### Deleting a ZTP Script from a Simulation

```python
sim_id = '...'  # Replace with your simulation ID
sim_with_ztp_script: Simulation = api.simulations.get(sim_id)

ztp_script = sim_with_ztp_script.ztp_script
assert ztp_script is not None
ztp_script.delete()

assert sim_with_ztp_script.ztp_script is None
```