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

# The media asset store

> How Orbit's tenant-scoped file surface works end to end — the upload envelope and size limits, the content-type gates, how assets bind into templates and branding with time-boxed signed URLs, and the audit trail on every write and delete.

# The media asset store

Every piece of rich content a customer or your organization uploads — a
campaign image, an email logo, a brand favicon, a document attached to a
message — travels through one server-to-server surface: the **files API**.
Upload a file once, receive a signed read URL for it, and reference that
asset from email templates, WhatsApp media, RCS catalogs, branding
settings, or any other surface that renders it.

Manage assets in the dashboard under **Developer > Media**, or directly
against the endpoints covered below.

## Section 1 — What the files API is for

The files API stores a single uploaded object per call in a private,
tenant-scoped media bucket. It exists so that any image, audio clip,
video, PDF, or spreadsheet your messages and templates cite is hosted by
you, under your own access scope, rather than on a third-party CDN link
that can expire or be repurposed.

Upload an asset with `POST /api/v1/files/upload` — one multipart form
with a single `file` field — and receive an `UploadResult` envelope
holding the asset's id, a signed read URL, the sanitized filename, the
stored content type, and its size:

```bash theme={null}
curl -X POST "https://orbit.devotel.io/api/v1/files/upload" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -F "file=@logo.png;type=image/png"
```

The returned `url` is what you store in a template block or brand
setting. Read it back with `GET /api/v1/files` (cursor-paginated list),
`GET /api/v1/files/:id` (single metadata), and remove it with
`DELETE /api/v1/files/:id`.

## Section 2 — Object keys and content-type gates

Every object lands under `<tenant>/<fileId>_<filename>`, so a file you
upload stays inside your tenant's namespace. Filenames are sanitized on
ingest: directory separators and `..` path-traversal sequences are
stripped, and any character outside letters, digits, dash, underscore,
and dot is removed.

The upload pipeline rejects a file before its bytes are stored when:

* the declared content type is not in the accepted set — images (JPEG,
  PNG, GIF, WebP), audio (MPEG, OGG, WAV, AAC, AMR), video (MP4, 3GPP),
  documents (PDF, Office formats), plain text, or CSV;
* the first bytes of the file disagree with the declared type (a renamed
  executable claiming to be a JPEG fails this check);
* the object would exceed the allowed size.

Requests that violate gates return `422 Unsupported file type`; a file
over the size limit returns `413 PAYLOAD_TOO_LARGE`. Script-capable
formats such as SVG are excluded deliberately — SVG can embed JavaScript
and would expose the platform to stored-XSS if served from an
organization-controlled path.

## Section 3 — Signed URLs are the only egress

Objects are stored privately; the only way to read them is a signed URL
minted at upload or on demand. Default lifetime is one hour. Callers
that pin a URL into a long-lived row — a WhatsApp media field, an email
image block, a brand logo — must either request a longer lifetime at
upload (up to seven days, the V4-signing maximum) or mint a fresh URL on
demand with `GET /api/v1/files/:id/presign`. Pass `ttl_ms` (in
milliseconds, clamped to 60 s–7 d) on `upload` and `presign`.

This is the envelope your content has to fit:

| Parameter            | Limit                                    |
| -------------------- | ---------------------------------------- |
| Single file size     | 25 MB                                    |
| Signed URL lifetime  | 60 seconds to 7 days; defaults to 1 hour |
| Multipart field name | `file`                                   |
| Scope                | One tenant namespace per organization    |

Several read paths re-mint URLs for you so a stored link does not die
under a customer-facing page. When an email is re-opened, when a white-label
sign-in or dashboard sidebar renders, and when a message preview resolves
media, the platform checks whether the stored URL points at one of your
objects and, if so, issues a fresh short-lived URL for it. That re-mint
runs only for objects under your organization's namespace, so a request
carrying a reference to someone else's asset fails to resolve rather than
silently borrowing it.

## Section 4 — Size, quota, and audit

Upload size is capped per file (see the table above), and storage is
bounded per organization; the cap shows in your billing side as usage
rather than a rejected request. A `413 PAYLOAD_TOO_LARGE` means the
object exceeded the 25 MB ceiling — split the asset or host your own
CDN link instead.

Every upload and every delete emits an audit event
(`file.uploaded` / `file.deleted`) with the user id, organization id,
filename, and content type, so a run of removals or unexpected uploads
is visible from **Compliance > Audit** the same way template or API-key
changes are.

## Section 5 — When the URL terminus is custom-domain-terminating

Egress for an asset is the signed URL, not a platform hostname. If you
front customer-facing previews or download links behind your own custom
domain, a signed URL still feeds them — the custom domain terminates the
dashboard/sign-in surface (see [Custom domains and managed
SSL](/concepts/custom-domains-and-ssl)), while media stays on the signed
URL path. Pair the two surfaces cleanly: use custom domains for your
white-label address, and the files API for the asset itself.

## Cross-references

* [Files API reference](/api-reference/files) — full endpoint envelope
  and error shapes.
* [Custom domains and managed SSL](/concepts/custom-domains-and-ssl) —
  white-label hostname termination.
* [Tenant isolation](/concepts/tenant-isolation) — the per-tenant
  namespace these object keys live under.
* [Compliance > Audit](/compliance/audit) — where upload/delete events
  land in the event log.
