# 

Two different things can come back from a payment that did not go through, and
they need opposite handling.

* An **error** means the request failed. You get a `4xx` or `5xx` and an error
body. No transaction was created.
* A **decline** means the request succeeded and the issuer said no. You get a
`200` and a transaction whose `result` is `DECLINED`.


This page covers both, errors first.

## The error envelope

Every error, from every endpoint, has the same four fields.

```json
{
  "error_code": "INVALID_REQUEST_DATA",
  "error_message": "The API request contains invalid data.",
  "details": {
    "amount": "Invalid amount",
    "payment_method.pan": "Malformed PAN"
  },
  "source": {
    "system": "TSYS",
    "code": "13",
    "message": "Invalid amount"
  }
}
```

| Field | Purpose |
|  --- | --- |
| `error_code` | The stable code. Branch on this |
| `error_message` | The same thing in words, for a log. May be reworded; do not match on it |
| `details` | Field-level specifics, as a flat map of field name to problem. May be null |
| `source` | The processor's own system, code, and message, passed through unchanged. May be null |


Two rules govern it:

* **Specifics live in `details`, never in `error_message`.** That keeps the code
list small and the messages stable.
* **`source` is the processor, not us.** `system` is `TSYS` or `VERICHECK`. It is
there for your support and reconciliation, not for your control flow — you
branch on `error_code`.


## The codes

Twelve codes can reach you today.

### Something is wrong with the request

| HTTP | `error_code` | What happened | Retry |
|  --- | --- | --- | --- |
| 400 | `INVALID_REQUEST_DATA` | A field is present and its value is unusable | After fixing the value |
| 400 | `REQUIRED_DATA_MISSING` | A required field is absent | After supplying it |
| 400 | `INVALID_ACTION` | The request is well formed, but the operation does not apply to this transaction's state | No |
| 400 | `AMOUNT_LIMIT_EXCEEDED` | A single amount is outside the allowed range | No |
| 400 | `VELOCITY_LIMIT_EXCEEDED` | A count or value cap over a period is spent | Later |


`INVALID_ACTION` is the one to read carefully. It does not mean the request was
malformed; it means the thing you asked for does not make sense for that
transaction right now — capturing a sale, refunding something unsettled,
canceling something already canceled. The section below lists the common ones.

### You are not allowed

| HTTP | `error_code` | What happened | Retry |
|  --- | --- | --- | --- |
| 401 | `NOT_AUTHENTICATED` | `Api-Key` or `Client-Id` is missing or wrong | No |
| 403 | `UNAUTHORIZED_ACTION` | Authenticated, but not permitted to do this | No |
| 404 | `RESOURCE_NOT_FOUND` | No such URL, or no such resource ID | No |
| 409 | `DUPLICATE_ACTION` | The gateway or the processor has already seen this | No |


A `401` does not distinguish a missing header from a wrong key. Check that
**both** `Api-Key` and `Client-Id` are present before assuming the key is bad —
see [Authentication](/docs/get-started/authentication).

### Something failed on our side or the processor's

| HTTP | `error_code` | What happened | Who fixes it | Retry |
|  --- | --- | --- | --- | --- |
| 500 | `SYSTEM_ERROR` | An unhandled fault inside the gateway | Us | Yes |
| 500 | `INTEGRATION_CONFIG_ERROR` | The merchant's stored integration record is wrong, such as a bad MID | Us, with the merchant's boarding data | Not until it is corrected |
| 502 | `DOWNSTREAM_ERROR` | The gateway reached the processor and could not use the reply | Us | Yes, but reconcile first |


**`500` and `502` are not interchangeable.**
A `500` means the processor was never reached, so no transaction exists.
A `502` means it was reached and **may have seen the transaction**.
There is no idempotency key, so retrying a payment after a `502` without
reconciling first can charge twice. See
[Errors and retries](/docs/get-started/errors).

## What the OpenAPI description declares

The description declares **seven** of these twelve on its operations:
`INVALID_REQUEST_DATA`, `NOT_AUTHENTICATED`, `UNAUTHORIZED_ACTION`,
`RESOURCE_NOT_FOUND`, `DUPLICATE_ACTION`, `SYSTEM_ERROR` and `DOWNSTREAM_ERROR`.

The other five — `INVALID_ACTION`, `REQUIRED_DATA_MISSING`,
`AMOUNT_LIMIT_EXCEEDED`, `VELOCITY_LIMIT_EXCEEDED` and
`INTEGRATION_CONFIG_ERROR` — arrive from the processor-mapping layer and are not
in the description yet.

So **a generated SDK's error enum is not the whole list.** Handle an unrecognized
`error_code` by falling back on the HTTP status rather than throwing.

## Common `INVALID_ACTION` conditions

All of these return `400` with `INVALID_ACTION`. The code stays the same and the
message narrows.

**Capture**

* Transaction already captured
* The transaction is not in an authorized state
* Cannot capture a `SALE`
* Cannot capture an ACH transaction
* Cannot capture a declined transaction


**Cancel**

* Transaction already canceled
* Cannot cancel a declined transaction
* Cannot cancel a transaction that has already settled


**Refund**

* Cannot refund a transaction that has not settled
* Already refunded in full
* The amount exceeds the remaining refundable amount
* The amount exceeds the original transaction amount
* Cannot refund a canceled transaction


**Batch close**

* A batch close is already in progress
* No open batch available to close


**Payment method and transaction type**

* Both card data and a payment token were sent
* Both ACH data and a payment token were sent
* `AUTH` is not supported for ACH
* `PAYOUT` is not supported for cards
* `CAPTURE`, `CANCEL` and `REFUND` are not valid types for creating a transaction


## Declines are not errors

**A declined payment is a successful API call.** It returns `200` with a
transaction whose `result` is `DECLINED`. No `error_code` is set, because nothing
went wrong with the request.

```json
{
  "result": "DECLINED",
  "response_code": "51",
  "response_description": "Insufficient funds",
  "batch_id": null
}
```

Three tests separate the two:

1. **Did the processor make a decision?** A decision, even a negative one, is a
decline. A failure to reach a decision is an error.
2. **Would resending the identical request help?** If no, because the issuer
refused, it is a decline.
3. **Is there a field to fix or a setting to change?** If yes, it is an error. If
the request was valid and the money simply was not authorized, it is a
decline.


Treating a decline as an error is the most expensive mistake on this page. A
client written against a `4xx` will retry it, alert on it, or show the cardholder
a system fault — and none of those is right.

[Handle declines](/docs/payments/handle-declines) covers what to do with each
outcome.

## Reading the processor's own code

`source.code` and `source.message` pass through unchanged, so you see exactly
what TSYS or VeriCheck said.

| Processor | Code shape | Example |
|  --- | --- | --- |
| TSYS | Two characters | `13` invalid amount, `51` insufficient funds |
| VeriCheck | `TN` and three digits | `TN007` routing number not provided |


These are for humans. Quote them in a support ticket alongside the
`x-request-id` header; do not branch on them, because the mapping between
processor codes and our codes can change without our contract changing.

**Never show a processor code or message to a cardholder.** "Do not honor" and
`51` mean nothing to them, and some imply things about their account that are not
yours to relay.

## Next steps

* [Errors and retries](/docs/get-started/errors)
* [Handle declines](/docs/payments/handle-declines)
* [Known limitations](/docs/reference/known-limitations)