Understanding HTTP 418 I'm a Teapot: The April Fools' Code That Refused to Die
418 I'm a Teapot is the rare HTTP status code whose entire appeal is that it was never supposed to be taken seriously — and then the internet took it seriously anyway. It started as a joke buried in an April Fools' RFC in 1998, nearly got deleted from major HTTP libraries in 2017, and survived because enough developers pushed back. This post covers where 418 actually comes from, the campaign that kept it alive, what today's HTTP standard says about it, and why — despite all the affection for it — it's the wrong code to reach for when something has genuinely gone wrong.
What Is a 418?
A 418 response tells the client that the server refuses to brew coffee because the server is, in fact, a teapot.
Any attempt to brew coffee with a teapot should result in the error code '418 I'm a teapot'. The resulting entity body MAY be short and stout. — RFC 2324, Section 2.3.2
That's not a paraphrase softened for a blog post — it's the literal text of the specification, published on April 1, 1998, as part of the Hyper Text Coffee Pot Control Protocol (HTCPCP), a joke RFC about controlling coffee pots over HTTP. RFC 2324 says outright that it "does not specify an Internet standard of any kind" and is classified as Informational, not Standards Track. The 418 status code has never, at any point, been a real, load-bearing part of the HTTP specification.
418 was an April Fools' joke from 1998 about the Hyper Text Coffee Pot Control Protocol. It means "I'm a teapot, not a coffee pot!" While it's not a real status code for production use, it's beloved by developers and sometimes used as an easter egg or for testing purposes.
In 2014, a second April Fools' RFC — RFC 7168, published (again) on April 1st — extended HTCPCP to cover networked tea brewing, and reused 418 for tea-incapable pots rather than redefining it. Two jokes, sixteen years apart, both landing on the same day of the year on purpose.
The 2017 Campaign to Save It
In August 2017, Mark Nottingham — at the time chair of the IETF's HTTP working group — argued that since 418 was never part of a real standard, it should be stripped out of Node.js, and suggested Go, Python's Requests, and ASP.NET follow suit. The reasoning was defensible: 418 doesn't appear in RFC 7231 (the HTTP spec of the time), so keeping it around in production HTTP libraries was arguably carrying a joke where it didn't belong.
The response was immediate. A then-15-year-old developer, Shane Brunswick, put up save418.com, the post went viral on Reddit, and #save418 trended as a hashtag. Node.js, Go, Python's Requests, and ASP.NET's HttpAbstractions library all ended up keeping 418 rather than removing it. The episode is a useful reminder that "not in the spec" and "not real" aren't the same thing once enough tooling and enough developers have built habits — and even inside-joke Easter eggs — around a status code.
Why RFC 9110 Still Won't Define It
Here's the detail that resolves the tension between "beloved by developers" and "not a real standard": RFC 9110 — the current HTTP semantics specification, published in 2022 — does reserve a slot for 418 in its status code registry, but the entry is titled "418 (Unused)" (Section 15.5.19). RFC 9110 doesn't carry forward the teapot semantics from RFC 2324, and it doesn't repurpose the number for something else either — it deliberately leaves it defined-as-undefined. IANA's own status code registry lists 418 the same way: "(Unused)," referencing that section.
So the honest way to describe where 418 stands: it isn't erased, and it isn't standardized. It's a number the HTTP registry has agreed to leave alone, out of respect for how deeply it's embedded in developer culture, without ever promoting it to a real, defined status.
418 vs Real Error Codes
418 doesn't have a natural confusable pair the way 401/403 or 404/410 do — its whole identity is that it sits outside the normal 4xx contract. What's worth comparing it against is what a real 4xx code promises, since that's exactly what 418 doesn't:
| Code | Meaning | Standards status |
|---|---|---|
| 400 Bad Request | The request is malformed | Standards Track, RFC 9110 |
| 404 Not Found | The resource doesn't exist (or the server won't say) | Standards Track, RFC 9110 |
| 451 Unavailable For Legal Reasons | Access is blocked by legal demand | Standards Track, RFC 7725 |
| 418 I'm a Teapot | A teapot was asked to brew coffee | Informational, April Fools' RFC 2324 — reserved as "(Unused)" in RFC 9110 |
Every other row in that table is a code you can build real error-handling logic around, with a defined meaning a client is expected to act on. 418 is the one row where the correct client behavior, per the spec that introduced it, is to laugh.
Legitimate(ish) Uses
The situations where teams actually reach for 418, with the understanding that none of them are "real errors":
- The server is, narratively, a teapot. Literal Easter eggs — an endpoint that exists purely to return the joke, often as a nod to the RFC itself.
- Testing and fixtures. Because 418 is instantly recognizable and guaranteed not to collide with a real error condition in your application, it's occasionally used as an unambiguous placeholder status in test suites or mock servers.
- Developer culture, not user-facing behavior. Teams sometimes wire it into internal tools or April Fools' releases — always as a deliberate wink, never as the response to an actual failure a real client needs to handle correctly.
Returning 418 (Responsibly)
Express / Node.js
// An intentional easter egg — not a real error path
app.get("/brew-coffee", (_req, res) => {
res.status(418).json({
error: "im_a_teapot",
message: "I'm a teapot, short and stout. Try /brew-tea instead.",
});
});Next.js App Router
// app/brew-coffee/route.ts
export async function GET() {
return Response.json(
{ error: "im_a_teapot", message: "I'm a teapot, short and stout." },
{ status: 418 },
);
}NGINX
# A dedicated, clearly-labeled easter egg route — not wired into real error handling
location = /brew-coffee {
default_type application/json;
return 418 "{\"error\":\"im_a_teapot\",\"message\":\"I am a teapot, short and stout.\"}";
}REST API JSON body
HTTP/1.1 418 I'm a teapot
Content-Type: application/json
{"error": "im_a_teapot", "message": "I'm a teapot, short and stout."}Keep it isolated to a route that's obviously a joke. The moment 418 shows up on an endpoint a real client is expected to handle programmatically, it's stopped being a wink and started being a bug.
Common Pitfalls
- Using 418 for a genuine error condition. Nothing about 418 communicates anything a real client can act on — reaching for it instead of 400, 404, or 503 for an actual failure just confuses whatever's consuming your API.
- Assuming 418 is part of the official HTTP standard because it "feels" standardized. It's from an Informational April Fools' RFC and is explicitly marked "(Unused)" in RFC 9110 — it has never been Standards Track.
- Stripping 418 support from a library without checking for real dependents. The 2017 near-removal showed that plenty of tooling, tests, and easter eggs quietly depend on 418 staying available, even though it's a joke — removing it isn't as harmless as "it's not a real code" makes it sound.
- Treating "(Unused)" in RFC 9110 as "available for reuse." IANA still lists 418 in the registry — it's reserved, not open for a project to redefine with new, serious semantics.
- Putting 418 in front of a client that doesn't expect it. Some HTTP clients, proxies, or monitoring tools treat an unrecognized or joke status code oddly (e.g., logging it as an anomaly) — know your audience before wiring 418 into anything beyond a clearly-labeled easter egg.
Wrapping Up
418 is proof that a joke can outlive the platform it joked about, provided enough people decide it's worth keeping. The rules of thumb:
- 418 means a teapot was asked to brew coffee — literally, per RFC 2324, an April Fools' RFC from 1998
- It has never been Standards Track, and RFC 9110 keeps it reserved as "(Unused)" rather than defining or repurposing it
- The 2017
#save418campaign is why it's still supported in Node.js, Go, Python's Requests, and ASP.NET today - Use it for easter eggs and clearly-isolated jokes only — never as a real error response a client is expected to handle
For more, see our page on 418 I'm a Teapot, and our posts on 400 Bad Request and 451 Unavailable For Legal Reasons for what a genuinely standardized 4xx code looks like by comparison.