Offsite: S3 remotes and catalog
A remote is an S3-compatible bucket and its credentials. Exports land under the remote’s prefix in a self-describing layout, and the catalog is the appliance’s cached index of what the bucket holds. The bucket alone is enough to rebuild the catalog and recover a library.
<prefix>/<system>/<library-serial>/<cart-label>/<generation>/chunk-00000.tar.zst
<prefix>/<system>/<library-serial>/<cart-label>/<generation>/manifest.json
<prefix>/<system>/<library-serial>/topology.json
<prefix>/<system>/.openvtl-system.json
Remotes
The remote object never includes the secret key. has_secret reports
whether one is stored.
{
"id": 1,
"name": "minio-lab",
"endpoint": "minio.lab:9000",
"region": "us-east-1",
"bucket": "openvtl",
"prefix": "",
"access_key": "AKIA…",
"use_ssl": false,
"path_style": true,
"created_at": "2026-08-31T00:10:00Z",
"last_test_at": "2026-08-31T00:10:30Z",
"last_test_ok": true,
"last_test_detail": "write/read/delete OK",
"has_secret": true
}
GET /api/remotes
Lists every remote.
POST /api/remotes
| Body field | Type | Notes |
|---|---|---|
name |
string | Required. |
bucket |
string | Required. |
access_key |
string | Required. |
secret_key |
string | Required on create. Write-only. |
endpoint |
string | Host or host:port. Defaults to s3.amazonaws.com. |
region |
string | |
prefix |
string | Key prefix inside the bucket. |
use_ssl |
bool | Default true. |
path_style |
bool | Default false. Typically true for non-AWS endpoints. |
curl -sk -X POST https://appliance:8443/api/remotes \
-H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
-d '{"name":"minio-lab","endpoint":"minio.lab:9000","bucket":"openvtl","access_key":"AKIA…","secret_key":"…","use_ssl":false,"path_style":true}'
Returns 201 with the remote.
PUT /api/remotes/{id}
Same fields as create. An empty or omitted secret_key keeps the stored
one.
DELETE /api/remotes/{id}
Removes the remote configuration and its cached catalog. Objects in the
bucket are not touched. Deleting the last remote brings the dashboard’s
no-offsite warning back. Returns {"ok": true}.
POST /api/remotes/{id}/test
Writes, reads, and deletes a probe object in the bucket, and records the result on the remote.
{"ok": true, "detail": "write/read/delete OK"}
A failed test is still a 200 with ok: false and the error in
detail.
GET /api/remotes/{id}/objects
Every object under the remote’s prefix, keyed relative to it. The raw bucket browser.
{"objects": [{"key": "demo01/OVTL280164/OVB001L5/20260831T001507Z/manifest.json", "size": 2311}, {"key": "demo01/OVTL280164/OVB001L5/20260831T001507Z/chunk-00000.tar.zst", "size": 573741824}]}
DELETE /api/remotes/{id}/objects
Deletes every object under a folder prefix in the bucket. The prefix
must end with /, which structurally prevents deleting a single chunk or
manifest. This removes offsite copies; the catalog will no longer offer
those generations after a rebuild.
curl -sk -X DELETE https://appliance:8443/api/remotes/1/objects \
-H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
-d '{"prefix":"demo01/OVTL280164/OVB009L5/"}'
{"deleted": 4}
Catalog
GET /api/catalog
The cached catalog for one remote: one entry per exported generation.
| Query | Notes |
|---|---|
remote_id |
Required. |
[
{"remote_id": 1, "system_name": "demo01", "library_serial": "OVTL280164", "cart_label": "OVB001L5", "generation": "20260831T001507Z", "logical_bytes": 267421859840, "stored_bytes": 137438953472, "chunk_count": 25, "exported_at": "2026-08-31T00:19:40Z", "synced_at": "2026-08-31T00:19:41Z"}
]
POST /api/catalog/rebuild
Clears the cached catalog for the remote and rebuilds it from a bucket listing, fetching every manifest. This is how a fresh appliance learns what an existing bucket holds. Nothing in the bucket is modified.
curl -sk -X POST https://appliance:8443/api/catalog/rebuild \
-H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
-d '{"remote_id":1}'
{"complete": 41, "incomplete": [{"system": "demo01", "library": "OVTL280164", "label": "OVB007L5", "generation": "20260830T231100Z"}]}
incomplete lists generation folders with no manifest: exports that
were interrupted before the completion marker landed. They are not
importable; retry the export or delete the folder.