---
title: "Delivery"
description: "Where artifacts land, saved.sh storage or a bucket you own."
url: "https://saved.sh/docs/backups/delivery"
---

By default an artifact is written once, to our storage. A **delivery plan** changes that:
you can add copies in buckets you own, and you can tell us not to keep one of our own.

Every copy is the **same finished object**, compressed and encrypted exactly as ours is. Your
bucket never receives a raw dump.

## The model [#the-model]

| Noun              | Lives on      | Holds                                                            |
| ----------------- | ------------- | ---------------------------------------------------------------- |
| **Destination**   | The workspace | Provider, endpoint, region, bucket, prefix, and one delete grant |
| **Delivery plan** | The backup    | Which destinations, and the three switches                       |
| **Location**      | The artifact  | One row per copy: where it went, and whether it got there        |

A run that delivers to our store and two of your buckets produces **one artifact with three
locations**. `keep_last` counts artifacts, not copies, and a copy that failed is visible
rather than rounded away.

## Linking a destination [#linking-a-destination]

Only **S3-compatible** stores are accepted: AWS S3, Cloudflare R2, Backblaze B2, Wasabi,
MinIO, DigitalOcean Spaces. Google Cloud Storage and Azure Blob are not S3-compatible and are
out of scope.

| Field          | Notes                                                              |
| -------------- | ------------------------------------------------------------------ |
| `name`         | How you select it on a backup                                      |
| `bucket`       | Required                                                           |
| `endpoint`     | For non-AWS stores. A bare host or a full URL both work            |
| `region`       | Optional                                                           |
| `prefix`       | Optional. Artifacts land under it, in the same tree layout as ours |
| `usePathStyle` | For MinIO and most self-hosted stores                              |
| `allowDelete`  | Whether retention may delete from this bucket. **Off by default**  |

Credentials are stored in our vault and **never returned by the API**.

### Linking proves the bucket works [#linking-proves-the-bucket-works]

A destination is not trusted on your word. Linking writes a small probe object, reads it
back, compares the bytes, and deletes it.

All three steps matter. A policy granting `PutObject` but not `GetObject` would pass a
write-only check and then strand every artifact you ever sent it. One forbidding
`DeleteObject` leaves the probe behind forever.

| State        | Meaning                              |
| ------------ | ------------------------------------ |
| `unverified` | Not checked yet                      |
| `active`     | The probe round-tripped              |
| `failed`     | It did not, and the reason is stored |

A failed probe is **stored with its reason** rather than refused, because "wrong key" is
fixed by editing the destination, not by creating it again.

The minimum policy is `PutObject` and `GetObject` on the prefix, plus `DeleteObject` for the
probe. Grant `DeleteObject` permanently only if you also want `allowDelete`.

## The three switches [#the-three-switches]

```yaml title="saved.yaml"
backups:
  - name: prod-db
    kind: cloud
    delivery:
      destinations: [prod-archive-eu]
      skip_permanent: false
      keep_permanent_on_failure: false
      second_copy: true
```

| Switch                      | Meaning                                                    | Requires                 |
| --------------------------- | ---------------------------------------------------------- | ------------------------ |
| `skip_permanent`            | Do not keep our own copy                                   | At least one destination |
| `keep_permanent_on_failure` | If a destination fails, keep our copy anyway               | `skip_permanent`         |
| `second_copy`               | Also keep a redundant copy in our independent second space | Not `skip_permanent`     |

Each requirement is enforced rather than ignored:

* `keep_permanent_on_failure` without `skip_permanent` is **refused**, because we always keep a
  copy in that case, so accepting it would be a setting that silently does nothing.
* `second_copy` with `skip_permanent` is **refused**, because the second copy is made from our
  copy and there would be nothing to replicate.

Destinations are deduplicated, and naming one twice is not an error.

## What a run actually does [#what-a-run-actually-does]

Your buckets are written **first, one at a time**, each with its own retries. Our copy is
decided afterwards.

```
inspect → compress → encrypt → plan → [dest 1 … dest N] → ours? → finalize
```

The order is what makes the fallback possible: only once your buckets have been tried is it
known whether `skip_permanent` should hold.

| Outcome                  | Artifact record                                | Run result             |
| ------------------------ | ---------------------------------------------- | ---------------------- |
| Every copy stored        | Written, with every location                   | **Succeeds**           |
| Some stored, some failed | **Written**, locations say exactly which exist | **Fails**              |
| Nothing stored anywhere  | **Not written**                                | Fails, before finalize |

<Callout type="warn">
  A partially delivered run **fails even though the artifact exists**. A destination you
  selected did not get your data, and reporting success would be a claim we cannot back. Check
  the artifact's locations to see which copies you actually have.
</Callout>

When nothing is stored, no artifact record is written and the temporary object is left alone,
so a retry still has the bytes.

Two situations override the plan rather than honouring it, and both are logged:

* **The backup definition is gone mid-run.** We deliver one copy to our own storage.
* **Every selected destination has been unlinked.** We drop `skip_permanent` and keep our
  copy, rather than discarding an artifact to honour a plan that no longer exists.

## What changes when you own the bucket [#what-changes-when-you-own-the-bucket]

### Download [#download]

With `skip_permanent`, the bytes only ever went to your bucket. **Download is refused**, and
the API tells you where the artifact is instead.

That is a deliberate limit on ourselves. We hold write credentials to your bucket, not a
mandate to read your data back out of it.

### Delete [#delete]

Deleting an artifact through our API removes **only our copy**. Copies in your buckets stay
where they are.

The retention sweep touches your bucket only where `allowDelete` is on, read live at sweep
time. Turning it off stops the sweep touching artifacts that already exist. See
[Retention](/docs/backups/retention#retention-in-your-own-bucket).

### Unlinking [#unlinking]

Unlinking a destination a backup still selects is **refused**. Doing it silently would turn a
backup that promised three copies into one that makes two, with nothing saying so until a
restore needed the missing one.

Remove it from the backups first, then unlink.

## The second space [#the-second-space]

<Callout type="warn">
  **Not available yet.** The second space is still being built. `second_copy` is accepted by
  the API and the manifest, but no replica is made and nothing is charged for one. This
  section describes what it will do, not what it does today.
</Callout>

`second_copy` keeps a redundant copy on a **separate provider and a separate account** of
ours. It is for the failure where our primary object store, or our account on it, is the
thing that is gone.

The copy is made by a replicator outside the run, so it is not something the run waits on.

## Billing [#billing]

BYOB and the second space **add no meter of their own**. What changes is which of the four
base meters you are charged on.

| Situation                   | Charged                                                               |
| --------------------------- | --------------------------------------------------------------------- |
| Our copy kept (the default) | `computation`, `storage`, and `archive` while it is held              |
| `skip_permanent`            | `computation` and `transit` only. Your bytes never touch our storage  |
| Any destination written     | `transit` for the egress, once per successful copy                    |
| `second_copy`               | `transit` once for the upload, then `archive` at **double** the bytes |

`skip_permanent` is therefore the cheapest way to run us, and the one where we can do least
for you. That trade is yours to make.

## Next [#next]

<Cards>
  <Card href="/docs/dashboard/buckets" title="Buckets" description="Linking and verifying a destination." />

  <Card href="/docs/backups/retention" title="Retention" description="What the sweep may and may not delete." />

  <Card href="/docs/billing/pricing" title="Pricing" description="The four meters in full." />
</Cards>
