
# The three persistence tiers

Every file-taking or file-producing op on Relaystation uses the same I/O model, with three tiers. You pick a tier per call by how you pass (or receive) the bytes — there is no setup step and no required storage product.

| Tier | What it is | Cost | Lifetime | Scope |
|---|---|---|---|---|
| **Inline** | base64 in the request/response body | free (part of the call) | none — nothing persists | the one call |
| **Scratch** | customer-scoped working storage | **free** | 24 hours | you only — reusable across calls |
| **Baton** | the durable storage product | paid (quoted at create) | what you configure | shareable, witnessed, durable |

Compute is priced per op on the metered input **regardless of which tier the bytes came from**. Scratch costs nothing; storage and egress economics live in the baton tier alone.

## Inline — small files, zero ceremony

Up to 4 MiB (decoded), pass `{ "inline": "<base64>" }` as the op's file field and get small outputs back the same way: `{ "output": { "inline": "<base64>", "sizeBytes": ..., "contentType": ... } }`. Nothing is stored; there is nothing to clean up. (One asymmetry, stated honestly: the *output* side gates on the **base64-encoded** size against the same 4 MiB threshold — base64 inflates ~1.37×, so raw outputs above ~3 MiB come back as a scratch reference instead. Handle both shapes and the difference never bites; see [receiving outputs](/docs/receiving-outputs).)

## Scratch — the free working tier

Over 4 MiB (or whenever you'd rather upload once and reuse), use scratch:

1. `POST /v1/cputools/upload-url` (free) returns a presigned upload form and an `inputKey` like `scratch/<your-customer-id>/<uuid>`.
2. Upload your file (up to 50 MB) with one multipart POST to that URL.
3. Pass `{ "inputKey": "..." }` as any op's file field — as many ops, as many times as you like, for 24 hours.

Scratch keys are namespaced to your customer identity. Another customer's key — even if leaked — resolves to `404` for you, and yours for them. The same gate admits all three auth modes: API key, wallet JWT, or a signed x402 payment (so an account-less agent can use scratch too).

When an op's **output** exceeds the inline threshold (measured on the encoded size — raw bytes above ~3 MiB), it lands in your scratch automatically and the response carries both handles:

```json
{ "output": { "outputKey": "scratch/<you>/<uuid>", "outputUrl": "https://…presigned, 1 hour…", "sizeBytes": 12023329, "contentType": "image/png" } }
```

`outputUrl` is for downloading now. `outputKey` is for **chaining**.

## Chaining: outputKey is an inputKey

An op's `outputKey` lives in the same namespace as your uploads, so you can feed it straight into the next op's `inputKey` — no download, no re-upload, no pipeline product:

```bash
# 1. Upload once (12 MB scan)
curl -s -X POST https://api.relaystation.ai/v1/cputools/upload-url \
  -H "Authorization: Bearer $KEY" -d '{"ext":"png"}'
# → { "inputKey": "scratch/<you>/aaaa.png", "url": ..., "fields": {...} }
#   (multipart-POST the file to url+fields)

# 2. First op — output exceeds 4 MiB, so it lands in scratch
curl -s -X POST https://api.relaystation.ai/v1/image/convert \
  -H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
  -d '{"file":{"inputKey":"scratch/<you>/aaaa.png"},"format":"png"}'
# → { "output": { "outputKey": "scratch/<you>/bbbb", ... } }

# 3. Chain — the outputKey IS the next inputKey
curl -s -X POST https://api.relaystation.ai/v1/image/metadata \
  -H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
  -d '{"file":{"inputKey":"scratch/<you>/bbbb"}}'
# → { "metadata": { "width": 2000, "height": 2000, "format": "png", ... } }
```

Each step is its own pay-per-call op with its own receipt. The bytes never leave the platform between steps, and the 24-hour scratch window comfortably covers a working session. (For chaining the *tabular* ops — filter, sort, join, SQL — in a single call, see [`POST /v1/pipeline`](https://cputools.relaystation.ai/docs/pipeline), which threads bytes step-to-step and bills the sum.)

## Baton — when the result needs to outlive the day

Scratch is working storage: free, yours, gone in 24 hours. When an output needs to be **durable** (kept past today), **shareable** (handed to another agent or a human via a token), or **witnessed** (tamper-evident, provable), that is the [baton](https://relaystation.ai/docs/batons) tier — a paid product with its own quoted price, lifecycle, and trust options.

Batons are optional, always. No op requires one; the [lodestone path](https://relaystation.ai/docs/lodestone) — one call, one payment, result back — never gains a mandatory storage step.
