104 Upload Resumption Supported
Informational response defined by draft-ietf-httpbis-resumable-upload.
Last updated July 27, 2026
Common causes at a glance
- A server implementing the IETF resumable uploads draft advertising support mid-upload
- A client sending an Upload-Draft-Interop-Version header the server recognises
- A tus-style upload service exposing the draft-based protocol
- Absence of the code because an intermediary buffered the request and swallowed the interim response
Reproduce it
curl -i -X POST -H 'Upload-Draft-Interop-Version: 6' --data-binary @large.bin https://api.example.com/uploadsWhat 104 Upload Resumption Supported means
Resumable upload protocols all need the same first step: the client has to learn, cheaply, whether the server supports resumption before it commits to sending gigabytes. 104 is that signal. The server emits it as an interim response early in the upload, along with the URL the client should use to query progress and resume, and then continues receiving the body. If the connection dies, the client asks that URL how many bytes landed and continues from there instead of starting over.
What the spec says
104 is registered in the IANA HTTP Status Code Registry as a temporary registration, referencing the HTTP working group’s resumable uploads draft (draft-ietf-httpbis-resumable-upload). Temporary registrations expire unless renewed, and draft semantics change between revisions — the interop version is carried in a request header precisely because of that. The general rules for interim 1xx responses in RFC 9110 §15.2 still apply: no body, and clients that do not understand it must ignore it.
What actually causes it
Because this is draft-stage, the common problem is version skew
rather than a server bug. Client and server must agree on the same interop version, and
a mismatch usually shows up as no 104 at all followed by an ordinary non-resumable
upload. Any hop that fully buffers request bodies — a number of WAFs and older reverse
proxies do — also prevents the server from responding until the upload finishes, which
defeats the mechanism entirely. Do not build a product dependency on 104 without a
fallback to plain POST.
How to debug it
First confirm both ends speak the same draft revision, then watch for the interim response while the body is still uploading:
curl -sv -X POST \
-H 'Upload-Draft-Interop-Version: 6' \
--data-binary @large.bin \
https://api.example.com/uploads 2>&1 | grep -E '^< (HTTP|location|upload-)'
If you see the final status with no 104 in between, check whether a proxy is buffering: send the same request directly to the origin and compare. Because the registration is temporary, re-read the current draft before you write client code — the header names and version token have changed across revisions, and treating any of this as stable is how you end up with an upload client that breaks on a server upgrade.
Working examples
curl
# The interop version header is mandatory; the exact value tracks the draft.
curl -sv -X POST \
-H 'Upload-Draft-Interop-Version: 6' \
-H 'Content-Type: application/octet-stream' \
--data-binary @large.bin \
https://api.example.com/uploads 2>&1 | grep -E '^< (HTTP|location|upload-)'
Python (requests)
import requests
# Interim responses are not exposed by requests. Use it to confirm the final
# result, and probe resumption support explicitly instead of relying on 104.
resp = requests.post(
"https://api.example.com/uploads",
headers={
"Upload-Draft-Interop-Version": "6",
"Content-Type": "application/octet-stream",
},
data=open("large.bin", "rb"),
timeout=300,
)
print(resp.status_code, resp.headers.get("Location"))
Node (fetch)
// Watch for the interim response with node:http; fetch() will not show it.
import http from "node:http";
import { createReadStream } from "node:fs";
const req = http.request(
{ host: "api.example.com", path: "/uploads", method: "POST",
headers: { "Upload-Draft-Interop-Version": "6" } },
(res) => console.log("final:", res.statusCode, res.headers.location),
);
req.on("information", (info) => {
if (info.statusCode === 104) console.log("resumable upload supported");
});
createReadStream("large.bin").pipe(req);Reference and tooling
The authoritative list of assigned status codes is theIANA HTTP Status Code Registry. For a searchable copy with the semantics summarised, use theHTTP status code reference on this site. To see exactly what a server is sending, theheader inspector shows the raw status line and response headers, and thecurl builder assembles the request flags for you.