docs(media-proxy): stop documenting literal response bodies (#2541)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-06 18:39:50 +02:00
1 parent 7a42291baf
commit 81fccaf0ab
4 files changed
+46 -47

No files matched your search

@@ -59,9 +59,9 @@ Which published base URL serves which mode is a deployment choice. The reference
## 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 path returns 405 with the body `Method Not Allowed`.
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 path returns 405.
The relay path accepts `PUT`. Any other method there returns 405 with an `Allow` header. An unknown path returns 404 with the body `Not Found`.
The relay path accepts `PUT`. Any other method there returns 405 with an `Allow` header. An unknown path returns 404.
The [signed external route](/media-proxy/routes/#get-signed-external-media) is the one place a HEAD can answer differently from the matching GET. A HEAD with no range and no transformation is served from an origin HEAD when that origin returns 200, declares a length within the [500 MiB media bound](/media-proxy/responses-and-limits/#request-and-media-limits), and names a non-SVG media type.
@@ -90,7 +90,7 @@ The Media Proxy reads no `If-None-Match`, `If-Modified-Since`, `If-Range`, `If-M
An operator MAY gate public reads on a CDN edge address allowlist. The gate is disabled by default. When it is enabled, the process fetches the Bunny edge address list at startup and refreshes it every 3600 seconds by default, and a process whose first fetch fails does not start.
A request from an address outside the list returns 403 with the body `origin not in bunny allowlist`. `/_health`, `/_metrics`, `/_metadata`, `/_thumbnail`, `/_frames`, and every path below `/v1/relay/` are exempt.
A request from an address outside the list returns 403. `/_health`, `/_metrics`, `/_metadata`, `/_thumbnail`, `/_frames`, and every path below `/v1/relay/` are exempt.
When the peer address is a configured trusted proxy, the client address is the rightmost `X-Forwarded-For` hop that is not itself a trusted proxy. Otherwise the Media Proxy uses the peer address and ignores `X-Forwarded-For`. It does the same when there is no `X-Forwarded-For`, or when every hop in it is unparseable or is itself a trusted proxy.
@@ -12,19 +12,18 @@ An unsuccessful response body is an English reason phrase under the content type
| Status | Body | Condition |
| --- | --- | --- |
| 400 | `Bad Request` | An invalid storage key, dimension, format, or external path, an unparsable relay `partNumber`, a failed attachment or external transformation, or a relay upload the endpoint could not take<sup>1</sup> |
| 401 | `Unauthorized` | An invalid signed external path signature, a missing, malformed, or expired relay capability, or a missing or invalid internal bearer token |
| 403 | `origin not in bunny allowlist` | The media access allowlist rejected the client address |
| 403 | `Forbidden` | A relay capability presented for another bucket, key, method, `uploadId`, or `partNumber` |
| 403 | `FORBIDDEN` | `/_metrics` requested from a non-loopback address |
| 404 | `Not Found` | A `GET` or `HEAD` of an unrouted path, an object that does not exist, or a relay `PUT` outside `upload` mode |
| 405 | `Method Not Allowed`<sup>2</sup> | A method other than `GET` or `HEAD` on a read route or on an unrouted path |
| 413 | `Payload Too Large` | A stored object, external body, upload body, or internal request body beyond its bound |
| 500 | `Transcode Failed` | An image asset transcode failed and the source is not directly displayable |
| 500 | `Internal Server Error` | A relay spool write to the endpoint's own disk failed |
| 502 | `Bad Gateway` | An object store read or write failed, an external origin could not be used, or an outbound socket deadline or a relay object storage write deadline expired |
| 503 | `Service Unavailable` | The upload relay spool budget is exhausted, an external buffer reservation or allocation failed, or an external origin answered `/_metadata` with 429<sup>3</sup> |
| 504 | `Gateway Timeout` | A transformation admission slot was unavailable or the transformation deadline expired |
| 400 | Bad request | An invalid storage key, dimension, format, or external path, an unparsable relay `partNumber`, a failed attachment or external transformation, or a relay upload the endpoint could not take<sup>1</sup> |
| 401 | Unauthorized | An invalid signed external path signature, a missing, malformed, or expired relay capability, or a missing or invalid internal bearer token |
| 403 | Media access denied | The media access allowlist rejected the client address |
| 403 | Forbidden | A relay capability presented for another bucket, key, method, `uploadId`, or `partNumber`, or `/_metrics` requested from a non-loopback address |
| 404 | Not found | A `GET` or `HEAD` of an unrouted path, an object that does not exist, or a relay `PUT` outside `upload` mode |
| 405 | Method not allowed<sup>2</sup> | A method other than `GET` or `HEAD` on a read route or on an unrouted path |
| 413 | Payload too large | A stored object, external body, upload body, or internal request body beyond its bound |
| 500 | Transcode failed | An image asset transcode failed and the source is not directly displayable |
| 500 | Internal server error | A relay spool write to the endpoint's own disk failed |
| 502 | Bad gateway | An object store read or write failed, an external origin could not be used, or an outbound socket deadline or a relay object storage write deadline expired |
| 503 | Service unavailable | The upload relay spool budget is exhausted, an external buffer reservation or allocation failed, or an external origin answered `/_metadata` with 429<sup>3</sup> |
| 504 | Gateway timeout | A transformation admission slot was unavailable or the transformation deadline expired |
<sup>1</sup> The relay also answers 400 when the client connection fails part way through the body
@@ -32,7 +31,7 @@ An unsuccessful response body is an English reason phrase under the content type
<sup>3</sup> `/_metadata` is the only endpoint that remaps an origin 429. The signed external read route retains 429 as 429
A retained external origin status uses the body `Upstream fetch failed` on the signed external read route and the canonical reason phrase of its status on `/_metadata`. An object store error that maps to no case above uses the canonical reason phrase of its status.
A retained external origin status reaches the client as an upstream fetch failure on the signed external read route and as the canonical reason phrase of its status on `/_metadata`. An object store error that maps to no case above uses the canonical reason phrase of its status.
Every error a route produces uses `Cache-Control: no-store` and the standard [security headers](/media-proxy/overview/#representation-headers). Three failures have the security headers and set no cache policy, and [Cache policies](#cache-policies) names them. No plain-text error has CORS headers unless it came from the [upload relay](/media-proxy/upload-relay/), and no error has `Retry-After` or a request identifier.
@@ -61,17 +60,17 @@ Every `HEAD` response has an empty body, so the Body column describes `GET`, `PU
| 503 | [Media error response](#media-error-response) | Upload spool capacity is exhausted, an external buffer reservation or allocation failed, or an external origin answered `/_metadata` with 429<sup>5</sup> |
| 504 | [Media error response](#media-error-response) | Transformation capacity was unavailable or a transformation deadline expired |
<sup>1</sup> Four [internal endpoints](/media-proxy/routes/#operator-and-internal-endpoints) answer 200 with a non-media body. `/_metadata` and `/_frames` answer with JSON, `/_health` with the plain text `OK`, and `/_metrics` with the Prometheus text exposition
<sup>1</sup> Four [internal endpoints](/media-proxy/routes/#operator-and-internal-endpoints) answer 200 with a non-media body. `/_metadata` and `/_frames` answer with JSON, `/_health` with plain text, and `/_metrics` with the Prometheus text exposition
<sup>2</sup> A path served by the read fallback answers with the body `Method Not Allowed`. Each registered path answers with an empty body, no `Content-Type`, and an `Allow` header
<sup>2</sup> A path served by the read fallback answers with the [media error response](#media-error-response). Each registered path answers with an empty body, no `Content-Type`, and an `Allow` header
<sup>3</sup> A retained external origin status is the only source of these statuses
<sup>4</sup> A locally unsatisfiable range answers with an empty body, `Content-Range: bytes */{size}`, and `Accept-Ranges: bytes`, and has no `Content-Type` and no cache policy
<sup>5</sup> The remap produces the body `Service Unavailable`, which is indistinguishable from a budget exhaustion 503
<sup>5</sup> The remap produces a service unavailable response, which is indistinguishable from a budget exhaustion 503
An external 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, with the body `Upstream fetch failed`. The origin response body and headers are not forwarded. Any other unsuccessful origin status becomes 502.
An external 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 response body and headers are not forwarded. Any other unsuccessful origin status becomes 502.
The Media Proxy evaluates no conditional request header and never redirects a noncanonical target, so it returns no 304 and no 308.
@@ -91,7 +90,7 @@ Decoded images are limited to 16,384 pixels on either edge and 268,435,456 pixel
The upload relay limits a body to the smaller of the capability's declared maximum and the endpoint's configured body limit, which defaults to the same 500 MiB ceiling and can be configured from 1 byte through 5 GiB. A request that declares no `Content-Length` is spooled to disk first, and spooled bodies share an 8 GiB endpoint budget by default.
An internal `/_metadata`, `/_thumbnail`, or `/_frames` request body is limited to the base64 expansion of the 500 MiB media bound plus 1 MiB. All three answer a larger body with 413 and the body `Payload Too Large`.
An internal `/_metadata`, `/_thumbnail`, or `/_frames` request body is limited to the base64 expansion of the 500 MiB media bound plus 1 MiB. All three answer a larger body with 413.
## Work admission
@@ -20,11 +20,11 @@ Every media read route can produce these statuses in addition to the ones in its
| Status | Body | Condition |
| --- | --- | --- |
| 403 | `origin not in bunny allowlist` | 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 |
| 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
@@ -72,10 +72,10 @@ A video source requires an explicit image `format`. Another transformation param
| --- | --- | --- |
| 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 |
| 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 |
| 504 | Gateway timeout | Transformation capacity was unavailable or the transformation exceeded its deadline |
The [common responses](#common-responses) also apply.
@@ -143,17 +143,17 @@ A video target requires an explicit image `format` to produce a thumbnail. Witho
| --- | --- | --- |
| 200 | Complete external representation | The validated origin returned a complete representation |
| 206<sup>1</sup> | 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 |
| 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 |
| 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 |
<sup>1</sup> 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, with the body `Upstream fetch failed`. The origin body and headers are not returned. Any other unsuccessful origin status becomes 502. The [common responses](#common-responses) also apply.
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](#common-responses) also apply.
### Side effects
@@ -181,7 +181,7 @@ The route selects no representation and accepts a `Range`.
| --- | --- | --- |
| 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 |
| 400 | Bad request | The decoded theme key is invalid |
| 416 | empty | The range is unsatisfiable |
The [common responses](#common-responses) also apply. A path that does not end in `.css` is not a theme path and falls through to 404.
@@ -259,11 +259,11 @@ An owner segment of the literal `.` or `..` parses as an asset path but produces
| --- | --- | --- |
| 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 |
| 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 |
| 500<sup>1</sup> | `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 |
| 500<sup>1</sup> | 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 |
<sup>1</sup> 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`
@@ -481,7 +481,7 @@ The complete request path is the storage key after percent-decoding. The route a
| --- | --- | --- |
| 200 | Complete object | The object is available |
| 206 | Selected bytes | One range is satisfiable |
| 400 | `Bad Request` | The decoded key is invalid |
| 400 | Bad request | The decoded key is invalid |
| 416 | empty | The range is unsatisfiable |
The [common responses](#common-responses) also apply.
@@ -492,10 +492,10 @@ These paths are not part of the public API surface. They are exempt from the med
| Path | Method | Description |
| --- | --- | --- |
| `/_health` | GET | Returns 200 with the body `OK` |
| `/_metrics` | GET | Returns the Prometheus text exposition of the process. A request from a non-loopback address returns 403 with the body `FORBIDDEN` |
| `/_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 with the body `Unauthorized` without it. Their JSON request bodies are bounded, and a body beyond the bound returns 413 with the body `Payload Too Large` on all three.
`/_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.
@@ -170,6 +170,6 @@ Animated WebP and animated APNG output is bounded again at encode time, and exce
The overall transformation deadline defaults to 15,000 ms and can be configured from 1,000 through 120,000 ms. An animated WebP or APNG encode stops 3,000 ms before that deadline so it has time to flush what it has already encoded. Animated GIF output has no such headroom, and a GIF resize that reaches the deadline fails.
An attachment or signed external transformation failure returns 400 with the body `Bad Request`. An image asset transformation failure returns 500 with the body `Transcode Failed` when the source is not directly displayable, and otherwise returns 200 with the original stored bytes and no `Content-Disposition`.
An attachment or signed external transformation failure returns 400. An image asset transformation failure returns 500 when the source is not directly displayable, and otherwise returns 200 with the original stored bytes and no `Content-Disposition`.
A transformation refused admission returns 504, and so does one that exceeds its deadline. [Responses and limits](/media-proxy/responses-and-limits/) defines the admission capacity and the deadlines.