# Data and security (/policy/data-and-security)



What RiffAds stores, how long it keeps it, and how to keep your keys safe.

## Where files live [#where-files-live]

* Outputs are copied into RiffAds storage (Cloudflare R2) when a job finishes. Only that copy is served.
* Provider links are never stored or returned.
* Every file sits under your workspace. The workspace comes from your key or connection, never from request input.
* Files are private. They are served only through short-lived signed links with `Cache-Control: private, no-store`. No public URLs.
* Finished outputs never expire.

<Boundary title="Your files are never posted anywhere">
  No code path publishes to Meta, TikTok, YouTube or X. Files leave only when you or your agent fetch a signed link.
</Boundary>

## Link lifetimes [#link-lifetimes]

| Link                                 | Lives for                                         |
| ------------------------------------ | ------------------------------------------------- |
| Output link on REST, MCP and the CLI | 10 minutes (`output_urls_expire_in_seconds: 600`) |
| Upload PUT link                      | 5 minutes, one use                                |

Agent links are short because they end up in model transcripts.

<Callout type="warn" title="Expired link is not a lost file">
  Read the generation again for a fresh link. A stale URL returns 403. Don't retry it.
</Callout>

* `url` is `null` until the generation is finished. Branch on status, not on the URL.
* If signing fails, `url` is `null`, not an error. Read again.

## What else is kept [#what-else-is-kept]

| Thing                        | Kept                                                                   |
| ---------------------------- | ---------------------------------------------------------------------- |
| Generation records           | Kept. Deleting a project does not delete its generations or outputs.   |
| Content check record         | A SHA-256 fingerprint of the input. Never the words.                   |
| Abandoned upload reservation | Cleared after 15 minutes.                                              |
| Revoked API key              | Disabled, never deleted, so history shows which key started which job. |

There is no self-service workspace deletion.

## API keys [#api-keys]

* **Shown once.** The database stores a hash. Nobody at RiffAds can recover it. Lost it? Revoke it and create another.
* **Live only.** No test keys, no sandbox. Every key can spend what its scopes allow.
* **Never expire.** Revoke to stop one.
* **Made in the app.** Owners and admins create and revoke keys at app.riffads.com/api-keys. No endpoint creates, lists or rotates keys, so an agent can't issue itself one.
* **Name a workspace, not a person.** Want per-person tracking? One key per person or system.
* **No IP allowlist.** The key is the whole credential.

Revoking works on the very next call. Work already running still finishes and is charged.

<Callout type="error" title="Keys belong on servers">
  Never put `sk_live_...` in browser code, mobile apps, public repos, CI logs or chat prompts. Whoever reads it can spend until it is revoked.
</Callout>

### No CORS on the REST API [#no-cors-on-the-rest-api]

`/api/v1` sends no CORS headers and has no `OPTIONS` handler. On purpose. Call it from a server, task runner, CI job or terminal. Never from a browser.

MCP is different: it allows CORS, because its token comes from an OAuth sign-in, not a secret key. [Connect MCP](/mcp/connect).

### CLI key file [#cli-key-file]

`riffads login` saves `~/.riffads/config.json` with mode `0600`. `RIFFADS_API_KEY` in the environment wins over the file. `riffads logout` deletes the file only. The key still works until you revoke it. [CLI setup](/cli/setup).

### Read-only access [#read-only-access]

* A key without the `generate` or `workflows` scope can read and estimate, never start work. It is refused before anything is written.
* A read-only MCP connection does not even see the spend tools.

Use read-only access for dashboards, reports and CI checks.

## Tenancy [#tenancy]

* **REST:** no workspace parameter or header. The workspace comes from the key.
* **MCP:** the token is a person, bound to one workspace. Checked again on every call.
* Another workspace's id and a missing id both return `not_found`. Nobody can probe what exists.

## What never leaves the server [#what-never-leaves-the-server]

* Provider names, model keys, prompts, signed URLs and stack traces never appear in an error.
* Your text is not stored by the content check. Only a fingerprint.
* Card numbers never reach RiffAds. Checkout runs on the payment provider's hosted page.

## Report a problem [#report-a-problem]

**Leaked key:** revoke it in the app first. It stops on the next call.

**Privacy or security report:** use the contact address in the [Privacy Policy](https://riffads.com/privacy). [Legal](/policy/legal).
