Read and asset routes
A Media Proxy read route resolves one path to one file and returns it. These paths are unversioned and sit under the base URL published as endpoints.media by instance discovery. The static object read uses endpoints.static_cdn.
Shared route contract
Section titled “Shared route contract”Every route here accepts GET and HEAD, reads no Authorization header, and has no request-count rate limit. HEAD returns the GET status and representation headers with an empty body, including 206 and 416.
Selector parsing defines query decoding. Byte ranges defines which ranges are recognised and which headers a 206 or 416 has.
Common responses
Section titled “Common responses”Every media read route can produce these statuses in addition to the ones in its own table.
| Status | Body | Condition |
|---|---|---|
| 403 | Media access denied | The request address is not permitted by the instance media access policy |
| 404 | Not found | The path is not a media route or the object does not exist |
| 405 | Method not allowed | The method is neither GET nor HEAD |
| 413 | Payload too large | The stored object exceeds the 500 MiB media bound |
| 502 | Bad gateway | The object store could not be read |
Get attachment
Section titled “Get attachment”GET/attachments/{path}Returns the attachment identified by the path, optionally transformed. The path comes from the attachment URL the HTTP API returned.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| path1 | string | The attachment object path from a message attachment object |
1 Fluxer issues {channel_id}/{attachment_id}/{filename}. After percent-decoding, the storage key must be non-empty, must not begin with /, and must not contain an empty, ., or .. segment, or the route returns 400
Query parameters
Section titled “Query parameters”| Field | Type | Description |
|---|---|---|
| width? | integer | The output width in pixels (1 through the decoded edge bound, any other value returns 400) |
| height? | integer | The output height in pixels under the same rule |
| format? | string | The output format under the attachment and external format contract (any other value returns 400) |
| quality? | string | The encoder profile under the quality contract |
| animated? | boolean | Whether animated output is requested |
| effort?1 | integer | The WebP encoder effort, clamped to 9 |
| download? | boolean | Whether attachment disposition is requested |
1 Read only on this route, and an empty or unparsable value is ignored. Only lossless animated WebP output uses a value above 6
A transformation begins when width, height, format, or quality is present, or when animated resolves to true. download and effort select none on their own.
Without a transformation Fluxer streams the object from the store and forwards the range to it. With a transformation it reads the complete object into memory first, and the range then applies to the transformed bytes.
An SVG attachment with a transformation parameter is rasterised, and format and quality select the output as they do for any other source, defaulting to WebP. Without a transformation parameter, only an mp endpoint rasterises it, always as lossless WebP. An upload endpoint returns the stored SVG bytes.
A video source requires an explicit image format. Another transformation parameter without format returns 400. A source that is neither an image nor a video returns 400 when format is present. Without format, Fluxer returns it unchanged.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | Complete media representation | The object and selected representation are available |
| 206 | Selected media bytes | One range is satisfiable |
| 400 | Bad request | The decoded key, a dimension, the format, or the transformation request is invalid, or the transformation failed |
| 404 | Not found | The attachment object does not exist |
| 416 | empty | The range is unsatisfiable |
| 504 | Gateway timeout | Transformation capacity was unavailable or the transformation exceeded its deadline |
The common responses also apply.
Get signed external media
Section titled “Get signed external media”GET/external/{signature}/{target}Returns external media through Fluxer, optionally transformed. Fluxer verifies the signature before it decodes or fetches the target.
Fluxer constructs the signed path and exposes it on proxy_url of an embed media object, proxy_icon_url of an embed author object, proxy_icon_url of an embed footer object, proxy_url of a resolved GIF entry, and proxy_src of a GIF media format object.
A field holds a signed path only when its source URL is external. A URL already under the Media Proxy endpoint, and any URL the deployment cannot sign, is returned verbatim, so a client MUST request the value exactly as issued and MUST NOT assume it has the signed shape.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| signature | string | The unpadded base64url HMAC-SHA256 of the target component under the deployment secret |
| target1 | string | The encoded target component, which can contain / separators |
1 The opaque form is v2/ followed by the unpadded base64url of the complete URL. The segmented form joins an optional percent-encoded ?query segment, the scheme, the host and port, and the percent-encoded path segments with /
A client MUST preserve both components exactly as issued and MUST NOT decode or reconstruct either one. The signature covers the target component alone, so adding or changing a query parameter does not invalidate it. A path with no / after /external/ returns 400.
Target validation
Section titled “Target validation”Fluxer validates the decoded target before every request and again for every redirect hop. It must be at most 8,192 bytes, must use http or https, must have a non-zero port when it names one, and must not contain credentials or a control character. A fragment is dropped before the fetch.
The target must resolve to a public address. A hostname must be at most 253 bytes and must contain at least one dot. Its labels use only ASCII letters, digits, and -, run to at most 63 bytes, and begin and end alphanumerically. The final label is not digits only. Fluxer rejects a literal address in an unspecified, loopback, private, link-local, carrier-grade NAT, benchmarking, documentation, 6to4 relay, multicast, reserved, broadcast, unique-local, IPv4-mapped, 6to4, or NAT64-embedded range. Fluxer validates every address DNS returns the same way before it opens the connection.
A hostname whose lookup fails or resolves to no address returns 400.
The route follows at most five redirects. A further redirect, a repeated URL, or a redirect without a Location header returns 502. A redirect to a rejected target returns 400.
Query parameters
Section titled “Query parameters”| Field | Type | Description |
|---|---|---|
| width? | integer | The output width in pixels (1 through the decoded edge bound, any other value returns 400) |
| height? | integer | The output height in pixels under the same rule |
| format? | string | The output format under the attachment and external format contract (any other value returns 400) |
| quality? | string | The encoder profile under the quality contract |
| animated? | boolean | Whether animated output is requested |
| download? | boolean | Whether attachment disposition is requested |
effort is read only on Get attachment and is ignored here.
A transformation begins when width, height, format, or quality is present, when animated resolves to true, when the target filename ends in .svg, or when the origin body is SVG by media type or by its first bytes. A transforming request forwards no Range to the origin and applies the client range to the transformed bytes, so it still selects 206 or 416.
A non-transforming request forwards the client range to the origin under the filter byte ranges defines, and relays an origin 206 with its Content-Range and Content-Length unchanged. An origin that ignores the range answers 200, and Fluxer streams that whole body through when the origin names an image/, video/, or audio/ media type other than image/svg+xml. Fluxer buffers a transforming request, an SVG body, and any body whose declared media type is empty, application/octet-stream, or outside those three categories. A buffered response has the range applied to the bytes the route finally serves, so a client range over a buffered origin 200 produces 206.
Buffering reserves the whole body from a process-wide external buffer budget of 500 MiB for each configured transform slot and queue slot, plus 512 KiB. A reservation the budget cannot cover returns 503.
Fluxer answers a HEAD with no transformation and no range from an origin HEAD when that origin returns 200, declares a Content-Length of at most 500 MiB, and names a non-SVG media type. Any other HEAD runs the GET path and returns its headers with an empty body.
A video target requires an explicit image format to produce a thumbnail. Without one, Fluxer returns the original bytes unchanged, and it does the same for a target that is neither an image nor a video.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | Complete external representation | The validated origin returned a complete representation |
| 2061 | Selected external bytes | The origin answered a forwarded range, or a range over buffered or transformed bytes is satisfiable |
| 400 | Bad request | The path shape, decoded URL, redirect target, transformation request, or transformation is invalid or prohibited |
| 401 | Unauthorized | The path signature is invalid |
| 413 | Payload too large | The origin declared or delivered more than 500 MiB |
| 416 | empty | A local range is unsatisfiable |
| 502 | Bad gateway | The origin could not be reached, exceeded the redirect bound, or returned a status outside the retained set |
| 503 | Service unavailable | The external buffer budget cannot cover the body, or the buffer allocation failed |
| 504 | Gateway timeout | Transformation capacity was unavailable or the transformation exceeded its deadline |
1 A forwarded range relays the origin Content-Range and Content-Length unchanged and omits either header the origin did not send, so a chunked origin 206 produces a 206 with no Content-Length. A local range over buffered or transformed bytes always has both
An origin status of 400, 401, 403, 404, 405, 406, 408, 409, 410, 411, 412, 413, 414, 415, 416, 428, or 429 reaches the client unchanged as an upstream fetch failure. The origin body and headers are not returned. Any other unsuccessful origin status becomes 502. The common responses also apply.
Side effects
Section titled “Side effects”The route requests the target from the validated third-party origin using the user agent Mozilla/5.0 (compatible; Fluxerbot/1.0; +https://fluxer.app), which is visible to that origin.
Get theme CSS
Section titled “Get theme CSS”GET/themes/{path}Returns a theme stylesheet with the fixed media type text/css; charset=utf-8. The themes resource writes the object to themes/{theme_id}.css.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| path1 | string | The path below /themes/, whose raw route path must end in the exact lowercase suffix .css |
1 After percent-decoding, the storage key must be non-empty, must not begin with /, and must not contain an empty, ., or .. segment, or the route returns 400
The route selects no representation and accepts a Range.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | Complete CSS representation | The theme object is available |
| 206 | Selected CSS bytes | One range is satisfiable |
| 400 | Bad request | The decoded theme key is invalid |
| 416 | empty | The range is unsatisfiable |
The common responses also apply. A path that does not end in .css is not a theme path and falls through to 404.
Get entrance sound
Section titled “Get entrance sound”GET/entrance-sounds/{user_id}/{filename}Returns the stored audio of a user entrance sound. The entrance sounds resource writes the object to entrance-sounds/{user_id}/{hash}.{ext}.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| user_id1 | string | The owning user snowflake |
| filename2 | string | {hash}.{ext} for the stored sound |
1 A non-empty run of ASCII digits. Any other value makes the path unroutable and returns 404
2 The hash is non-empty and ASCII alphanumeric, and Fluxer issues the first 16 hexadecimal characters of the MD5 digest of the audio. The extension is the exact lowercase mp3, ogg, m4a, or wav
The route selects no representation, accepts a Range, and sends no Content-Disposition. A successful response streams the stored bytes with the detected audio media type and the one-year no-transform cache policy.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | Complete audio representation | The sound object is available |
| 206 | Selected audio bytes | One range is satisfiable |
| 416 | empty | The range is unsatisfiable |
The common responses also apply.
Image asset contract
Section titled “Image asset contract”An image asset is a stored picture Fluxer serves at a requested size and format, such as an avatar, a guild icon, or an emoji. A resource object exposes the asset hash, and the path combines the owning resource, that hash, and a file extension. An emoji or sticker path has its own identifier as the filename. The grammar, query parameters, and responses below govern every image asset route. Asset size selection defines every size class.
A client builds the complete URL from the Media Proxy base URL, the path template for the asset class, the hash, and a file extension.
Fluxer matches an asset path by its shape alone. A path with the wrong number of segments, an empty owner segment, or a filename without exactly one dot is not an asset path and returns 404. A hash containing anything but ASCII letters, digits, and _ returns 404 as well, as does an extension outside the known image set. The known set is png, jpg, jpeg, webp, gif, apng, avif, heic, heif, jxl, and svg, matched case-insensitively.
Fluxer issues an eight-character lowercase hexadecimal hash, taken from the MD5 digest of the submitted image before metadata stripping. An owner segment is any non-empty segment, and Fluxer never checks it against a snowflake.
The storage key drops an a_ prefix from the hash, except on an emoji or sticker path, whose key keeps the filename stem exactly as given. A path whose key is missing is also tried under the canonical name of its extension, so both .jpg and .JPG resolve an object stored as .jpeg.
An owner segment of the literal . or .. parses as an asset path but produces an unsafe storage key and returns 400. So does a hash of the literal a_ on a path that strips the prefix.
Query parameters
Section titled “Query parameters”| Field | Type | Description |
|---|---|---|
| size?1 | integer | The requested square edge in pixels, snapped to the size ladder and clamped to the asset class |
| format?2 | string | The requested output format, also accepted under the name fmt |
| quality? | string | The encoder profile under the quality contract, defaulting to high |
| animated?3 | boolean | Whether animated output is requested |
| download? | boolean | Whether attachment disposition is requested |
1 An absent or unparsable value selects 128 before clamping. No value is rejected
2 Accepts case-insensitive auto, png, jpg, jpeg, webp, gif, apng, and avif, where auto keeps path-based selection and avif selects WebP. Any other value falls back to path-based selection, and a sticker path always selects WebP and ignores the field
3 An absent value keeps the hash-derived default, which is animated only for an a_ hash. PNG and JPEG output has no animation
width, height, and effort are not read on an asset path, and no asset query value produces a 400. The route accepts a Range over the selected representation.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | Complete asset representation | The original or transformed asset is available |
| 206 | Selected asset bytes | One range is satisfiable |
| 400 | Bad request | An owner segment of . or .., or a hash of a_ on a prefix-stripping path, makes the storage key unsafe |
| 404 | Not found | The path is not a valid asset path or the asset does not exist |
| 416 | empty | The range is unsatisfiable |
| 5001 | Transcode failed | A required transcode failed and the source is not directly displayable |
| 504 | Gateway timeout | Transformation capacity was unavailable or the transformation exceeded its deadline |
1 A failed transcode whose stored media type begins with image/ and is not image/avif, image/heic, image/heif, or SVG instead returns 200 with the original stored bytes and media type but without Content-Disposition
The common responses also apply. Transformations defines geometry, animation, output formats, and original representation selection.
Get user avatar
Section titled “Get user avatar”GET/avatars/{user_id}/{hash}.{ext}Returns the user avatar under the icon size class.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| user_id | string | The owning user snowflake paired with the avatar hash |
| hash | string | The avatar hash, optionally prefixed with a_ to default to animated output |
| ext | string | The path extension, which selects the output format |
Response
Section titled “Response”The operation uses the shared image asset response.
Get guild icon
Section titled “Get guild icon”GET/icons/{guild_id}/{hash}.{ext}Returns the guild icon under the icon size class.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| guild_id | string | The owning guild snowflake paired with the icon hash |
| hash | string | The icon hash, optionally prefixed with a_ to default to animated output |
| ext | string | The path extension, which selects the output format |
Response
Section titled “Response”The operation uses the shared image asset response.
Get instance branding
Section titled “Get instance branding”GET/branding/{entity_id}/{hash}.{ext}Returns the instance branding image written by Create branding asset, under the icon size class.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| entity_id | string | The instance branding entity key supplied by the branding URL |
| hash | string | The branding hash, optionally prefixed with a_ to default to animated output |
| ext | string | The path extension, which selects the output format |
Response
Section titled “Response”The operation uses the shared image asset response.
Get owner banner
Section titled “Get owner banner”GET/banners/{owner_id}/{hash}.{ext}Returns the owner banner under the banner size class.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| owner_id | string | The owning user or guild snowflake paired with the banner hash |
| hash | string | The banner hash, optionally prefixed with a_ to default to animated output |
| ext | string | The path extension, which selects the output format |
Response
Section titled “Response”The operation uses the shared image asset response.
Get guild splash
Section titled “Get guild splash”GET/splashes/{guild_id}/{hash}.{ext}Returns the guild splash under the banner size class.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| guild_id | string | The owning guild snowflake paired with the splash hash |
| hash | string | The splash hash, optionally prefixed with a_ to default to animated output |
| ext | string | The path extension, which selects the output format |
Response
Section titled “Response”The operation uses the shared image asset response.
Get guild embed splash
Section titled “Get guild embed splash”GET/embed-splashes/{guild_id}/{hash}.{ext}Returns the guild embed splash under the banner size class.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| guild_id | string | The owning guild snowflake paired with the embed splash hash |
| hash | string | The embed splash hash, optionally prefixed with a_ to default to animated output |
| ext | string | The path extension, which selects the output format |
Response
Section titled “Response”The operation uses the shared image asset response.
Get guild member avatar
Section titled “Get guild member avatar”GET/guilds/{guild_id}/users/{user_id}/avatars/{hash}.{ext}Returns the guild member avatar under the icon size class.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| guild_id | string | The guild snowflake owning the member asset |
| user_id | string | The member user snowflake paired with the member avatar hash |
| hash | string | The member avatar hash, optionally prefixed with a_ to default to animated output |
| ext | string | The path extension, which selects the output format |
Response
Section titled “Response”The operation uses the shared image asset response.
Get guild member banner
Section titled “Get guild member banner”GET/guilds/{guild_id}/users/{user_id}/banners/{hash}.{ext}Returns the guild member banner under the banner size class.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| guild_id | string | The guild snowflake owning the member asset |
| user_id | string | The member user snowflake paired with the member banner hash |
| hash | string | The member banner hash, optionally prefixed with a_ to default to animated output |
| ext | string | The path extension, which selects the output format |
Response
Section titled “Response”The operation uses the shared image asset response.
Get emoji image
Section titled “Get emoji image”GET/emojis/{emoji_id}.{ext}Returns the emoji image under the emoji size class with a square cover crop.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| emoji_id1 | string | The emoji snowflake, which is also the storage key |
| ext2 | string | The path extension, which selects the output format |
1 Any non-empty run of ASCII letters, digits, and _. Fluxer clients request {emoji_id}.webp
2 An extension whose encoder is not enabled for output selects WebP instead
Fluxer issues emoji paths without an a_ prefix, so an emoji request defaults to static output and needs animated=true for animation. The parser still reads a leading a_ as an animation request and keeps the stem verbatim in the storage key, so /emojis/a_123.webp names a different object than /emojis/123.webp.
Response
Section titled “Response”The operation uses the shared image asset response.
Get sticker image
Section titled “Get sticker image”GET/stickers/{sticker_id}.{ext}Returns the sticker image under the sticker size class with a square cover crop. Output is always WebP, except that an animated GIF source stays GIF when animated resolves to true.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| sticker_id1 | string | The sticker snowflake, which is also the storage key |
| ext | string | The path extension, used only as the source hint |
1 Any non-empty run of ASCII letters, digits, and _. Fluxer clients request {sticker_id}.webp
Fluxer issues sticker paths without an a_ prefix, so a sticker request defaults to static output and needs animated=true for animation. The parser still reads a leading a_ as an animation request and keeps the stem verbatim in the storage key.
Response
Section titled “Response”The operation uses the shared image asset response.
Get static object
Section titled “Get static object”GET/{key}Returns a raw object from the static bucket. The route exists only on the static deployment mode, published as endpoints.static_cdn by instance discovery. It serves default avatars, client bundles, and other published files.
The complete request path is the storage key after percent-decoding. The route applies no transformation and reads no query parameter. The response uses the detected media type and the one-year cache policy, and a static endpoint omits X-Robots-Tag.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| key1 | string | The object key in the static bucket, taken from the complete request path |
1 After percent-decoding, the key must be non-empty, must not begin with /, and must not contain an empty, ., or .. segment, or the route returns 400
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | Complete object | The object is available |
| 206 | Selected bytes | One range is satisfiable |
| 400 | Bad request | The decoded key is invalid |
| 416 | empty | The range is unsatisfiable |
The common responses also apply.
Operator and internal endpoints
Section titled “Operator and internal endpoints”These paths are not part of the public API surface. They are exempt from the media access allowlist and are not published by instance discovery. A client MUST NOT depend on them.
| Path | Method | Description |
|---|---|---|
/_health | GET | Returns 200 while the process is running |
/_metrics | GET | Returns the Prometheus text exposition of the process. Only a loopback address is served, and any other address returns 403 |
/_metadata | POST | Extracts media metadata, a placeholder, and an optional NSFW verdict for the HTTP API |
/_thumbnail | POST | Produces a WebP thumbnail of a staged upload for the HTTP API |
/_frames | POST | Extracts one JPEG video frame for the HTTP API |
/_metadata, /_thumbnail, and /_frames require Authorization: Bearer {deployment secret key} and return 401 without it. Their JSON request bodies are bounded, and a body beyond the bound returns 413 on all three.