Libraries and drives
A library is declared against a pool, then activated in a maintenance window that restarts the tape emulator and rebuilds the FC target. The host sees the declared model, serial, and drive serials; those survive reboots and rebuilds.
GET /api/libraries
The persisted library rows (configuration, not live state; the live
state is in GET /api/status).
[
{"id": 10, "name": "PROD-VTL", "vendor": "IBM", "product": "3573-TL", "variant": "3573-TL", "serial": "OVTL280164", "drive_model": "ULT3580-TD5", "num_drives": 2, "label_prefix": "OVB", "media_dir": "/var/lib/openvtl/pools/prod", "home_pool": 1, "state": "active", "created_at": "2026-08-31T00:04:02Z"}
]
state is pending_restart (declared, not yet served) or active.
GET /api/libraries/{lib}/next-label
The label a mint would generate next for this library, from every label the appliance knows locally and in the S3 catalog.
{"label": "OVB004L5", "prefix": "OVB", "suffix": "L5"}
POST /api/libraries
Declares a library. Nothing is served until POST /api/libraries/apply.
| Body field | Type | Notes |
|---|---|---|
name |
string | Display name. Empty defaults to the generated serial. |
product |
string | Library model wire id from GET /api/models: 3573-TL for TS3100/TS3200 (3573), 03584L32 for TS3500 (3584). |
drive_product |
string | Drive model wire id, for example ULT3580-TD5. Must belong to the library’s family. |
num_drives |
int | 1 up to the model’s max_drives. |
num_slots |
int | 1 to 400. 0 takes the default (100). |
num_map |
int | Import/export slots, 1 to 32. 0 takes the default (4). |
label_prefix |
string | Exactly three A-Z0-9 characters. Barcodes mint as <prefix>001L5, <prefix>002L5, and so on. |
pool_id |
int | An active pool with no library on it. |
curl -sk -X POST https://appliance:8443/api/libraries \
-H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
-d '{"name":"PROD-VTL","product":"3573-TL","drive_product":"ULT3580-TD5","num_drives":2,"num_slots":100,"num_map":4,"label_prefix":"OVB","pool_id":1}'
{
"library": 10,
"serial": "OVTL280164",
"drive_serials": ["OVTD280164A", "OVTD280164B"],
"state": "pending_restart",
"note": "declared in device.conf — activation requires the Apply maintenance window (mhVTL restart + FC rebuild + operator vary off/on)"
}
201 on success. 400 when the pool is not active, already homes a
library, or a field fails validation.
POST /api/libraries/apply
The maintenance window: restarts the tape emulator, rebuilds the FC
target, reconciles ACLs, and marks every library that comes up live as
active. Every host session drops for a minute or two. Afterwards
the operator varies the host’s device descriptions off and on and
creates a device description for any new library.
Preflight refuses with 400 while any job is active or queued, or
while a drive holds a cartridge. The steps stream as maint_step
events.
curl -sk -X POST https://appliance:8443/api/libraries/apply \
-H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
-d '{"confirm":"apply"}'
{
"steps": ["…", "target fabrics: fc: 2 port(s), 3 LUNs identity-verified, ACLs reconciled", "READY FOR OPERATOR: vary off/on the host device descriptions; create the new MLB description for the added library"],
"fc": {"OK": true, "Detail": "fc: 2 port(s), 3 LUNs identity-verified, ACLs reconciled"}
}
The request survives a client disconnect; do not retry it because a
timeout fired. Watch the events stream or poll GET /api/status instead.
DELETE /api/libraries/{lib}
Deletes a library, its drives, and every cartridge on it in one operation. Copies in S3 are never touched; the catalog keeps them and the labels can be re-imported into another library. A library that is live can only release its emulated devices cleanly through a reboot, so deleting a live library reboots the appliance once the cleanup is persisted. Erasing many cartridges on a deduplicated pool takes minutes.
| Body field | Rules |
|---|---|
confirm |
The library’s name or serial, exactly. |
acknowledge |
The literal string I understand. |
Preflight refuses with 400 while any job is active or queued, and
with 409 while a graceful restart is draining.
curl -sk -X DELETE https://appliance:8443/api/libraries/10 \
-H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
-d '{"confirm":"PROD-VTL","acknowledge":"I understand"}'
{"ok": true, "library": 10, "name": "PROD-VTL", "cartridges_deleted": 40, "skipped": 0, "rebooting": true}
rebooting: false for a library that was declared but never served. A
retry after an interrupted delete finishes the cleanup.
POST /api/libraries/recover
One-click disaster recovery. Reads the library’s topology.json from
the bucket, recreates the library on a free pool with its original
model, geometry, and serial (drives get new serials), runs the
activation window, then queues an import of every cartridge’s newest
generation.
| Body field | Type | Notes |
|---|---|---|
remote_id |
int | The S3 remote holding the export. |
system_name |
string | The exporting appliance’s system name (the first key segment). |
library_serial |
string | The library to recover. |
pool_id |
int | An active pool with no library on it. |
name |
string | Optional display name; defaults to the name recorded in the topology. |
curl -sk -X POST https://appliance:8443/api/libraries/recover \
-H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
-d '{"remote_id":1,"system_name":"demo01","library_serial":"OVTL280164","pool_id":2}'
{
"library": 20,
"serial": "OVTL280164",
"name": "PROD-VTL",
"drive_serials": ["OVTD9A1C02A", "OVTD9A1C02B"],
"carts": 40,
"import_jobs": [101, 102, 103],
"steps": ["…"]
}
201 on success. 400 when the serial already exists here, the pool is
in use, no topology exists in the bucket (only libraries exported by
v0.7 or later wrote one), or jobs are active. If activation fails the
response is a 500 that still carries library and import_jobs: the
library row exists as pending_restart, and once it is live the queued
imports can be retried from Jobs. The host re-attaches on vary off/on
because the serial is preserved; the drives are new resources.
Drives
Manual changer moves, the same path a host MOVE MEDIUM takes. The
library must be live.
POST /api/libraries/{lib}/drives/{index}/load
Loads a cartridge from its storage or import/export slot into drive
{index} (0-based).
curl -sk -X POST https://appliance:8443/api/libraries/10/drives/0/load \
-H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
-d '{"label":"OVB001L5"}'
{"ok": true, "detail": "OVB001L5: storage:1 -> library 10 drive 0 (manual)"}
400 if the drive already holds a cartridge, the label is not in this
library, or the library is not live.
POST /api/libraries/{lib}/drives/{index}/unload
Returns the loaded cartridge to its home slot (or the first empty
storage slot). The one hard rule: media is never pulled from a drive
that is reading, writing, or mounting, or that was active in the
last 15 seconds. The host sees the usual not-ready/unit-attention on
its next access, as with an operator move on a physical library.
{"ok": true, "detail": "OVB001L5: library 10 drive 0 -> storage slot 1 (manual)", "slot": 1}