Skip to content
Last updated

API response codes

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.

{
  "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"
  }
}
FieldPurpose
error_codeThe stable code. Branch on this
error_messageThe same thing in words, for a log. May be reworded; do not match on it
detailsField-level specifics, as a flat map of field name to problem. May be null
sourceThe 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

Thirteen codes can reach you today.

Something is wrong with the request

HTTPerror_codeWhat happenedRetry
400INVALID_REQUEST_DATAA field is present and its value is unusableAfter fixing the value
400REQUIRED_DATA_MISSINGA required field is absentAfter supplying it
400INVALID_ACTIONThe request is well formed, but the operation does not apply to this transaction's stateNo
400AMOUNT_LIMIT_EXCEEDEDA single amount is outside the allowed rangeNo
400VELOCITY_LIMIT_EXCEEDEDA count or value cap over a period is spentLater

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

HTTPerror_codeWhat happenedRetry
401NOT_AUTHENTICATEDApi-Key or Client-Id is missing or wrongNo
403UNAUTHORIZED_ACTIONAuthenticated, but not permitted to do thisNo
404RESOURCE_NOT_FOUNDNo such URL, or no such resource IDNo
409DUPLICATE_ACTIONThe gateway or the processor has already seen thisNo

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.

Something failed on our side or the processor's

HTTPerror_codeWhat happenedWho fixes itRetry
500SYSTEM_ERRORAn unhandled fault inside the gatewayUsYes
500INTEGRATION_CONFIG_ERRORThe merchant's stored integration record is wrong, such as a bad MIDUs, with the merchant's boarding dataNot until it is corrected
502DOWNSTREAM_ERRORThe gateway reached the processor and could not use the replyUsYes, but reconcile first
503PROCESSOR_UNAVAILABLEThe processor is down or unreachable, and did not process the requestThe processorYes, with backoff

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. A 503 means the processor did not process the request, so no transaction exists, and a retry after a backoff is safe. There is no idempotency key, so retrying a payment after a 502 without reconciling first can charge twice. See Errors and retries.

What the OpenAPI description declares

The description declares eight of these thirteen on its operations: INVALID_REQUEST_DATA, NOT_AUTHENTICATED, UNAUTHORIZED_ACTION, RESOURCE_NOT_FOUND, DUPLICATE_ACTION, SYSTEM_ERROR, DOWNSTREAM_ERROR and PROCESSOR_UNAVAILABLE. The last is named in the description of the 503 response, which carries no example yet.

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.

{
  "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 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.

ProcessorCode shapeExample
TSYSTwo characters13 invalid amount, 51 insufficient funds
VeriCheckTN and three digitsTN007 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