Errors
Every failure returns the same shape, with a stable machine code you can branch on and a message meant for a human reading a log.
The error shape
{
"error": {
"code": "card_not_enabled",
"message": "Card payments are not configured for this organisation.",
"param": "checkoutMethod",
"doc_url": "https://flik.co.nz/docs/errors/#card_not_enabled"
}
}
amount.total. The key is
always present; it is null when no single field
is to blame.
validation_failed only — one entry per
field that failed.
if (error.code === "token_expired"), never
if (error.message.includes("expired")). Message
wording is not part of this contract.
One exception: POST /api/token predates this shape.
On a 400 or 401 it answers
{ "error": "<message>" } — a string, not an
object — so branch on the status there. See
Authentication.
HTTP status codes
| Status | Meaning | Retry? |
|---|---|---|
| 200 | Success. | — |
| 400 | Your request, or your account configuration, needs changing. | Not without changing something |
| 401 | Authentication failed. | Only token_expired |
| 404 | No such resource — or it exists but is not yours. | No |
| 500 | Something went wrong at Flik's end. | Yes, with backoff |
Validation
validation_failed
400. One or more fields are missing, the wrong
type, or out of range. details lists every problem,
so one round trip tells you everything rather than making you fix
them one at a time.
{
"error": {
"code": "validation_failed",
"message": "The request body failed validation.",
"param": "amount.total",
"doc_url": "https://flik.co.nz/docs/errors/#validation_failed",
"details": [
{
"field": "amount.total",
"constraints": { "max": "total must not be greater than 10000" }
},
{
"field": "creditorReference.reference",
"constraints": {
"matches": "[0-9A-Za-z '.,@_-] are allowed. Maximum 12 characters."
}
}
]
}
}
The most common causes: a total over
10000 or with more than two decimal places, a
reference longer than 12 characters or containing
punctuation the banks will not carry, and a
redirectUrl that is not a valid absolute URL.
Account configuration
These four mean the request was well-formed but your Flik account cannot do what you asked. The fix is in the portal, not in your code. Retrying will not help.
card_not_enabled
400. You asked for card or
both, but card payments are not configured for your
organisation. Set card payments up in the portal, or use
open_banking.
open_banking_not_enabled
400. You asked for open_banking or
both, but your open-banking application has not been
approved. Approval is a Flik-side process — if it is outstanding,
talk to us.
settlement_account_missing
400. Bank payments settle into your nominated account and your organisation does not have one recorded. Add a bank account in organisation settings.
open_banking_currency_unsupported
400. Your organisation settles in a currency the
bank rail cannot carry. Open banking is New Zealand dollars only.
If you settle in AUD, use card.
This is structural rather than a configuration gap — there is nothing to switch on. It is checked before the other two so you get the real reason rather than a misleading one.
Currency
currency_mismatch
400. You sent an amount.currency
that is not your organisation's settlement currency.
Flik rejects rather than converting or silently repricing. An AUD
merchant whose integration still sends NZD out of
habit would otherwise have every amount charged in AUD and
reconciled against a number they believed was NZD. A 400 turns
that into a one-line fix.
The simplest remedy is to stop sending amount.currency
at all — it is optional, and Flik fills it in.
Idempotency
idempotency_error
400. You reused an Idempotency-Key
with a different request body. Keys are remembered for 24 hours.
Flik refuses rather than guessing which request you meant. Either you meant to retry — in which case send the identical body — or this is a new payment and needs its own key. Deriving the key from your order id avoids the problem entirely.
Authentication
unauthorized
401. No Authorization header, or it
is not in the form Bearer <token>.
token_expired
401. The token was valid but has passed its expiry. The one error worth retrying automatically — fetch a new token and repeat the request, once.
invalid_token
401. The token could not be verified. Almost always a truncated, mangled or double-prefixed header rather than anything sinister.
unknown_api_key
401. The token is well-formed but the API key it was issued for no longer exists — it was removed after the token was issued. Getting a new token will not help; you need working credentials.
Not found
not_found
404. One of three things:
- The id does not exist.
- It exists but belongs to another organisation. Flik answers 404 rather than 403 deliberately — a 403 would confirm that an id you are not entitled to see is real.
-
You called
GET /api/transaction/{id}for a checkout session. That endpoint is not part of this API; useGET /api/checkout-sessions/{id}. For a payment link the same endpoint answers while the link is still payable and 404s once it settles — either way, do not build on it: read the link back withGET /api/payment-links/{id}.
Server errors
internal_error
500. Something failed at Flik's end. Retry with exponential backoff.
Send an Idempotency-Key so those retries are
safe. Without one, a 500 is genuinely ambiguous — the
session may or may not have been created before the failure, and
retrying may produce a second payment. With one, a retry either
completes the original request or replays its result.
If a 500 persists for more than a few minutes, contact support with the timestamp and, if you have it, the session id.
Handling errors well
-
Switch on
error.code. Never match on message text. -
Retry only
token_expiredand 500s. Everything else needs a change first, so retrying just burns requests. -
Log
code,paramandmessagetogether. The three of them usually make the cause obvious without a reproduction. - Treat account-configuration errors as alerts, not customer-facing failures. They mean something needs doing in the portal, and they will affect every payment until it is.
- Handle unknown codes. New codes get added. Fall back to "payment could not be started" rather than crashing.