402 Payment Required
Client error response defined by RFC 9110 §15.5.3.
Last updated July 27, 2026
Common causes at a glance
- A payment provider rejecting the card behind the request, as Stripe does
- A metered API plan whose included quota or credit balance is exhausted
- A free trial that expired while the API key stayed valid
- An unpaid invoice putting the account into a suspended-but-authenticated state
Reproduce it
curl -i -H "Authorization: Bearer $TOKEN" https://api.example.com/v1/renderWhat 402 Payment Required means
402 is the only 4xx in the core specification that says nothing at all about what it means. The original intent was some form of digital-cash negotiation that never materialised, and rather than reassign the number, successive revisions of HTTP have left it parked. That makes it a squatter’s code: available, memorable, undefined.
What it means on the wire is therefore whatever the API you are calling decided. Stripe uses it for card errors — declines, insufficient funds, expired cards. Other SaaS APIs use it for quota exhaustion on a paid plan, trial expiry, or an unpaid invoice. Those are different situations and a client cannot distinguish them from the status alone, so any handling you write has to read the response body.
What the spec says
RFC 9110 §15.5.3 is one sentence: the code “is
reserved for future use”. No required header, no defined body, no caching guidance beyond
the general 4xx rules in §15.5, and no registration process for the
ad-hoc meanings vendors have attached to it. MDN’s
402 reference
documents the same non-status. Because the code carries no semantics, the
machine-readable part has to live in the body, and RFC 9457 problem details
is the sensible container: a stable type URI distinguishing “card declined”
from “quota exceeded” is the thing clients can branch on.
What actually causes it
Almost every real 402 is a billing state, and the giveaway is that authentication succeeded — the credential is fine, the account is not. Stripe documents 402 for card errors in its error handling guide, where the useful information is the decline code in the body rather than the status. Metered APIs are the other family: exhaust the included credit on an inference or SMS endpoint and you get 402 rather than 429, because the limit is commercial rather than rate-based. That distinction drives client behaviour. A 429 is worth retrying after a delay; a 402 keeps failing until a human updates a payment method.
How to debug it
Confirm first that this is a billing state and not a misclassified authorization failure. If the same token works on other endpoints, the credential is valid and the account is the problem. Then read the body, because the status alone carries nothing:
curl -s -o body.json -w '%{http_code}\n' -H "Authorization: Bearer $TOKEN" \
https://api.example.com/v1/render && jq . body.json
Look for a vendor error code, a type URI, or a link to a billing page, then
check the provider’s dashboard for a failed charge or an exhausted balance. Make sure
your retry layer excludes 402: generic backoff wrappers treat every non-2xx as retryable,
and a suspended account then produces a stream of doomed requests. Alert once and stop
calling.
Working examples
curl
# The status carries no meaning; the body does. Capture both.
curl -s -o body.json -w 'status=%{http_code}\n' \
-H "Authorization: Bearer $TOKEN" \
https://api.example.com/v1/render
jq . body.json
# Prove the credential is valid by hitting a non-metered endpoint.
curl -i -H "Authorization: Bearer $TOKEN" https://api.example.com/v1/me
Python (requests)
import requests
resp = requests.post(
"https://api.example.com/v1/render",
headers={"Authorization": f"Bearer {token}"},
json={"template": "invoice"},
timeout=30,
)
if resp.status_code == 402:
# 402 is not retryable: no amount of backoff fixes an unpaid invoice.
detail = resp.json()
print("billing failure:", detail.get("type"), detail.get("detail"))
raise SystemExit("payment method or plan needs human attention")
resp.raise_for_status()
Node (fetch)
const res = await fetch("https://api.example.com/v1/render", {
method: "POST",
headers: {
Authorization: "Bearer " + token,
"Content-Type": "application/json",
},
body: JSON.stringify({ template: "invoice" }),
});
if (res.status === 402) {
const body = await res.json().catch(() => ({}));
// Branch on the body's type/code, never on 402 alone.
console.error("billing:", body.type ?? body.code, body.detail ?? body.message);
// Do not retry. Escalate.
}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.
Other 4xx codes
- 400 Bad Request
- 401 Unauthorized
- 403 Forbidden
- 404 Not Found
- 405 Method Not Allowed
- 406 Not Acceptable
- 407 Proxy Authentication Required
- 408 Request Timeout
- 409 Conflict
- 410 Gone
- 411 Length Required
- 412 Precondition Failed
- 413 Content Too Large
- 414 URI Too Long
- 415 Unsupported Media Type
- 416 Range Not Satisfiable
- 417 Expectation Failed
- 418 (Unused)
- 419 Page Expired
- 421 Misdirected Request
- 422 Unprocessable Content
- 423 Locked
- 424 Failed Dependency
- 425 Too Early
- 426 Upgrade Required
- 428 Precondition Required
- 429 Too Many Requests
- 431 Request Header Fields Too Large
- 444 No Response
- 451 Unavailable For Legal Reasons
- 494 Request Header Too Large
- 495 SSL Certificate Error
- 496 SSL Certificate Required
- 497 HTTP Request Sent to HTTPS Port
- 499 Client Closed Request