Authentication
Create an API key, send it, and understand scopes, environments, and rotation.
Contents
Creating an API key
Keys are created in the dashboard, never through the API — a key that could mint another key would make every key equivalent to the strongest one.
- Go to Developers → API keys.
- Use the environment switch in the top bar to choose
testorlive. A key belongs to exactly one of them. - Choose Secret — keys are for server-side use. Never ship one to a browser, a mobile binary, or a repository.
- Tick the scopes the integration actually needs. You can create a narrower key later.
- Copy the key. It is shown once: Radok stores only a keyed hash, so there is no way to reveal it again — not for you, and not for us.
Live keys need approved KYB
403 KYB_LIVE_REQUIRED until your business verification is approved. Test keys are available immediately.Sending the key
Pass the key as a bearer token on every request. There is no other accepted form — no query parameter, no custom header.
Authorization: Bearer sk_test_a1b2c3_YOUR_KEYcurl https://api.radokpay.com/v1/collections \
-H "Authorization: Bearer $RADOK_SECRET_KEY"const response = await fetch("https://api.radokpay.com/v1/collections", {
headers: {
Authorization: `Bearer ${process.env.RADOK_SECRET_KEY}`,
},
});
const { data, meta } = await response.json();import os, requests
response = requests.get(
"https://api.radokpay.com/v1/collections",
headers={"Authorization": f"Bearer {os.environ['RADOK_SECRET_KEY']}"},
timeout=30,
)
body = response.json()Read the key from the environment
Key format
A key encodes what it is, where it works, and which merchant it belongs to. The merchant fragment lets a leaked key be traced back to an account during an incident without a database lookup.
sk_test_a1b2c3_kJ8nQv2xLp...
│ │ │ └── 28 random bytes, base64url
│ │ └───────── 6-character merchant fragment
│ └────────────── environment: test | live
└───────────────── type: sk (secret)Key types
| Field | Type | Description |
|---|---|---|
| sk_ | secret | Full access within its scopes. Server-side only. Never ship one to a browser, a mobile binary, or a repository. |
Scopes
A key carries an explicit list of what it may do. A request outside that list is rejected with 403 SCOPE_INSUFFICIENT before any business logic runs.
Available scopes
| Field | Type | Description |
|---|---|---|
| collections:read | read | List and read collections. |
| collections:write | write | Create collections. |
| virtual_accounts:read | read | List and read virtual accounts. |
| virtual_accounts:write | write | Mint a virtual account. Billable in live mode. |
| payouts:read | read | List and read payouts and saved payout accounts. |
| payouts:write | write | Create a payout. This moves money — grant it deliberately. |
| kyc:read | read | Read past KYC verifications. |
| kyc:write | write | Run a BVN or NIN verification. Billable in live mode. |
Webhooks are configured in the dashboard
Your own role caps the key
payouts:write, that scope is silently dropped rather than granted — so a bug in a permission check can never produce a payout-capable key from a Developer session.Test and live
Everything in Radok — credentials, ledger balances, virtual accounts, collections, payouts, webhook endpoints and their signing secrets — exists twice, once per environment. The two never see each other.
testalways routes through a stub gateway. No real bank is contacted, no fee is charged, and no money moves.liveroutes through the real PSP and requires an approved KYB application.
Configure both webhook endpoints
Rotating a key
Rotation creates a new key immediately and lets you choose how long the old one keeps working, so you can deploy without a window where neither key is valid.
- On the API keys page, choose Rotate and pick a grace period, up to 24 hours.
- Copy the new key. Like creation, it is shown once.
- Deploy it everywhere.
- The old key stops working when the grace period ends. Choose No overlap instead if the key is already compromised — that revokes it the instant the new one is issued.
Rotating and revoking both require re-entering your password and 2FA code. That is deliberate: a stolen dashboard session should not be enough to lock you out of your own integration.
Rate limits
Limits are counted per key, not per IP — your servers usually share one egress address, so an IP-based limit would either starve real traffic or be meaningless.
Limits by route class
| Field | Type | Description |
|---|---|---|
| Reads | 300 / minute | Every GET endpoint. |
| Writes | 30 / minute | Creating virtual accounts, payouts, KYC checks. |
| Write burst | 10 / 10 seconds | An additional short-window cap on the same write endpoints. |
Exceeding a limit returns 429 RATE_LIMITED with a Retry-After header in seconds. Wait at least that long — retrying sooner just consumes the next window.
IP allowlist
A key can be restricted to a set of source addresses in CIDR notation. When the list is empty, every address is accepted; when it is non-empty, anything outside it is rejected with 403 IP_NOT_ALLOWED before the key is even considered valid.
Worth setting for live secret keys
If a key leaks
- Revoke it. Not rotate — revoke. Rotation with a grace period leaves the leaked key working for the length of that window.
- Create a replacement and deploy it.
- Check your webhook deliveries and your payouts for activity you do not recognise.
- Payout destinations are safe from a leaked key on their own: adding or changing one is a dashboard action gated behind your password and 2FA, and is not reachable with an API key at all.