Clients branch on the code and display the message

A client matching on the text of an error breaks when the wording is improved, translated or corrected — and every one of those will happen.

{
  "error": {
    "code": "payment.card_declined",
    "message": "Your card was declined.",
    "retryable": false,
    "fields": { "card_number": ["invalid_checksum"] }
  }
}

The stable identifier separate from the human string costs nothing at the point the first error is designed and cannot be retrofitted once clients exist. retryable earns its place quickly: without it every client invents its own rule about which failures deserve a retry button, and those rules disagree with each other and with the server. Per-field codes rather than sentences are what let a form highlight the right input in the right language.