HTTP 303 is See Other: the answer to this request is at another URL, given in Location, and the client should fetch it with GET (or HEAD) whatever method it used (RFC 9110 section 15.4.4). It’s the redirect for “done, now look over there”. The classic use is sending a browser from a form’s POST to a page that shows the result.
Post/Redirect/Get
POST /orders HTTP/1.1
Content-Type: application/x-www-form-urlencoded
item=42&qty=1
HTTP/1.1 303 See Other
Location: /orders/1017
The browser follows with GET /orders/1017. Reloading that page repeats the GET, not the order, and the back button doesn’t offer to resubmit the form.
303, 302 or 307
Browsers follow the Fetch standard, which changes the method like this:
301, 302 POST becomes GET; PUT, PATCH and DELETE are kept
303 every method except GET and HEAD becomes GET
307, 308 the method and the body are kept
A 302 works after a classic form POST, but after a fetch() with DELETE or PUT it sends the same DELETE or PUT to the new URL. 303 is the only redirect that always lands on a GET. Rails’ docs recommend it for that case, and its scaffolds answer a destroy with status: :see_other. Outside browsers, RFC 9110 lets a client keep POST on a 302, one more reason to say 303 when you mean it.
Gotchas
- FastAPI’s
RedirectResponsedefaults to 307, which makes the browser repeat the POST at the new URL, often into a 405. Passstatus_code=303. - Express’s
res.redirect()defaults to 302. Fine for form POSTs; pass 303 for anything a script might send with another method. curl -Lturns a POST into a GET on a 303, unless you pass--post303. But with-X POST, curl uses POST for every request it makes, the followed one included; send the body with-dand drop-X.- Caching. A 303 isn’t heuristically cacheable; it’s stored only with explicit freshness. The page it points to is cached on its own terms.
- On a GET, RFC 9110 reads a 303 as “there’s no representation of this, but here’s a resource that describes it”. Linked Data uses it that way for URIs that name things rather than documents.
In your stack
res.redirect(303, `/orders/${order.id}`); // Express
redirect_to order_path(@order), status: :see_other # Rails
return RedirectResponse(f"/orders/{order.id}", status_code=303) # FastAPI
return redirect(url_for("order", id=order.id), code=303) # Flask
http.Redirect(w, r, "/orders/1017", http.StatusSeeOther)
Search 302 303 307 in the tool below to compare the three.