Artifacts
Listing, inspecting, downloading and deleting stored files.
View as MarkdownAn artifact is what a successful run produced. These commands read and manage them;
sctl restore is what turns one back into data.
Listing
sctl artifact list # the whole workspace
sctl artifact list --backup "$BACKUP_ID" # one backupID FILENAME RUN SIZE ENC LOCK CREATED
018f5a2c-... app.dump.gz.gpg scheduled-018f3c2a-1754640000 1483920128 yes until 2026-09-07 2026-08-08T02:04:11Z
018f4b1d-... app.dump.gz.gpg scheduled-018f3c2a-1754553600 1479201280 yes n/a 2026-08-07T02:03:58ZENC is the column to check when you assumed encryption was on. LOCK shows n/a, locked,
or the date the protection lapses.
Inspecting one
sctl artifact get <artifact-id>ID: 018f5a2c-...
Backup: 018f3c2a-...
Run: scheduled-018f3c2a-1754640000
State: archived
Filename: app.dump.gz.gpg
Size: 1483920128 bytes
Original: 4192104448 bytes
Checksum: sha256:9f86d081884c7d65...
Compressed: yes
Encrypted: yes
Key: 3AA5C34371567BD2
Lock: until 2026-09-07T02:04:11Z
Created: 2026-08-08T02:04:11ZThis is the whole of what you need to open the file without us. Two fields repay attention:
Key is the fingerprint of the public key it was encrypted to. After a key rotation,
different artifacts of the same backup need different private keys, and this is what says
which.
Original versus Size tells you whether our pipeline transformed the file. When they
differ, compression or encryption was applied on our side, which changes how you
verify the checksum.
Downloading
sctl artifact download <artifact-id> --output ./artifact--output (or -o) is optional; without it the file is written as <artifact-id>.artifact.
The download is a presigned URL straight to object storage. The bytes do not pass through our API, and the file arrives exactly as stored: still compressed, still encrypted.
download gives you the raw stored object. sctl restore gives you
usable data, by also decrypting, decompressing and reconstructing. Use download when you
want the artifact itself, for archival or to open by hand.
When download is refused
this artifact was delivered only to your own buckets, so we have no copy to serve;
fetch it from the bucket named in its locationsA backup configured with skip_permanent sends the bytes only to your own destinations. We
hold write credentials to your bucket, not a mandate to read your data back out of it, so we
serve nothing and tell you where it is instead.
aws s3 cp s3://my-bucket/<workspace-id>/<backup-id>/<run-id>/artifact ./artifactVerifying
# Linux
sha256sum ./artifact
# macOS
shasum -a 256 ./artifactGet-FileHash .\artifact -Algorithm SHA256Compare against the Checksum field. For a local backup that matches the downloaded file
directly. For cloud and manual backups with our transforms applied, it matches the
file after you decrypt it, because the hash attests to what was uploaded rather than what was
stored. See artifact format.
Deleting
sctl artifact delete <artifact-id>Two refusals, both deliberate:
| Refusal | Meaning |
|---|---|
artifact_locked | Still inside its lock_for window. Nothing removes it early, including us |
last_artifact | It is the only one left. A backup that has run keeps something to restore from |
Deleting removes our copy. Copies in your own destination buckets are left where they are, because we hold write credentials rather than a mandate to destroy data in your storage. Clearing those out is yours to do.
Ordinary expiry is retention, not this command. Deleting by hand is for a specific artifact you want gone now.
Scripting
# The newest artifact for a backup
ARTIFACT_ID=$(sctl artifact list --backup "$BACKUP_ID" | awk 'NR==2 { print $1 }')
# Confirm the latest artifact is encrypted, and fail the job if not
sctl artifact list --backup "$BACKUP_ID" | awk 'NR==2 && $5 != "yes" { exit 1 }'That second line is worth putting in a scheduled check. A backup silently producing plaintext is a failure nothing else reports.
The table format is not a stable interface. For anything load-bearing, call the API and read JSON.