---
title: "Artifacts"
description: "Listing, inspecting, downloading and deleting stored files."
url: "https://saved.sh/docs/cli/artifacts"
---

An artifact is what a successful run produced. These commands read and manage them;
[`sctl restore`](/docs/cli/restore) is what turns one back into data.

## Listing [#listing]

```bash
sctl artifact list                          # the whole workspace
sctl artifact list --backup "$BACKUP_ID"    # one backup
```

```
ID              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:58Z
```

`ENC` is the column to check when you assumed encryption was on. `LOCK` shows `n/a`, `locked`,
or the date the protection lapses.

## Inspecting one [#inspecting-one]

```bash
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:11Z
```

This 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](/docs/recover/artifact-format#verifying-the-checksum).

## Downloading [#downloading]

```bash
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.

<Callout>
  `download` gives you the raw stored object. [`sctl restore`](/docs/cli/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.
</Callout>

### When download is refused [#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 locations
```

A 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.

```bash
aws s3 cp s3://my-bucket/<workspace-id>/<backup-id>/<run-id>/artifact ./artifact
```

## Verifying [#verifying]

```bash
# Linux
sha256sum ./artifact

# macOS
shasum -a 256 ./artifact
```

```powershell
Get-FileHash .\artifact -Algorithm SHA256
```

Compare 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](/docs/recover/artifact-format#verifying-the-checksum).

## Deleting [#deleting]

```bash
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 |

<Callout type="warn">
  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.
</Callout>

Ordinary expiry is [retention](/docs/backups/retention), not this command. Deleting by hand is
for a specific artifact you want gone now.

## Scripting [#scripting]

```bash
# 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.

<Callout type="warn">
  The table format is not a stable interface. For anything load-bearing, call the
  [API](/docs/api/artifacts) and read JSON.
</Callout>

## Next [#next]

<Cards>
  <Card href="/docs/cli/restore" title="Restore" description="Turning an artifact back into data." />

  <Card href="/docs/recover/artifact-format" title="Artifact format" description="Opening one without the CLI." />

  <Card href="/docs/backups/retention" title="Retention" description="What expires an artifact, and what protects it." />
</Cards>
