Error codes
How errors are surfaced — three patterns
Understanding which pattern an endpoint uses is the first step to handling errors correctly.
Pattern A — Asynchronous (with-reader)
Applies to: POST /transactions (card-present sale, pre-auth, refund via terminal)
The POST always returns HTTP 202:
{ "statusMessage": "Operation Accepted", "transactionResultId": "..." }
No error is returned at POST time. Poll GET /transaction-result/{transactionResultId} to get the outcome:
| Poll response | Meaning |
|---|---|
| HTTP 204 | Still processing — body is empty. Do not call .json() on this response. Wait and re-poll. |
| HTTP 200 | Result ready — parse the JSON body; read finStatus and statusMessage. |
The error is encoded in finStatus and statusMessage of the HTTP 200 response.
Pattern B — Synchronous flat error (without-reader)
Applies to: POST /reversal, POST /preauthorization/capture, POST /preauthorization/increase, POST /moto/sale, POST /moto/refund, POST /transactions/{id}/tip-adjustment
Error shape:
{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Human-readable description",
"code": "ERROR_CODE_HERE",
"details": { "...endpoint-specific": "data..." }
}
}
Read error.code for programmatic error identification. error.message is human-readable but may be localized.
Pattern C — Synchronous nested error (Get Card Token)
Applies to: GET /transactions/{id}/token
The outer HTTP status is 400. The actual error code from the downstream Viscus system is two levels deep:
{
"error": {
"statusCode": 400,
"name": "BadRequestError",
"message": "Viscus operation failed",
"details": {
"status": 403,
"body": {
"error": {
"errorCode": "3112",
"reason": "Transaction type is not eligible for deferred tokenization",
"httpStatus": "403",
"errorGuid": "..."
}
}
}
}
}
Read error.details.body.error.errorCode for programmatic identification. Do not rely on error.message — it always reads "Viscus operation failed" regardless of the underlying error.
HTTP status codes — without-reader endpoints
| HTTP | name | When it occurs |
|---|---|---|
200 | — | Success |
400 | BadRequestError | Business logic rejection (wrong amount, already reversed, not found) — see code field |
403 | ForbiddenError | Invalid or missing API key |
404 | NotFoundError | GET /transaction-result/{id} — ID not found or expired |
422 | UnprocessableEntityError | Request body validation failed — wrong field names or missing required fields; see details array |
429 | TooManyRequests | Rate limit exceeded — 2 requests per second per merchant API key. Back off and retry after 1 second. For high-throughput ISVs with multiple merchants, use a separate API key per merchant to get an independent rate limit per key. |
Error codes — POST /reversal
code | message | Meaning | What to do |
|---|---|---|---|
3051 | Already reversed | Transaction has already been reversed | Check your records; no further action needed |
3052 | Authorization has already been completed | Transaction was already captured or reversed (applies to both reversal and pre-auth capture/increase) | Check transaction state before acting; no further action needed |
3153 | Unable to find message to reverse. | originalGuid not found | Verify the GUID is the transactionID from the original transaction result |
4066 | Partial reversal amount exceeds original amount | amount exceeds the original transaction amount | Reduce amount or omit amount for a full reversal |
Error codes — POST /preauthorization/capture and POST /preauthorization/increase
code | HTTP | Meaning | What to do |
|---|---|---|---|
3156 | 404 | No pre-authorization found for the originalGuid | Verify the GUID is the transactionID from the pre-auth create result |
3207 | 400 | The referenced transaction is not a pre-authorization | Reference the Create, not an increase or a capture |
3211 | 403 | The pre-authorization was declined, already captured, or already reversed | Check transaction state before adjusting or capturing |
3212 | 403 | The decrease would take the hold to zero or below | Send a Pre-Auth Reversal to release the hold in full |
3215 | 403 | The capture amount exceeds the current hold total | Increase the hold first, then capture |
5001 | 400 | NullPointerException — internal error surfaced for unknown GUIDs on these endpoints | Verify the GUID is the transactionID from the pre-auth create result |
On POST /preauthorization/increase the amount field is increaseAmount (not amount) and takes a decimal major-unit string, e.g. "20.00". Sending amount returns 422 VALIDATION_FAILED.
For how increases and decreases accumulate, which GUID to reference, and the per-path decrease signal, see the Pre-Authorization Guide.