# API · How to read an error response

> An HTTP 200 does not mean success. Where the issuer decline arrives and how to write error handling.

- kind: concept
- status: stable
- api_version: v1
- last_verified: 2026-10-22
- url: https://tilopay.com/en/developers/api/procesos-operativos/errors

## The issuer decline [#rechazo]

When the issuer declines a transaction, the response carries two fields:

<Param id="error-code" name="code" type="string">
The issuer decline code.
</Param>

<Param id="error-description" name="description" type="string">
The decline text.
</Param>

## An HTTP 200 does not mean success [#estado-http]

> **Warning:** There is no defined standard for when the API responds HTTP 4xx and when it responds HTTP 200
> with the error in the body. Both forms coexist, and there are five different error envelopes
> depending on the endpoint.
> 
> Practical consequence: your error handling must always look at the body, not only the status
> code, and tolerate more than one body shape.

## Error body shapes [#formas-del-cuerpo]

`processModification` error (`type` 300 or 400):

```json
{"type":"400","message":"Order number: TYP974_172 does not exist"}
```

Five different envelopes, depending on the endpoint:

| Envelope | Endpoints |
|---|---|
| `{"error": "Unauthorized"}` | `login`, `loginSdk` |
| `{"type", "message"}` | `processPayment`, `processModification` |
| `{"type", "result"}` | `processRecurrentPayment`, `processTokenize` |
| `{"code", "description", "error"}` | server to server |
| `{"code", "description"}` | issuer decline |

## How to write error handling [#como-manejar]

- Treat `code` and `description` as data to log and display, not as an enumeration to branch on.
- Do not write `if (status === 200) success`. Check the body.
- Store the raw response of every decline: it is what lets you reconstruct your merchant's
  decline pattern.
- To determine the real state of a transaction after a failure, use
  [safe retries](/en/developers/reintentos-seguros).
