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

# Private media storage

> Configure S3-compatible access, CORS and safe retention.

Use a private S3-compatible bucket, such as Cloudflare R2. Create an object read/write credential limited to that bucket. Keep it in the API/worker runtime environment; the UI must not receive it.

Set these values together:

```dotenv theme={null}
POSTED_S3_ENDPOINT=https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com
POSTED_S3_REGION=auto
POSTED_S3_BUCKET=posted-data
POSTED_S3_ACCESS_KEY_ID=YOUR_BUCKET_SCOPED_KEY
POSTED_S3_SECRET_ACCESS_KEY=YOUR_BUCKET_SCOPED_SECRET
```

Use your storage service's correct endpoint and region. R2 uses `auto`; other providers may require a region name. Replace placeholders privately.

## CORS

Allow the exact UI origin to PUT and GET signed objects. Expose ETag and allow headers used by S3 signing. For R2:

```json theme={null}
[
  {
    "AllowedOrigins": ["https://posted.example.com"],
    "AllowedMethods": ["GET", "PUT", "HEAD"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3600
  }
]
```

Do not enable a public bucket domain. A successful upload in the UI confirms CORS and signing; container health alone does not.

## Retention

Temporary uploads use `posted/uploads/`; expire that prefix after one day. Prepared delivery copies use `posted/delivery/`; expire them after two days. Retain immutable originals in `posted/originals/` and normalized, ready videos in `posted/media/` for saved drafts and future schedules.

Never use bucket-wide expiry. It can remove files needed by a future scheduled post or saved draft. Back up private retained media alongside PostgreSQL and `POSTED_SECRET`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.