Skip to content
Fluxer API

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.

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.

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.

FieldTypeDescription
b1stringThe destination bucket
k2stringThe destination object key
m3stringThe authorised method, always put
u?4stringThe multipart upload identifier
p?4integerThe multipart part number
ct?5stringThe media type stored with the object
mb6integerThe maximum body length in bytes
e7integerThe 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

{
"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/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.

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

FieldTypeDescription
t1stringThe capability, as the unpadded base64url payload, a ., and the unpadded base64url signature
uploadId?2stringThe multipart upload identifier, present only when the capability has one
partNumber?3integerThe 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

FieldTypeDescription
Content-Length?1integerThe declared length of the request body
Content-Type?2stringThe 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.

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.

The relay forwards no object storage response body and no arbitrary response headers.

StatusBodyCondition
200emptyThe object or part was stored
400Bad RequestThe partNumber value is unparsable or the client body ended early
401UnauthorizedThe capability is missing, malformed, incorrectly signed, or expired
403ForbiddenThe bucket, key, uploadId, or partNumber disagrees with the capability
404Not FoundThe endpoint does not serve the upload relay
4051emptyThe method is not PUT
413Payload Too LargeThe declared or delivered body exceeds the capability mb field or the endpoint body limit
500Internal Server ErrorA spool write to the relay’s own disk failed
502Bad GatewayThe object store refused the write or could not be written to
503Service UnavailableThe 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.

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.

A successful request writes the object the capability selects to the uploads bucket. It creates no attachment record and no message.

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.