Skip to content

Responses and limits

Media reads have no request-count rate limit, so every bound below is a resource bound. The two media surfaces report failures differently: the media routes under endpoints.media answer with a small JSON object, the media proxy (/external/..., /og) with a plain-text reason.

Media error response

Media routes

An unsuccessful response from an image asset route or from /files/{key} has a JSON body with a single field:

{ "error": "..." }
Field Type Description
error string A short, human-readable reason. The text is not stable and may be localised

This is not the HTTP API error body: there is no code field. Every error response has Cache-Control: no-store and Access-Control-Allow-Origin: *, so a CDN or browser never stores a failure. A 404, 422 or 502 also carries CDN-Cache-Control: no-store and X-Content-Type-Options: nosniff.

A 416 has an empty body. A non-numeric size, width or height on /files/{key} is rejected by request parsing before the route runs, and that 400 has a plain-text body instead of the JSON object.

Media proxy

Every failure on /external/... and /og is a 400 with a text/plain; charset=utf-8 reason phrase, whatever went wrong: an invalid signature, a refused target, a network error, an unsuccessful origin status, a disallowed content type, an oversized body, or a failed transformation. An unknown path returns 404.

If a passthrough body grows beyond the size limit after the response has started, the connection is terminated and the client sees a truncated body.

Handling contract

Branch on the HTTP status only. Do not parse, compare or pattern-match the error text: it can be reworded or translated without notice, and it does not reliably name the parameter that caused the failure.

Status registry

Media routes

Status Body Condition
200 Media bytes (empty for HEAD) A complete representation was served
206 Selected bytes (empty for HEAD) One byte range is satisfiable
400 Error object Invalid owner ID, extension or hash on an image asset path; invalid key or format on /files/{key}
404 Error object The object does not exist, or an image asset path points at an object that is not an image
416 Empty The range is unsatisfiable
422 Error object The object is a PNG, JPEG, GIF or WebP image but could not be decoded or re-encoded
502 Error object The object store could not be read

Media proxy

Status Body Condition
200 Media bytes, or OpenGraph JSON on /og The target was fetched
400 Plain-text reason Any failure
404 Empty The path is not a media proxy route

The media proxy never passes an origin's error status through: an origin 403, 404 or 5xx is reported as 400. No media response is ever a 304; conditional request headers are not evaluated.

Request and media limits

Limit Value
Media proxy: largest proxied or transformed body 10 MiB by default (instance-configurable)
Media proxy: HTML read by /og First 2 MiB of the page (instance-configurable); the rest is not downloaded
Media proxy: redirects followed 5 by default (instance-configurable)
Media proxy: allowed target content types image/*, audio/*, video/mp4, video/webm, video/ogg on /external; text/html, application/xhtml+xml on /og
Media routes: pixels in one decoded frame 64,000,000 (larger sources are returned untransformed)
Media routes: animation 50,000,000 total pixels and 1000 frames (larger animations are returned untransformed)
Media routes: largest output edge 1024 for icons, 2400 for banners, 512 for emoji and stickers, 4096 for other keys

Media-route size limits on the upload side (for example the maximum avatar or attachment size) are enforced when the file is uploaded, not when it is read. See Uploads.

Work admission

Image transformations on the media routes run on a bounded worker pool (by default half the server's CPU cores, at least 2; instance-configurable). When every worker is busy, a request waits for a free worker instead of failing, so a burst of requests is slowed down rather than rejected.

Identical concurrent requests for the same representation share one transformation, and finished representations are kept in an in-memory cache (the x-tebhos-media-cache response header reports hit, miss or bypass). Large representations are not cached.

Deadlines

Deadline Default Applies to
Origin connect timeout 5 s Media proxy fetches
Origin total request timeout 10 s (instance-configurable) Media proxy fetches, including the body

A media proxy fetch that exceeds its deadline fails with 400. The media routes have no per-request deadline of their own.

Cache policies

Response Cache-Control
Image asset route, success public, max-age=31536000, immutable
/files/{key} image, success public, max-age=31536000
/files/{key} non-image object public, max-age=604800
Signed external, passthrough public, max-age=86400
Signed external, transformed public, max-age=604800
/og public, max-age=3600
Any media route error, and 416 no-store

Successful media route responses also carry CDN-Cache-Control: public, max-age=31536000.

Image asset URLs are safe to cache forever: a new upload always produces a new hash, so a changed avatar has a new URL. The same holds for /files/{key}, whose keys carry a unique ID per upload. No media response has ETag or Last-Modified; a cache refreshes a representation by fetching it again.

Range response headers

A complete media route response has Accept-Ranges: bytes, the representation Content-Type, and an exact Content-Length. A 206 also sets Content-Range: bytes {start}-{end}/{size} and a Content-Length equal to the selected byte count. A 416 has Content-Range: bytes */{size}, Accept-Ranges: bytes and an empty body.

HEAD applies the same range contract as GET and returns 206 or 416 with an empty body. The range is applied to the transformed representation, not to the stored object. Byte ranges defines which request ranges are recognised.