I have an API that charges for an order. It accepts the orderId and the amount as inputs. Then it makes a '/charge' call to the downstream, which returns a 202. Immediately after this call, it calls a '/verify' endpoint to make sure that the previous charge was successful.

Now it may happen that the charge was declined. One of the reasons for this can be that the user used an expired card. What should be the error code in this scenario?

As I see it, I can't send a 4xx as the request was correct for my API perspective. A bad request is something that the user can correct - In this case, he can't correct anything since the API just accepts the 'orderId' and the total amount to charge.

If I am sending a 5XX, then 500 does not make sense as this was not an 'unexpected condition' on my server. I can neither send a 503 as my server was not overloaded or down for maintenance.

Currently, I am sending back a 503 with an app code that maps to: Payment verification failed.

  • Don't use HTTP status codes to communicate credit card status. They weren't designed to do that. Oct 21, 2019 at 19:27
  • @RobertHarvey Can you explain a bit more on that? In fact, I am using my own custom codes for communicating that. They are much more detailed. I am just conflicted on a 4xx and a 5xx. Thanks! Oct 21, 2019 at 19:29
  • What do you want to convey in your HTTP status code? Oct 21, 2019 at 19:30
  • The status of the request? Oct 21, 2019 at 19:30
  • 1
    Yes, that's how I see it. HTTP status codes don't have any notion of "business rules." Oct 21, 2019 at 19:35

2 Answers 2


HTTP Status Codes are only designed to tell you about the status of your HTTP transmissions. They have no notion of "business rules."

Stick to 200 and 400. When you get a 400, you can retry the request or fail it. When you get a 200, your message is valid; you can then check the returned metadata for status information from your application (i.e. whether the payment succeeded or not).


You will probably do well to review Jim Webber's 2011 talk. Among his early points; HTTP is an application protocol, the application domain is the transfer of documents over a network.

Your API is a facade designed to provide the illusion that the HTTP requests are being handled by a "document store" - that's what allows us to leverage all of the general purpose appliances that have been built for HTTP; they all agree on the same semantics of the messages.

So when you are trying to identify what the right status code to use for your particular circumstances, you need to be thinking in terms of what a general purpose client will assume is going on when it gets that response from a general purpose server, rather than about how a bespoke client would interact with your bespoke server.

With that in mind, you can start working through the flow chart.

For an unsafe request (PUT, POST, PATCH, ...), the first important thing to figure out is whether you should be signaling that the request was successful -- general purpose caching uses "non-error status codes" to signal cache invalidation. So if you want the client to keep its currently cached copy(-ies) of the resource, then you need to choose an error status.

Broadly, the important distinction between 4xx and 5xx is simply this: is the problem in the request?

The 5xx class of status codes is deliberately coarse grained, because there isn't much that the client can do to solve any problem on the server. You've got 500 (the server fell over), and 503 (the server is out of service), and that's it.

That doesn't seem to fit the scenarios you describe, where the root cause of the problem is the information that was contained within the request. So you should be looking into options in the 4xx class.

403 Forbidden is a reasonable starting point here; "I understood what you wanted, and I'm choosing not to do it." You could also consider 409 Conflict and 422 Unprocessable Entity. The latter has the advantage of highlighting the message-body of the request as the source of the problem.

One of the reasons for this can be that the user used an expired card.

Fine grained domain-specific semantics belong in the response body. "The server SHOULD send a representation containing an explanation of the error situation, and whether it is a temporary or permanent condition."

One way to think of the distinction is to consider what happens on the web; when you do a POST, the thing you get back is a web page, explaining to the human being what has happened, and what additional resources are available to proceed (if any). The meta data, like the response code, is information for the browser, not the human being.

Which means, in practice, that you can be a little bit loose with 403/409/422 -- because the general purpose components, like the web browser, really don't care very much about those distinctions.

See also Mark Nottingham's How To Think About HTTP Status Codes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.