--- # SPDX-License-Identifier: AGPL-3.0-or-later title: Media Proxy overview description: Media Proxy surfaces, methods, byte ranges, response headers, and cache behaviour. --- The Media Proxy serves Fluxer attachments, image assets, themes, entrance sound audio, and signed external media. It also accepts uploads authorised by a capability, a signed grant in the upload URL. ## Route families | Family | Paths | Reference | | --- | --- | --- | | Attachment | `/attachments/{path}` | [Get attachment](/media-proxy/routes/#get-attachment) | | Signed external | `/external/{signature}/{target}` | [Get signed external media](/media-proxy/routes/#get-signed-external-media) | | Theme CSS | `/themes/{path}.css` | [Get theme CSS](/media-proxy/routes/#get-theme-css) | | Entrance sound | `/entrance-sounds/{user_id}/{filename}` | [Get entrance sound](/media-proxy/routes/#get-entrance-sound) | | Image asset1 | `/avatars`, `/icons`, `/branding`, `/banners`, `/splashes`, `/embed-splashes`, `/emojis`, `/stickers` | [Image asset contract](/media-proxy/routes/#image-asset-contract) | | Static object2 | `/{key}` | [Get static object](/media-proxy/routes/#get-static-object) | | Upload relay | `/v1/relay/{key}` | [Upload relay](/media-proxy/upload-relay/) | | Operator and internal | `/_health`, `/_metrics`, `/_metadata`, `/_sniff`, `/_thumbnail`, `/_frames` | [Operator and internal endpoints](/media-proxy/routes/#operator-and-internal-endpoints) | 1 Guild member avatars and banners are image assets too, at `/guilds/{guild_id}/users/{user_id}/avatars` and `/guilds/{guild_id}/users/{user_id}/banners` 2 Served by a `static` endpoint alone [Transformations](/media-proxy/transformations/) defines the query parameters that select a representation for the families that accept them. [Responses and limits](/media-proxy/responses-and-limits/) lists statuses, size bounds, deadlines, and cache policies. ## Base URLs Read paths have no `/v1` prefix. Take `endpoints.media` and `endpoints.static_cdn` from [instance discovery](/http-api/instance/#instance-endpoints-object). The upload relay is the exception in both respects. It is served below `/v1/relay/`, and its base comes from a deployment setting. A client MUST take that base from the upload URL the HTTP API issued and MUST NOT rebuild one. ## Authorisation No public Media Proxy route reads the HTTP API `Authorization` header. An attachment is authorised by its path and, under the [signed attachment URL policy](#signed-attachment-urls), by the signature in its query string. Any other stored object is authorised by its path, signed external media by its path signature, and an upload by the capability in its URL. Only the internal `POST` endpoints read `Authorization`, and they require the exact value `Bearer {secret}` built from the deployment secret. :::caution[A media URL is a bearer capability] Anyone holding a URL reads the object without an account. Under `enforce`, an ordinary signed attachment URL works until its `ex` time, which is at most one day after it was signed, and a [data package URL](/http-api/messages/#data-package-attachment-urls) works until the secret that signed it is removed. Under `off` or `report`, an attachment path works for as long as the object exists. Leaving the channel does not revoke a URL already issued. ::: The Media Proxy has no request-count rate limit, so no response has the [rate limit headers](/topics/rate-limits/#rate-limit-headers) of the versioned HTTP API. A failure returns a short `text/plain` body, and [media error response](/media-proxy/responses-and-limits/#media-error-response) defines that shape. ## Deployment modes One Media Proxy process serves exactly one mode. The mode is fixed at startup and defaults to `mp`. | Mode | Serves | | --- | --- | | `mp` | Attachments, signed external media, themes, entrance sounds, and every image asset route | | `static` | Every read route as a raw object read from the static bucket, with no transformation and no SVG rasterisation1 | | `upload` | The [upload relay](/media-proxy/upload-relay/), plus every `mp` read route from the same buckets | | `relay` | The [upload relay](/media-proxy/upload-relay/) alone. Every read path returns 404 | 1 A `static` mode endpoint also strips `X-Robots-Tag` from every response and sends no `Content-Disposition` The relay `PUT` returns 404 outside `upload` and `relay` mode. A `relay` endpoint returns 404 for a `GET` or `HEAD` of any read path and reads no object for it. On a read that requests no transformation, only an `mp` endpoint rasterises SVG, so an `upload` endpoint returns the original SVG bytes. The [operator and internal endpoints](/media-proxy/routes/#operator-and-internal-endpoints) behave the same in every mode. The operator chooses which mode serves each published base URL. The reference self-hosted deployment serves `endpoints.media` from an `upload` mode process. ## Methods Every read route accepts `GET` and `HEAD`. HEAD returns the same status and representation headers as GET with an empty body, and a `Range` on HEAD still selects 206 or 416. Any other method on a read route returns 405. The relay path accepts `PUT`. Any other method there returns 405 with an `Allow` header. An unknown path returns 404. On the [signed external route](/media-proxy/routes/#get-signed-external-media), a HEAD request can be answered from the headers of an origin HEAD response, so its representation headers can differ from GET. ## Request headers Only `Range` affects the representation a public read returns. The [cross-origin read policy](#cross-origin-reads) reads `Origin`, and under `enforce` that header decides `Access-Control-Allow-Origin` and can turn a 200 into a 403. Every other request header, including `Authorization`, `Cookie`, `Accept`, `If-Range`, `If-None-Match`, `If-Modified-Since`, `If-Match`, and `If-Unmodified-Since`, is ignored on a public read route. ### Common request headers | Field | Type | Description | | --- | --- | --- | | Range?1 | string | One byte range under the [byte-range contract](#byte-ranges) | | Origin?2 | string | Serialised request origin read by the [cross-origin read policy](#cross-origin-reads) | 1 A value whose unit is not the exact lowercase `bytes`, or that names more than one range, is ignored, and the response has the complete representation 2 Read only when the cross-origin read policy is `report` or `enforce` ## Cross-origin reads An operator can limit which web origins read media through CORS. `FLUXER_MEDIA_PROXY_CORS_MODE` sets this cross-origin read policy, and it defaults to `off`. | Value | Description | | --- | --- | | `off` | A media representation has `Access-Control-Allow-Origin: *`, and no `Origin` is read | | `report` | Every response is the same as under `off`, and each read `enforce` would refuse is logged | | `enforce` | A read from an origin outside the allowlist returns 403 | Under `enforce`, an `Origin` is allowed when it equals the serialised form of an entry in `FLUXER_MEDIA_PROXY_CORS_ALLOWED_ORIGINS` byte for byte. The desktop app origin `fluxer-app://app` is always allowed and needs no entry. Anything else is refused, including `null`, a repeated `Origin` header, and a value that is not ASCII. A refused read returns 403 on every read path, and the Media Proxy reads no object and fetches no external URL for it. [Media Proxy settings](/operator/configuration/#media-proxy-settings) defines how an entry is parsed and serialised. A read with no `Origin` is served without `Access-Control-Allow-Origin`. A browser sends no `Origin` for an `` or a `