Something failed on our side, or in a system we depend on. The body is the ordinary {success, code, message} envelope.
Only code is contractual. message is not. The text on this path describes an internal failure, is not drawn from any documented set of strings, and can change without notice. Switch on code, keep the whole body in your logs if it helps you, and never parse message, match on it, or show it to a payer.
The shared code is SYSTEM_DATABASE_ERROR, reachable on fourteen of the sixteen operations. The two conversion operations cannot produce it and describe their own failure codes; the operations that can raise a second 500 code name it in their own description.
A 500 can also arrive with a code this reference does not list — today, SYSTEM_UNEXPECTED_ERROR. It is not part of the contract, its cause is not documented, and it is handled exactly like any other 500. Nothing is promised about when it fires.
A 500 is usually not a signal to change your request. Retry once, then escalate rather than looping — a 500 that keeps repeating is almost always ours to fix, not yours.
The "almost" is deliberate. A few inputs pass validation and then produce a 500 that only a change to the request will clear, and retrying those never helps: a currency field sent as a JSON array rather than a string, on card-payment create and on both conversion operations; a destination on POST /v1/conversions/quote that the operation cannot price, which is only checked for length; and amount_type on that same operation sent as null or "" instead of being omitted. None of those refusals is documented and no status is promised for them — send a three-letter string, send a currency you are credited in, and omit an optional field rather than emptying it.