Authentication
The API uses the OAuth 2.0 client credentials grant. You exchange your client_id and client_secret for a JWT access token, then send that token on every request.
:::note Sandbox vs production
Sandbox (https://sandbox.merchants.taifapay.africa) and production (https://merchants.taifapay.africa) each have their own client_id / client_secret — a sandbox token only works against the sandbox host, and vice versa.
:::
This is your company token — it's what you use for every endpoint in these guides (transactions, checkout invoices, customers, wallet lookups). There's no separate customer-facing credential to issue yourself: customer authentication (SMS OTP) happens only on Taifa Pay's own hosted checkout page, never through this API — see Customers & wallets.
Obtaining a token
POST /v1/auth/token
Credentials go in the HTTP Basic Authorization header — base64(client_id:client_secret) — and the body carries the grant type:
curl -X POST https://sandbox.merchants.taifapay.africa/v1/auth/token \
-u "your_client_id:your_client_secret" \
-H "Content-Type: application/json" \
-d '{"grant_type": "client_credentials"}'
Successful response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
"expires_in": "3599",
"token_type": "Bearer"
}
| Field | Meaning |
|---|---|
access_token | JWT scoped to your company |
expires_in | Lifetime in seconds (~1 hour) |
token_type | Always Bearer |
A 401 Unauthorized means the credentials are wrong, the Authorization: Basic header is missing/malformed, or grant_type is not client_credentials.
Using the token
Send the token on every API call, in either of two equivalent headers:
# Standard header (recommended)
-H "Authorization: Bearer eyJhbGciOiJIUzI1..."
# Alternative header, same format including the Bearer prefix
-H "x-auth-token: Bearer eyJhbGciOiJIUzI1..."
Token lifecycle
- Tokens live for about 1 hour (
expires_in: 3599). - Request a new token before the old one expires and cache it; do not request a fresh token per API call.
- A
401on a normal endpoint usually just means the token expired — fetch a new one and retry once.
Example caching pattern (Node.js):
let cached = { token: null, expiresAt: 0 };
async function getToken() {
if (cached.token && Date.now() < cached.expiresAt - 60_000) {
return cached.token;
}
const res = await fetch('https://sandbox.merchants.taifapay.africa/v1/auth/token', {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64'),
'Content-Type': 'application/json',
},
body: JSON.stringify({ grant_type: 'client_credentials' }),
});
if (!res.ok) throw new Error(`Token request failed: ${res.status}`);
const data = await res.json();
cached = {
token: data.access_token,
expiresAt: Date.now() + Number(data.expires_in) * 1000,
};
return cached.token;
}
Keeping credentials safe
- Store
client_secretin a secret manager or environment variable, never in source control. - Only call the API from your backend. The customer-facing checkout page needs no credentials — customers just open the
checkoutUrl. - If a secret leaks, contact support to rotate it immediately.