> ## Documentation Index
> Fetch the complete documentation index at: https://docs.opper.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Files

> Reusable storage for media. Upload once, reference by id across image, audio, and video calls.

Files are how media moves between calls on Opper. Upload a file (or let a generation store its output) and you get back a `file_<id>` — a reusable handle you can pass as input to later calls instead of re-uploading or re-encoding bytes. One uploaded image can seed a video; one generated image can be edited by the next call; a generated audio clip can be fed straight to transcription.

## Two ways files appear

* **You upload them.** `POST /v3/files` (multipart) returns a `file_<id>` for reference media — an image to animate, a source video to edit, an audio clip to transcribe.
* **Generations store them.** The [image](/build/multimodal/images), [audio](/build/multimodal/audio), and [video](/build/multimodal/video) endpoints save their output to Files by default (`store: true`) and return a `file_id` alongside the result. Set `store: false` to opt out.

## Using a `file_id` as input

A `file_id` is accepted anywhere a media source is — next to an http(s) URL or a data-URI:

| Endpoint                                                   | Fields that accept a `file_id`       |
| ---------------------------------------------------------- | ------------------------------------ |
| [`POST /v3/images`](/build/multimodal/images)              | `image`, `mask`, `reference_images`  |
| [`POST /v3/audio/transcriptions`](/build/multimodal/audio) | `audio`                              |
| [`POST /v3/videos`](/build/multimodal/video)               | `image`, `video`, `reference_images` |

```bash theme={null}
# 1. Upload a reference image
curl -sX POST https://api.opper.ai/v3/files \
  -H "Authorization: Bearer $OPPER_API_KEY" \
  -F "file=@cat.jpg"
# → { "id": "file_abc123", ... }

# 2. Use it as the seed for a video
curl -sX POST https://api.opper.ai/v3/videos \
  -H "Authorization: Bearer $OPPER_API_KEY" -H "Content-Type: application/json" \
  -d '{ "model": "openai/sora-2", "prompt": "the cat blinks slowly", "image": "file_abc123" }'
```

## Lifecycle

Files are **permanent until you delete them** — uploads and stored generation outputs alike. There is no default expiry: a `file_id` you get today keeps working until you call `DELETE /v3/files/{id}`.

If you want a file to clean itself up, opt into an expiry:

* **On upload** — pass `ttl_seconds` (a positive integer, up to 100 years) as a form field and the file expires that many seconds from now.
* **Later** — `PATCH /v3/files/{id}` with `{"ttl_seconds": 3600}` sets or reschedules the expiry from the moment of the call; `{"ttl_seconds": null}` clears it and makes the file permanent again.

```bash theme={null}
# Give an existing file one hour to live
curl -sX PATCH https://api.opper.ai/v3/files/file_abc123 \
  -H "Authorization: Bearer $OPPER_API_KEY" -H "Content-Type: application/json" \
  -d '{ "ttl_seconds": 3600 }'

# Changed your mind — make it permanent again
curl -sX PATCH https://api.opper.ai/v3/files/file_abc123 \
  -H "Authorization: Bearer $OPPER_API_KEY" -H "Content-Type: application/json" \
  -d '{ "ttl_seconds": null }'
```

Expired files are removed by a background sweep, within the hour of their expiry.

### Files and retention policy

[Retention](/control-plane/rules/retention) rules govern telemetry — traces and generation recordings — not your stored files. A 30-day retention rule does not delete files. The one exception is **zero data retention**: a zero-day scope cannot hold files at all — uploads are rejected, generation outputs aren't persisted (the response signals the skip), and enabling zero-day on a scope that already holds files permanently deletes them, after an explicit confirmation of the file count.

## Quotas

Each organization has a storage quota — a total byte budget (it can vary by plan) and a cap on the number of files. Uploads and stored generation outputs draw from the same budget.

* **Uploads** that would exceed the quota are rejected with `413`.
* **Generated outputs** (`store: true`) degrade gracefully when the quota is full: the call still succeeds and returns the result inline, it just isn't persisted — the response signals the skip rather than failing.

Since files never expire on their own, the quota is what bounds your storage: delete files you no longer need, or give short-lived ones a `ttl_seconds` so they clean themselves up. Larger budgets are tied to your plan.

## Operations

| Operation              | Endpoint                                                                 |
| ---------------------- | ------------------------------------------------------------------------ |
| Upload a file          | [`POST /v3/files`](/v3-api-reference/files/upload-file)                  |
| List files             | [`GET /v3/files`](/v3-api-reference/files/list-files)                    |
| Get metadata           | [`GET /v3/files/{id}`](/v3-api-reference/files/get-file)                 |
| Get a download URL     | [`GET /v3/files/{id}/content`](/v3-api-reference/files/get-file-content) |
| Set or clear an expiry | [`PATCH /v3/files/{id}`](/v3-api-reference/files/update-file)            |
| Delete a file          | [`DELETE /v3/files/{id}`](/v3-api-reference/files/delete-file)           |

## What's next

<CardGroup cols={2}>
  <Card title="Images" icon="palette" href="/build/multimodal/images">
    Generate and edit images; feed a `file_id` for image-to-image.
  </Card>

  <Card title="Video" icon="film" href="/build/multimodal/video">
    Seed a video from an uploaded or generated image.
  </Card>

  <Card title="Audio" icon="waveform-lines" href="/build/multimodal/audio">
    Transcribe an audio `file_id`.
  </Card>

  <Card title="Multimodality" icon="shapes" href="/build/multimodal/overview">
    How the modalities fit together.
  </Card>
</CardGroup>
