Upload relay
The upload relay is where a client sends the bytes of a file. Each upload URL has a signed capability, and the relay stores the one object it authorises. Request attachment upload URLs and Create stream preview upload URL issue them. Attachment uploads states the flow the relay takes part in.
Base URL and authorisation
Section titled “Base URL and authorisation”Both paths here are relative to the relay base in the issued upload URL. That base is a deployment setting.
The capability is the complete authorisation. The relay reads no HTTP API Authorization header and has no request-count rate limit. The PUT has a deployment mode gate and returns 404 outside upload mode. Every path below /v1/relay/ is exempt from the media access policy.
Relay capability object
Section titled “Relay capability object”A relay capability is a JSON payload signed with HMAC-SHA256 under the deployment relay secret. The signed value is the encoded payload text, and the capability is that text, a ., and the signature. Both halves use unpadded base64url.
The payload is opaque to a client.
Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| b1 | string | The destination bucket |
| k2 | string | The destination object key |
| m3 | string | The authorised method, always put |
| u?4 | string | The multipart upload identifier |
| p?4 | integer | The multipart part number |
| ct?5 | string | The media type stored with the object |
| mb6 | integer | The maximum body length in bytes |
| e7 | integer | The expiry as a Unix timestamp in seconds |
1 The uploads bucket configured on the endpoint. Any other value returns 403
2 Equal to the decoded request key. Any other value returns 403
3 A payload naming another method returns 401
4 Present together on a multipart part capability and absent together on a single-object capability. The relay checks the query string values against these fields and then forwards both to the object store
5 Takes precedence over the request Content-Type. When neither declares one, an S3 backend stores the object as application/octet-stream
6 The HTTP API sets it to the exact length of the object or part for an attachment, and to 1,000,000 for a stream preview
7 The bound is exclusive, so the relay rejects the capability once the clock reaches it. The HTTP API issues a 900 second lifetime by default
Example
Section titled “Example”{ "b": "fluxer-uploads", "k": "a3f1c8de-4b21-4f0e-9c77-2d5b6e1a0f43", "m": "put", "ct": "image/png", "mb": 184320, "e": 1780000000}The relay answers 401 to a malformed capability, a signature that is not 32 bytes, an incorrect signature, a payload that is not valid unpadded base64url, a payload that is not valid JSON, and an expired capability.
Put relay object
Section titled “Put relay object”PUT/v1/relay/{key}Stores the exact object the relay capability authorises and returns 200 with an empty body. An ETag accompanies it when the object store supplied one. An S3 backend supplies one and a local filesystem backend does not.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| key1 | string | The object key of the issued URL |
1 The key can contain / separators and the issuer percent-encodes each segment. The relay compares the decoded key against the capability k field, and any other value returns 403
Fluxer issues a UUID for an attachment and stream_previews/{name}-{digest}.jpg for a stream preview.
Query parameters
Section titled “Query parameters”| Field | Type | Description |
|---|---|---|
| t1 | string | The capability, as the unpadded base64url payload, a ., and the unpadded base64url signature |
| uploadId?2 | string | The multipart upload identifier, present only when the capability has one |
| partNumber?3 | integer | The multipart part number, present only when the capability has one |
1 A missing t returns 401
2 When the capability has u, the relay rejects an absent or differing value with 403. When it has no u, the relay rejects a non-empty value with 403 and accepts an absent or empty one
3 An empty or unparsable value returns 400. When the capability has p, the relay rejects an absent or differing value with 403, and when it has no p, the relay rejects any value with 403
Request headers
Section titled “Request headers”| Field | Type | Description |
|---|---|---|
| Content-Length?1 | integer | The declared length of the request body |
| Content-Type?2 | string | The media type to store, read only when the capability declares none |
1 A declared length above the capability mb field or above the endpoint body limit returns 413
2 The capability ct field takes precedence over it
The relay ignores every other request header.
Request body
Section titled “Request body”The body is arbitrary bytes.
With Content-Length, the relay streams the body to the object store as it arrives. A body longer than the declared length returns 413 and a body shorter than it returns 400.
Without Content-Length, the relay spools the body to a temporary file first, bounded by the smaller of the capability mb field and the endpoint body limit. Exceeding that bound returns 413. A client transfer that fails part way returns 400. The relay returns 503 when the endpoint spool budget cannot cover the request.
Response
Section titled “Response”The relay forwards no object storage response body and no arbitrary response headers.
| Status | Body | Condition |
|---|---|---|
| 200 | empty | The object or part was stored |
| 400 | Bad Request | The partNumber value is unparsable or the client body ended early |
| 401 | Unauthorized | The capability is missing, malformed, incorrectly signed, or expired |
| 403 | Forbidden | The bucket, key, uploadId, or partNumber disagrees with the capability |
| 404 | Not Found | The endpoint does not serve the upload relay |
| 4051 | empty | The method is not PUT |
| 413 | Payload Too Large | The declared or delivered body exceeds the capability mb field or the endpoint body limit |
| 500 | Internal Server Error | A spool write to the relay’s own disk failed |
| 502 | Bad Gateway | The object store refused the write or could not be written to |
| 503 | Service Unavailable | The endpoint spool budget cannot cover the request |
1 It has no relay CORS headers and no cache policy
Complete attachment upload assembles a multipart upload once every part is stored, and a singlepart upload needs no completion step. A stream preview becomes readable as soon as the stored object is in place, and the Streams resource defines that flow.
The relay has no replay tracking. Repeating a request with the same capability writes the object again, and the last successful write wins. Retrying a multipart part after a 502 or 503 is safe, because a part is addressed by its number and the repeated write replaces it.
Response headers
Section titled “Response headers”The 200, 400, 401, 403, 413, 500, 502, and 503 have the relay CORS headers. The 404 and the 405 have none of them.
Relay CORS means Access-Control-Allow-Origin: *, Access-Control-Allow-Headers: Content-Type, Content-Length, Authorization, X-Fluxer-Features, X-Client-Context, and Access-Control-Expose-Headers: ETag, X-Fluxer-Version.
Side effects
Section titled “Side effects”A successful request writes the object the capability selects to the uploads bucket. It creates no attachment record and no message.
Streaming limits
Section titled “Streaming limits”The endpoint body limit defaults to 500 MiB and an operator can configure it from 1 byte through 5 GiB. The effective limit for one request is the smaller of that value and the capability mb field.
A spooled body is written through a 1 MiB buffer by default, configurable from 64 KiB through 64 MiB. Spooled bodies share an 8 GiB endpoint budget by default, configurable up to 256 GiB. A request that arrives with no Content-Length reserves the complete effective limit from that budget before the relay reads a byte. The endpoint refuses to start when its body limit exceeds that budget, so one reservation always fits an idle endpoint.
The object storage write has a 900,000 ms deadline by default, configurable from 1,000 through 3,600,000 ms. For a streamed body the relay extends the deadline by one second for every 16 KiB of declared length.