Authentication
Flik uses OAuth 2.0 client credentials. You exchange a key pair for a short-lived bearer token, then send that token on every API call.
API keys
Create keys under API Keys in the Flik portal. Each key is a pair:
Proves the request is yours. Shown once, at creation. Flik stores only a hash and cannot recover it — if you lose it, create a new key.
Test and live keys
A key is test or live from the moment it is made, and the portal decides which by its own Test Mode switch, at the bottom of the sidebar. Turn it on before you create the key and you get a test key; leave it off and you get a live key. The mode is visible in the credential itself:
| Prefix | Mode | Effect |
|---|---|---|
flik_live_ |
Live | Real payments. Money moves. |
flik_test_ |
Test |
Sessions behave normally, no money moves, no bank is
contacted. Responses carry
"testMode": true.
|
The prefix exists so mistakes are visible. Assert on it when your
application boots — a staging environment holding a
flik_live_ key should refuse to start rather than
take a real payment during a test run.
There is no separate sandbox host. Test and live use the same base URL and the same endpoints; the key decides. See Testing.
Getting an access token
Send your key pair as
application/x-www-form-urlencoded. This is the one
Flik endpoint that does not take JSON — post it
as a form body and a JSON body is rejected as missing
credentials, which reads confusingly if you are not expecting it.
This endpoint takes no Authorization header. The key
pair in the body is the credential. Every other endpoint
is the other way round: a bearer token in the header and no
credentials in the body.
Authorization header, and does not accept
client_id/client_secret in
snake_case. The field names are clientId and
clientSecret exactly.
Parameters
Response
{
"accessToken": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresIn": "1h"
}
Bearer.1h. Read this rather than assuming it.
Using the token
Send it as a bearer token on every other API call:
Authorization: Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...
What the token grants
A token carries the organisation the key belongs to and nothing
else. There are no scopes to request and no per-endpoint
permissions: a token can do everything the merchant API offers,
for that one organisation, and nothing at all for any other. A
request naming another organisation's session or payment link
comes back 404 rather than 403, because
a 403 would confirm the id exists.
Test and live are decided by the key, not by the token and not by
the URL. A token minted from a flik_test_ key can
only ever create test payments.
Token lifetime
Tokens are valid for one hour. Long enough that a token exchange is a once-an-hour cost rather than something on the path of every call, short enough that one found in a log afterwards has almost certainly died with the hour. Your API key is the standing credential; the token is not.
Read the lifetime from expiresIn rather than
hard-coding an hour. It has already changed once — it was five
minutes — and a client that read the field kept working while one
that assumed the number did not.
Two failure modes worth avoiding:
-
Caching a token past its life. You get
401 token_expiredat an unpredictable moment, usually under load, because that is when the cached value is being reused hardest. - Fetching a fresh token for every request. It works, but it doubles your call count and puts a password hash comparison in front of every operation.
The sensible pattern is to cache the token in memory, expire it slightly early so a request never starts with a token that dies mid-flight, and refresh on demand. The panel alongside shows it. Cache in memory, not in a shared store: a token is cheap to re-mint and a shared cache is one more place it can leak from.
Authentication errors
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized |
No Authorization header, or it is not a
Bearer token.
|
| 401 | token_expired |
The token was valid but has passed its expiry. Fetch a new one and retry the request once. |
| 401 | invalid_token |
The token could not be verified. Usually a truncated or mangled header. |
| 401 | unknown_api_key |
The token is well-formed but its key no longer exists — it was removed after the token was issued. |
token_expired is the only one worth retrying
automatically, and only once. See
Errors for the full catalogue.
Errors from the token endpoint
This endpoint predates the error envelope: error is
a plain string here, so branch on the status.
| Status | Cause | Fix |
|---|---|---|
| 400 |
clientId or clientSecret missing,
empty, or sent more than once.
|
Usually a JSON body where a form body was expected, or snake_case field names. |
| 401 | The credentials did not match. Unknown client ID and wrong secret give the same response. | Check for a truncated secret, a stray newline from a copy, or a test key against a live integration. If you no longer have the secret, create a new key. |
| 500 | Flik-side failure. | Retry with backoff; tell us if it persists. |
Rotating a key
Keys have no expiry. To rotate: create a second key in the portal, deploy it, confirm traffic has moved, then ask us to revoke the old one — keys cannot yet be deleted from the portal. Both work simultaneously, so there is no cutover window.