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 withPOST /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:
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.
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 withGET /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:
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. A413 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), 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 — full endpoint envelope and error shapes.
- Custom domains and managed SSL — white-label hostname termination.
- Tenant isolation — the per-tenant namespace these object keys live under.
- Compliance > Audit — where upload/delete events land in the event log.