Skip to content
Fluxer API

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.

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.

Every media read route can produce these statuses in addition to the ones in its own table.

StatusBodyCondition
403Media access deniedThe request address is not permitted by the instance media access policy
404Not foundThe path is not a media route or the object does not exist
405Method not allowedThe method is neither GET nor HEAD
413Payload too largeThe stored object exceeds the 500 MiB media bound
502Bad gatewayThe object store could not be read
GET/attachments/{path}Unauthenticated

Returns the attachment identified by the path, optionally transformed. The path comes from the attachment URL the HTTP API returned.

FieldTypeDescription
path1stringThe 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

FieldTypeDescription
width?integerThe output width in pixels (1 through the decoded edge bound, any other value returns 400)
height?integerThe output height in pixels under the same rule
format?stringThe output format under the attachment and external format contract (any other value returns 400)
quality?stringThe encoder profile under the quality contract
animated?booleanWhether animated output is requested
effort?1integerThe WebP encoder effort, clamped to 9
download?booleanWhether 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.

StatusBodyCondition
200Complete media representationThe object and selected representation are available
206Selected media bytesOne range is satisfiable
400Bad requestThe decoded key, a dimension, the format, or the transformation request is invalid, or the transformation failed
404Not foundThe attachment object does not exist
416emptyThe range is unsatisfiable
504Gateway timeoutTransformation capacity was unavailable or the transformation exceeded its deadline

The common responses also apply.

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.

FieldTypeDescription
signaturestringThe unpadded base64url HMAC-SHA256 of the target component under the deployment secret
target1stringThe 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.

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.

FieldTypeDescription
width?integerThe output width in pixels (1 through the decoded edge bound, any other value returns 400)
height?integerThe output height in pixels under the same rule
format?stringThe output format under the attachment and external format contract (any other value returns 400)
quality?stringThe encoder profile under the quality contract
animated?booleanWhether animated output is requested
download?booleanWhether 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.

StatusBodyCondition
200Complete external representationThe validated origin returned a complete representation
2061Selected external bytesThe origin answered a forwarded range, or a range over buffered or transformed bytes is satisfiable
400Bad requestThe path shape, decoded URL, redirect target, transformation request, or transformation is invalid or prohibited
401UnauthorizedThe path signature is invalid
413Payload too largeThe origin declared or delivered more than 500 MiB
416emptyA local range is unsatisfiable
502Bad gatewayThe origin could not be reached, exceeded the redirect bound, or returned a status outside the retained set
503Service unavailableThe external buffer budget cannot cover the body, or the buffer allocation failed
504Gateway timeoutTransformation 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.

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/themes/{path}Unauthenticated

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.

FieldTypeDescription
path1stringThe 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.

StatusBodyCondition
200Complete CSS representationThe theme object is available
206Selected CSS bytesOne range is satisfiable
400Bad requestThe decoded theme key is invalid
416emptyThe 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-sounds/{user_id}/{filename}Unauthenticated

Returns the stored audio of a user entrance sound. The entrance sounds resource writes the object to entrance-sounds/{user_id}/{hash}.{ext}.

FieldTypeDescription
user_id1stringThe owning user snowflake
filename2string{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.

StatusBodyCondition
200Complete audio representationThe sound object is available
206Selected audio bytesOne range is satisfiable
416emptyThe range is unsatisfiable

The common responses also apply.

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.

FieldTypeDescription
size?1integerThe requested square edge in pixels, snapped to the size ladder and clamped to the asset class
format?2stringThe requested output format, also accepted under the name fmt
quality?stringThe encoder profile under the quality contract, defaulting to high
animated?3booleanWhether animated output is requested
download?booleanWhether 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.

StatusBodyCondition
200Complete asset representationThe original or transformed asset is available
206Selected asset bytesOne range is satisfiable
400Bad requestAn owner segment of . or .., or a hash of a_ on a prefix-stripping path, makes the storage key unsafe
404Not foundThe path is not a valid asset path or the asset does not exist
416emptyThe range is unsatisfiable
5001Transcode failedA required transcode failed and the source is not directly displayable
504Gateway timeoutTransformation 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/avatars/{user_id}/{hash}.{ext}Unauthenticated

Returns the user avatar under the icon size class.

FieldTypeDescription
user_idstringThe owning user snowflake paired with the avatar hash
hashstringThe avatar hash, optionally prefixed with a_ to default to animated output
extstringThe path extension, which selects the output format

The operation uses the shared image asset response.

GET/icons/{guild_id}/{hash}.{ext}Unauthenticated

Returns the guild icon under the icon size class.

FieldTypeDescription
guild_idstringThe owning guild snowflake paired with the icon hash
hashstringThe icon hash, optionally prefixed with a_ to default to animated output
extstringThe path extension, which selects the output format

The operation uses the shared image asset response.

GET/branding/{entity_id}/{hash}.{ext}Unauthenticated

Returns the instance branding image written by Create branding asset, under the icon size class.

FieldTypeDescription
entity_idstringThe instance branding entity key supplied by the branding URL
hashstringThe branding hash, optionally prefixed with a_ to default to animated output
extstringThe path extension, which selects the output format

The operation uses the shared image asset response.

GET/banners/{owner_id}/{hash}.{ext}Unauthenticated

Returns the owner banner under the banner size class.

FieldTypeDescription
owner_idstringThe owning user or guild snowflake paired with the banner hash
hashstringThe banner hash, optionally prefixed with a_ to default to animated output
extstringThe path extension, which selects the output format

The operation uses the shared image asset response.

GET/splashes/{guild_id}/{hash}.{ext}Unauthenticated

Returns the guild splash under the banner size class.

FieldTypeDescription
guild_idstringThe owning guild snowflake paired with the splash hash
hashstringThe splash hash, optionally prefixed with a_ to default to animated output
extstringThe path extension, which selects the output format

The operation uses the shared image asset response.

GET/embed-splashes/{guild_id}/{hash}.{ext}Unauthenticated

Returns the guild embed splash under the banner size class.

FieldTypeDescription
guild_idstringThe owning guild snowflake paired with the embed splash hash
hashstringThe embed splash hash, optionally prefixed with a_ to default to animated output
extstringThe path extension, which selects the output format

The operation uses the shared image asset response.

GET/guilds/{guild_id}/users/{user_id}/avatars/{hash}.{ext}Unauthenticated

Returns the guild member avatar under the icon size class.

FieldTypeDescription
guild_idstringThe guild snowflake owning the member asset
user_idstringThe member user snowflake paired with the member avatar hash
hashstringThe member avatar hash, optionally prefixed with a_ to default to animated output
extstringThe path extension, which selects the output format

The operation uses the shared image asset response.

GET/guilds/{guild_id}/users/{user_id}/banners/{hash}.{ext}Unauthenticated

Returns the guild member banner under the banner size class.

FieldTypeDescription
guild_idstringThe guild snowflake owning the member asset
user_idstringThe member user snowflake paired with the member banner hash
hashstringThe member banner hash, optionally prefixed with a_ to default to animated output
extstringThe path extension, which selects the output format

The operation uses the shared image asset response.

GET/emojis/{emoji_id}.{ext}Unauthenticated

Returns the emoji image under the emoji size class with a square cover crop.

FieldTypeDescription
emoji_id1stringThe emoji snowflake, which is also the storage key
ext2stringThe 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.

The operation uses the shared image asset response.

GET/stickers/{sticker_id}.{ext}Unauthenticated

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.

FieldTypeDescription
sticker_id1stringThe sticker snowflake, which is also the storage key
extstringThe 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.

The operation uses the shared image asset response.

GET/{key}Unauthenticated

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.

FieldTypeDescription
key1stringThe 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

StatusBodyCondition
200Complete objectThe object is available
206Selected bytesOne range is satisfiable
400Bad requestThe decoded key is invalid
416emptyThe range is unsatisfiable

The common responses also apply.

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.

PathMethodDescription
/_healthGETReturns 200 while the process is running
/_metricsGETReturns the Prometheus text exposition of the process. Only a loopback address is served, and any other address returns 403
/_metadataPOSTExtracts media metadata, a placeholder, and an optional NSFW verdict for the HTTP API
/_thumbnailPOSTProduces a WebP thumbnail of a staged upload for the HTTP API
/_framesPOSTExtracts 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.