> ## Documentation Index
> Fetch the complete documentation index at: https://docs.2501.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Import and Export

> Bulk import and export hosts and agents as CSV or JSON

Move a fleet in and out of 2501 as a file. Export what you have, edit it in a spreadsheet, import it back. Useful for onboarding an inventory you already keep somewhere else, for scripted automation, and for copying a setup between environments.

Hosts and agents each have their own file. Import order for a new fleet is **hosts, then agents** - an agent file points at hosts and cannot create them, so export your hosts after importing them to get the ids the agent file needs.

## The files

One format per entity, in CSV or JSON. The columns are the same either way, and they are the fields of the API - so a file doubles as documentation of what you can set.

**A file belongs to one organization.** The organization comes from the request, and everything a row points at is an id, which only means something inside that organization.

### hosts

| column                                          | notes                                                                                                                                    |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                            | present on every export. On import, filled means "update this host", empty means "create one"                                            |
| `name`                                          | required to create a host; identifies it within the organization, and is how a later row in the same file can point at it as a jump host |
| `public_ip`, `private_ip`, `ip_connect_mode`    | `auto` (default), `public`, or `private`                                                                                                 |
| `target_type`, `target_port`, `skip_tls_verify` | `ssh` (default) or `winrm`; the TLS flag applies to WinRM only                                                                           |
| `additional_names`                              | list                                                                                                                                     |
| `tags`                                          | list, from the closed tag vocabulary                                                                                                     |
| `knowledge`                                     | free text                                                                                                                                |
| `jump_hosts`                                    | ordered list of hosts to route through, engine side first. Host ids, or the name of a row above this one                                 |
| `is_jump_host`                                  | `false` by default                                                                                                                       |
| `jump_host_username_credential`                 | credential id holding the relay login user; required when `is_jump_host` is true                                                         |
| `jump_host_secret_credential`                   | credential id holding the relay password or key; required when `is_jump_host` is true                                                    |
| `eligible_subnets`                              | list of IPv4 CIDRs; jump hosts only                                                                                                      |

### agents

| column                                                        | notes                                                                                                                                                                                             |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                                          | present on every export. On import, filled means "update this agent", empty means "create one"                                                                                                    |
| `name`                                                        | required on every import row - part of what identifies the agent                                                                                                                                  |
| `specialty`                                                   | specialty id, required to create an agent                                                                                                                                                         |
| `host`                                                        | host id, required to create an agent, and cannot be changed afterwards                                                                                                                            |
| `main_engine`, `secondary_engine`                             | model keys; both or neither                                                                                                                                                                       |
| `remote_execution`                                            | `true` by default                                                                                                                                                                                 |
| `ssh_winrm_username`, `ssh_winrm_password`, `ssh_private_key` | CSV only - one column per login credential, each holding a credential id. The username and password columns serve an SSH or a WinRM login alike; keys are SSH-only, since Windows has no key auth |
| `accessible_credentials`                                      | list of credential ids the agent may reference in placeholders                                                                                                                                    |

In JSON, an agent's credentials are one `credential_config` array instead of the three columns. Use JSON when you need something the columns cannot express: an optional credential, two credentials on one role, a hand-tuned order, or a public key (which has no column, because it is stored but never used to build a connection).

Ids come from an export - a host's is also in the URL of its page in Command Center. Start a hand-written agent file from a hosts export, so the `host` ids are already right.

### Host tags are a closed list

`tags` is the one column whose values are not free text. Operational rules and prompts target these tags, so a tag nobody defined **matches nothing** — you get silent no-ops rather than an error when you type it. Four closed namespaces, and one open one:

| namespace | values                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `os:`     | `linux`, `windows`, `aix`, `solaris`, `fortios`, `cisco-ios`, `junos`, `nx-os`, `esxi`, `ibm-i` — give the family and the distro where both apply                                                                                                                                                                                                                                                                                                                                                                 |
| `shell:`  | `posix`, `non-posix`, `dsl-cli` (a vendor CLI with no shell: Cisco IOS, FortiOS, JunOS, NX-OS). One per host                                                                                                                                                                                                                                                                                                                                                                                                      |
| `type:`   | `database`, `web`, `jump-host`, `backup`, `monitoring`, `identity`, `compute`, `object-storage`, `network-appliance`, `hypervisor`, `control-plane`, `generic-storage`, `iot`, `vault`                                                                                                                                                                                                                                                                                                                            |
| `tech:`   | `docker`, `kubernetes`, `postgres`, `mysql`, `redis`, `mongodb`, `elasticsearch`, `kafka`, `nginx`, `active-directory`, `ansible`, `terraform`, `tomcat`, `iis`, `weblogic`, `springboot`, `fastapi`, `kong`, `oracle`, `exadata`, `mssql`, `db2`, `ibmmq`, `cics`, `saa`, `hsm-luna`, `splunk`, `cyberark`, `f5`, `palo-alto`, `cisco-firepower`, `ecs-fargate`, `powermax`, `netapp`, `vsphere`, `veritas`, `jenkins`, `artifactory`, `aws`, `gcp`, `azure`, `proxmox`, `podman`, `rabbitmq`, `hashicorp-vault` |
| `app:`    | **open** — name your own business applications: `app:` followed by lowercase letters, digits, `.`, `_` or `-`                                                                                                                                                                                                                                                                                                                                                                                                     |

`tech:` values are deliberately unversioned: the specific version a host runs belongs in `knowledge`, so a rule tagged `tech:tomcat` matches every Tomcat host.

Two behaviours worth knowing:

* Only tags a row **introduces** are checked. A tag already stored on a host stays editable even if the vocabulary changes later, so one legacy tag cannot block every future edit of that host.
* `type:jump-host` is **managed for you**, in lockstep with `is_jump_host`. Setting it by hand does nothing; removing it does not unflag the host.

### References

Anywhere a file points at another entity - a credential, a specialty, an agent's host - write its **id**. Exports write ids, and an id survives a rename.

There is one exception, so that a whole fleet fits in one file: in a hosts file, `jump_hosts` may hold the **name of a row above it**, which is how you create a bastion and the hosts routed through it in a single import. That name is matched only against earlier rows of the same file, never against your existing hosts.

So **file order matters**: put a bastion above the hosts routed through it, and the innermost bastion first in a chain. A reference pointing further down the file is an error that tells you which line to move, and the preview reports all of them at once.

A two-hop fleet in one file, ordered innermost bastion first. `edge-gw` is written before `bastion-dmz` refers to it, and both before `sensor-01` routes through the pair:

```csv theme={null}
name,private_ip,is_jump_host,jump_hosts,jump_host_username_credential,jump_host_secret_credential
edge-gw,10.0.0.1,true,,cred_1f3c8a90-...,cred_2a7d5e10-...
bastion-dmz,10.30.0.5,true,edge-gw,cred_3b8e6f21-...,cred_4c9a7b32-...
sensor-01,10.30.4.12,,"edge-gw,bastion-dmz",,
```

Swap the first and third lines and `sensor-01` fails with "edge-gw is defined on row 3, below this one".

Everything else a file references - credentials, specialties, an agent's host - must already exist.

## What an import does to existing data

Two rules cover everything.

**A row with an `id` updates that entity. A row without one creates it.** There is no third case. An `id` that does not exist, or belongs to another organization, is an error - an `id` never creates one.

**An update states the whole record.** A file that updates anything has to carry **every column**: a value sets a field, an **empty cell clears it**, and a missing column is rejected with the column named. There is no partial update by file.

What that means in practice:

* **Bulk-edit by editing an export.** An export already contains every column, so export → change what you want → import does exactly what it looks like: the fleet matches the file.
* **A two-column update file is an error, not a shortcut.** `id,knowledge` will not import. That is deliberate: the alternative is a file that quietly blanks every field it forgot to mention.
* **To change a single host or agent**, use its own API endpoint (`POST /api/v1/hosts/{id}`) rather than a file.
* **A file that only creates may carry any subset of columns**, because there is nothing to lose - `name,private_ip,jump_hosts` is a perfectly good first-import file. Anything the file leaves out takes its default.

In JSON the same rule applies per key: an update row carries every key, and `null` clears.

Validation runs on the result, not on the row: a create must produce a complete, valid entity, and an update may not clear something mandatory or leave an invalid combination. Nothing can produce a half-valid host or agent - a row that would fails instead.

Re-importing a file whose `id` cells are empty tries to create everything again. Host names must be unique in an organization, so you get errors rather than duplicates - keep the ids if you want a file you can re-apply.

## Importing

In Command Center, open **Hosts** or **Agents** and use **Import**. Pick a `.csv` or `.json` file and press **Preview** first: the preview is the real server verdict for every row - names resolved against your live inventory, licences checked - not a guess made in the browser. Nothing is written until you confirm.

Options:

* **Stop at the first failure.** Off by default, so every row is attempted and you get a full report in one pass. On, the run stops at the first failing line and the rows below it are left untouched.

Rows are applied in file order. Every one comes back with a result - created, updated, skipped or failed - against its line number, so a partial import tells you exactly what happened where. There is no rollback: rows that succeeded stay.

### Via the API

The same endpoints back the UI, so anything you can do in Command Center you can script.

```bash theme={null}
# CSV: the file is the body
curl -X POST "https://<host>/api/v1/hosts/batch?org_id=<org>&dry_run=true" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @hosts.csv

# JSON: rows in an envelope
curl -X POST "https://<host>/api/v1/agents/batch" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"org_id":"<org>","dry_run":true,"rows":[ ... ]}'
```

`dry_run: true` validates everything and writes nothing. The other control is `on_error` (`continue`, the default, or `stop`). A batch is capped at 10000 rows.

The response is HTTP 200 with one result per input row even when every row failed - the per-row verdicts are the answer. A 4xx means the request itself was wrong: bad authentication, no access to the organization, a malformed envelope, or a file-level problem such as an unknown column, which fails the whole file because no row can be trusted after it.

## Exporting

**Export** on the Hosts or Agents page downloads the current organization as CSV. Or call the endpoint:

```bash theme={null}
# CSV
curl "https://<host>/api/v1/hosts/export?org_id=<org>" \
  -H "Authorization: Bearer $API_KEY" -H "Accept: text/csv"

# JSON - a plain array of rows, ready to post straight back to /batch
curl "https://<host>/api/v1/agents/export?org_id=<org>" \
  -H "Authorization: Bearer $API_KEY"
```

Add `template=true` for the header row alone, as a starting point for a hand-written file.

Reading requires read access, importing requires write access - so an auditor can export a fleet and can never import one.

**Secrets never leave.** A credential is exported as a reference, never its value. A fleet rebuilt from an export needs its secrets supplied again.

Export is capped at 10000 rows, the same as import, so an export is always a file that can be imported.

### Round trip

Exporting and re-importing without editing reports every row as `updated` and changes nothing. This holds within one organization: every reference in the file is an id, and ids do not exist in another organization. Two more edges:

* A **CSV** export of an agent loses credential detail the role columns cannot hold - an optional credential, two credentials on one role, a custom order, a public key. JSON keeps all of it, and a lossy CSV export says so in an `x-export-lossy` response header.
* An agent with no host is not a valid row in either format, so it is left out of the file and counted in a response header.

## Versioning

The columns are part of the `v1` API contract. New optional columns can be added, and files written today keep importing. Renaming or removing a column, or changing what one means, would be a new API version with new templates - your `v1` files would keep importing against `v1`.
