Error Handling
Understanding API error responses
Error Format
All API errors return a consistent JSON format:
{
"code": "VALIDATION_ERROR",
"message": "Invalid currency code",
"details": { "field": "currency" },
"requestId": "req_abc123"
}Error Codes
| HTTP Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Request body or parameters are invalid |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 403 | FORBIDDEN | API key does not have permission for this action |
| 404 | NOT_FOUND | The requested resource does not exist |
| 409 | CONFLICT | The request conflicts with the current state (e.g., double capture) |
| 500 | INTERNAL_ERROR | An unexpected error occurred on our side |
Refund limits
A merchant account can carry a maximum number of refunds per day. Once it is
reached, every refund you request answers 400:
{
"code": "VALIDATION_ERROR",
"message": "Daily refund limit reached: 20 of 20 refunds already requested today. Contact us to refund more today.",
"details": { "reason": "DAILY_REFUND_LIMIT_REACHED", "limit": 20, "used": 20 },
"requestId": "req_abc123"
}Branch on details.reason rather than on the message. The counter covers the
refunds you request — from the API or from your dashboard — over a UTC
calendar day, and resets at 00:00 UTC. A partial refund counts as one, like a
full one. Failed refunds do not count. Refunds initiated by the payment provider
are never blocked and never count against it.
Retrying does not help before the next UTC day: contact us to have the limit raised or lifted.
Declined refunds
When the payment provider refuses a refund, the refund endpoint answers 400:
{
"code": "VALIDATION_ERROR",
"message": "Refund declined by the payment provider: Refund declined by issuer",
"details": { "reason": "REFUND_DECLINED_BY_PSP", "pspMessage": "Refund declined by issuer" },
"requestId": "req_abc123"
}No money moved. The attempt is recorded and shows as failed on the payment's
refund list; it does not count against your daily refund limit. In sandbox,
trigger this case with the test card 4000000000005126 (the payment succeeds,
any refund of it is declined).
Handling Errors
const response = await fetch("https://api.settleflow.io/v2/payments", {
method: "POST",
headers: {
"X-Api-Key": apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const error = await response.json();
console.error(`${error.code}: ${error.message}`);
// Handle specific error codes
if (error.code === "VALIDATION_ERROR") {
// Show validation errors to the user
}
}Request IDs
Error responses carry a requestId, as in the example above. Successful
responses do not.
You can also set your own on the way in, with an X-Request-Id header, and it
will be the one echoed back on an error — useful for stitching a failure to the
call in your own logs.