# Error handling

Errors follow [RFC 9457 Problem Details](https://www.rfc-editor.org/rfc/rfc9457.html) (the successor to RFC 7807) and include a `code` field for programmatic error identification.

### Example error response

```json
{
  "type": "https://docs.ripple.com/products/stablecoin",
  "title": "Not Found",
  "status": 404,
  "code": "TRANSACTION_NOT_FOUND"
}
```

## HTTP status codes

| HTTP Status | Description |
|  --- | --- |
| `400 Bad Request` | Invalid request parameters or body |
| `401 Unauthorized` | Missing or expired access token |
| `403 Forbidden` | Insufficient scope for the requested operation |
| `404 Not Found` | Resource does not exist |
| `409 Conflict` | Invalid state transition |
| `500 Internal Server Error` | Server error; contact support if persistent |
| `502 Bad Gateway` | An upstream system Ripple depends on could not be reached. Returned by the wallets, bank-accounts, fiat-bank-details, and fiat-instructions (get, list, create) endpoints; safe to retry |


## Error codes

| Code | HTTP Status | Description |
|  --- | --- | --- |
| `TRANSACTION_NOT_FOUND` | 404 | Transaction ID not found |
| `TRANSACTION_INVALID_STATUS_TRANSITION` | 409 | Cannot update a transaction that is not in `PENDING` status (e.g., approving an already `PROCESSING` or `COMPLETED` transaction) |
| `TRANSACTION_DESTINATION_TYPE_IMMUTABLE` | 409 | A destination has already been chosen for this pending redemption and cannot be changed. Submitting the *same* destination again is idempotent and returns `200 OK`; only a *different* destination produces this error |
| `WALLET_NOT_FOUND` | 404 | The `walletRipId` on a `CRYPTO` approval does not resolve to one of your wallets (a wallet belonging to another customer is reported the same way). List your wallets with `GET /v1/stablecoin/wallets` to get a valid ripId |
| `WALLET_NOT_APPROVED` | 409 | The `walletRipId` on a `CRYPTO` approval resolves to one of your wallets, but its `status` is not `APPROVED`. Only an approved wallet can receive RLUSD |
| `INVALID_REQUEST_BODY` | 400 | Malformed or invalid request body (e.g., `walletRipId` provided when `type` is `FIAT`, or missing `walletRipId` when `type` is `CRYPTO`; or a fiat instruction `destination.type` other than `CRYPTO`) |
| `FIAT_INSTRUCTION_NOT_FOUND` | 404 | Fiat instruction id is unknown, belongs to another customer, has been deleted, or (for `DELETE`) addresses a redemption (`FIAT`) instruction |
| `FIAT_INSTRUCTION_ALREADY_EXISTS` | 409 | A fiat instruction with this `id` already exists with a different body; use a new `id` |
| `FIAT_INSTRUCTION_INVALID_STATUS_TRANSITION` | 409 | Attempted to move a fiat instruction out of a terminal status (e.g., deleting a `USED` instruction) |
| `STABLECOIN_BANK_DETAILS_NOT_FOUND` | 404 | `GET /v1/stablecoin/fiat-bank-details` has no bank to return: no active default bank account, no link to a Ripple receiving bank, or a linked bank that cannot be paid. Not transient |
| `FORBIDDEN` | 403 | Your tenant is not entitled to the API — Ripple holds no RLUSD customer record for it. Returned by `PUT`/`GET /v1/stablecoin/fiat-instructions` and `GET /v1/stablecoin/fiat-bank-details`. Not transient |
| `MOCK_RECORD_LIMIT_EXCEEDED` | 400 | **UAT only.** A `PUT /v1/stablecoin/wallets` or `PUT /v1/stablecoin/bank-accounts` body carrying more than 50 records. See [Seed wallets and bank accounts](/pt-br/products/stablecoin/api/uat#seed-wallets-and-bank-accounts) |
| `UPSTREAM_UNAVAILABLE` | 502 | An upstream system Ripple depends on could not be reached. Safe to retry — wallets, bank-accounts, fiat-bank-details, and fiat-instructions endpoints (get, list, create). Create is idempotent on `id`, safe to retry despite being a write |
| `UNKNOWN_EXCEPTION` | 500 | Unexpected server error |


Not found on list endpoints
`GET /v1/stablecoin/wallets` and `GET /v1/stablecoin/bank-accounts` never return `404` for an absent record. An unknown `ripId` returns `200` with an empty `content` array and `totalElements: 0` — treat "no rows" as the not-found signal.