Products and scopes
Why an endpoint documented on this site answers 403 for your credential: every key carries an explicit list of what it may call, and there is no wildcard.
The reference documents every operation this API has. Your credential reaches
the subset your agreement covers. When those two differ you get a 403, and
this page is how to read it.
A credential carries an explicit list
Every API key is issued with a list of scopes in resource:action form. A call
to an operation whose scope is not on your list is refused — by the credential,
before the operation runs.
Three properties of that list are worth knowing before you debug one:
- There is no wildcard. No
*, noadmin, no super-scope. The list is exhaustive. - An empty list authorises nothing. It is not a shorthand for "unrestricted".
- Widening a credential means issuing a new one. A scope cannot be added to a key in place, which is also why the grant is worth getting right at issuance rather than discovering in integration.
The scopes
| Scope | What it unlocks |
|---|---|
health:read | GET /v1/health — the smallest signed call, for testing signing. |
quotes:write | Taking a quote. |
orders:write | Creating an order, and attaching a transaction hash to one. |
orders:read | Reading an order back. |
balances:read | The account you hold with us, and what is available in it. |
funding:read | Your deposit instructions and collection account. |
payout_accounts:read | The Kenyan bank accounts a payout can settle to. |
wallets:write | Registering, confirming and removing a withdrawal address. |
wallets:read | Listing your registered addresses. |
admins:write | Registering and removing an approver on your account. |
admins:read | Listing them. |
A read scope never implies its write counterpart, and a write scope never implies the read. Ask for both where you need both.
What a refusal looks like
Response — 403 Forbidden:
{
"statusCode": 403,
"message": "This credential is not authorised for this endpoint. Required scope: orders:write.",
"error": "insufficient_scope",
"requestId": "req-1756512000000-a1b2c3d",
"errorCode": "orders:write"
}error is the constant insufficient_scope. The scope the route needed is
in errorCode, and message repeats it in prose — so a refusal tells you what
to ask for rather than sending you to us to find out.
What the response does not contain is the list your credential holds. That is a description of your own privileges, and it is more than a single refusal needs to disclose.
A 403 is not a signing problem
It is a 403 and not a 401 deliberately: your signature, your key and your
clock were all correct, and the call was authenticated. Rotating a working
secret will not fix it. A 401 is the signing path — see
Authentication.
Why your grant is shaped the way it is
A grant is scoped to what your integration does. A platform that only converts
in one direction does not need the other direction's write scope, and a
credential that never registers a destination does not need wallets:write.
That is not friction for its own sake: the narrower the grant, the less a
leaked secret can do, and the destinations it could reach are registered in
advance anyway. See API credentials.
If you need a scope you were not issued, tell us what you are building and we will issue a credential that carries it. Email info@onlink.africa.