Errors
The Flowlix API uses conventional HTTP response codes and returns structured JSON error objects to help you diagnose and handle problems programmatically.
HTTP status codes
| Code | Meaning |
|---|
200 | OK — The request succeeded. |
201 | Created — A new resource was created (e.g. a payment). |
400 | Bad Request — The request was malformed or had an invalid parameter. |
401 | Unauthorized — The API key is missing or invalid. |
403 | Forbidden — The API key does not have permission for the requested operation. |
404 | Not Found — The requested resource does not exist. |
409 | Conflict — An idempotency key was reused with a different request body, or another request with the same key is currently in flight. |
422 | Unprocessable Entity — The request was well-formed but could not be completed (synchronous card decline, or a refund that violates business rules). |
429 | Too Many Requests — Rate limit exceeded. |
500 | Internal Server Error — Something went wrong on our end. |
503 | Service Unavailable — Flowlix is temporarily unavailable. Retry with exponential backoff. |
201 Created does not mean the card was charged. For Direct API
payments and HPP sessions, 201 only means Flowlix accepted the request
for asynchronous processing. The terminal outcome is reported on the
Payment object’s status field on subsequent GET calls — succeeded,
failed, expired, or requires_action. A declined card commonly shows
up as 201 followed by a GET returning status: "failed" with a
decline_code. The 422 card_error shape is only used in the rarer case
where the upstream provider rejects the card synchronously.
Error object structure
All errors follow the same shape:
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "amount must be greater than or equal to 1",
"param": "amount",
"decline_code": null,
"doc_url": "https://docs.flowlix.dev/api-reference/errors",
"request_id": "req_abc123def456"
}
}
Error fields
| Field | Type | Description |
|---|
type | string | The category of error. See error types below. |
code | string or null | A machine-readable code identifying the error class. For card errors this is always card_declined; the specific decline reason is in decline_code. |
message | string | A human-readable explanation of what went wrong. May contain a bracketed sub-code for refund validation failures (see Refunds). |
param | string or null | The specific request parameter that caused the error, when the API can identify one. May be null for many error classes. |
decline_code | string or null | For card_error only — the reason the card was declined. |
doc_url | string or null | A link to the relevant documentation page. For declined cards this points to the matching /declines/<code> page. |
request_id | string | The unique request ID for support reference. |
Error types
| Type | Description |
|---|
api_error | An unexpected error on Flowlix’s servers. These are rare and should be retried. |
authentication_error | The API key is missing, invalid, or revoked, or the key does not have permission for the requested action. |
card_error | The card was declined synchronously by the upstream provider. Check decline_code for the specific reason. |
idempotency_error | An idempotency key cannot be honored — see the code field. |
invalid_request_error | The request was malformed, missing a required parameter, had an invalid value, or violated a business rule. |
rate_limit_error | Too many requests in a short period. Back off and retry. |
Common error codes
| Code | Type | HTTP | Description |
|---|
parameter_missing | invalid_request_error | 400 | A required header was not provided (the field-level missing case maps to parameter_invalid). |
parameter_invalid | invalid_request_error | 400 | A request parameter is missing, malformed, or violates a constraint. |
invalid_request_body | invalid_request_error | 400 | The request body could not be parsed as JSON. |
invalid_api_key | authentication_error | 401 | The API key is missing, malformed, or not recognized. |
not_permitted | authentication_error | 403 | The API key does not have permission for this operation. |
resource_missing | invalid_request_error | 404 | The requested resource (e.g. payment ID) does not exist. |
idempotency_key_in_use | idempotency_error | 409 | The idempotency key was reused with a different body, or another request with the same key is currently being processed. |
duplicate_request | idempotency_error | 409 | A duplicate of an already-completed request was detected. |
card_declined | card_error | 422 | The card was declined synchronously. The specific reason is in decline_code. |
action_not_allowed | invalid_request_error | 422 | The operation is not allowed in the current state. Used for refund validation failures (the bracketed sub-code in message distinguishes them — see Refunds). |
rate_limit_exceeded | rate_limit_error | 429 | Too many requests. Check the Retry-After header. |
internal_error | api_error | 500 | An unexpected internal error occurred. |
service_unavailable | api_error | 503 | Flowlix or an upstream provider is temporarily unavailable. Retry after a short delay. |
Authentication errors (invalid_api_key) return one of three messages depending on the issue:
"Missing or malformed Authorization header." — no Authorization header or not using Bearer scheme
"Invalid API key format." — key does not start with fl_test_sk_ or fl_live_sk_
"Invalid API key provided." — key format is correct but the key is not recognized, revoked, or the merchant validation backend is unreachable
Refund validation failures (such as “amount exceeds remaining” or
“transaction is not in a refundable state”) all share the top-level
code: "action_not_allowed". The internal validator code is embedded
inside message, prefixed with Refund validation failed [<code>]:.
See Refunds for the full list and matching examples.
Handling errors in code
Here is a recommended pattern for handling Flowlix API errors:
import requests
response = requests.post(
"https://api.flowlix.dev/v1/payments",
headers={"Authorization": "Bearer fl_test_sk_abc123"},
json={"amount": 4999, "currency": "eur", "card": {...}}
)
if response.status_code == 201:
payment = response.json()
# Accepted for processing — poll GET /v1/payments/{payment["id"]}
# until status is succeeded / failed / expired / requires_action
elif response.status_code == 422:
error = response.json()["error"]
if error["type"] == "card_error":
decline_code = error.get("decline_code")
# Synchronous card decline — show a message to the customer
else:
# Business-rule violation — read error["message"]
pass
elif response.status_code == 401:
# Authentication failed — check your API key
pass
elif response.status_code == 403:
# API key not permitted for this operation
pass
elif response.status_code == 409:
# Idempotency conflict — same key with different body, or
# another request with the same key is still in flight
pass
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 30))
# Back off and retry after `retry_after` seconds
elif response.status_code >= 500:
# Flowlix or upstream error — retry with exponential backoff
pass
else:
error = response.json()["error"]
# Other client error — log and investigate
Decline codes
A card_error response carries the specific reason in decline_code. The
same decline codes also appear on the Payment object itself when a card is
declined asynchronously (status: "failed", decline_code: "...", with
the matching decline_message):
| Decline code | Meaning |
|---|
generic_decline | The card was declined for an unspecified reason. |
do_not_honor | The issuer declined without a specific reason. |
insufficient_funds | The card does not have enough funds. |
expired_card | The card has expired. |
invalid_number | The card number is not valid. |
lost_card | The card has been reported lost. |
stolen_card | The card has been reported stolen. |
card_velocity_exceeded | The card has exceeded its transaction limit. |
The decline_code is extracted by the gateway from the upstream provider’s
response when the message follows the form Card declined: <code>. If the
upstream provider returns a non-card or unstructured failure, decline_code
may be null even on a card_error response.
See the Decline Codes section for details on
each code, including customer-facing messages and merchant actions.
Tips
- Always check
error.type to determine how to handle the error.
- Log
request_id from every error response — you will need it if you contact support.
- Use
decline_code for card errors to show appropriate messages to customers.
- Retry on
api_error and 503 with exponential backoff (start at 1 second, max 30 seconds).
- Respect
Retry-After on rate limit errors instead of guessing a wait time.
- Match on the bracketed sub-code in the
message for refund validation failures, since they all share code: "action_not_allowed".