Skip to main content

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:
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: 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), 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