OnLink
Concepts

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.

Your credential is issued for ONE product

OnLink's partner API serves two products, and every partner is set up for exactly one:

  • USDT rails — quoting, buying and selling USDT against KES, registering withdrawal addresses, sending USDT to a registered destination.
  • Collections — collecting and converting funds without the USDT leg.

Which product you were onboarded for decides which operations you can ever be issued a scope for. A usdt:write scope is meaningless to a Collections partner and will never appear on one of your keys, and the reverse holds for a scope that belongs only to Collections. Ask your OnLink contact which product your account is set up for if you are unsure.

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.

Four properties of that list are worth knowing before you debug one:

  • There is no wildcard. No *, no admin, no super-scope. The list is exhaustive.
  • An empty list authorises nothing. It is not a shorthand for "unrestricted".
  • A scope outside your product can never be issued to you. Not by mistake, not by request — the platform refuses it at issuance.
  • 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

Some scopes are available to every partner regardless of product; others belong to one product only.

Available to every partner:

ScopeWhat it unlocks
health:readGET /v1/health — the smallest signed call, for testing signing.
balances:readThe account you hold with us, and what is available in it.
funding:readYour deposit instructions and collection account.
funding:writeRequesting an M-PESA prompt on a number you name.
payout_accounts:readThe Kenyan bank accounts a payout can settle to.
transfers:writeSending money via a bank transfer.
transfers:readReading a bank transfer back.
confirmations:writeConfirming or resending the code for a bank transfer.
admins:writeRegistering and removing an approver on your account.
admins:readListing them.

USDT rails only:

ScopeWhat it unlocks
quotes:writeTaking a quote.
orders:writeCreating an order, and attaching a transaction hash to one.
orders:readReading an order back.
wallets:writeRegistering, confirming and removing a withdrawal address.
wallets:readListing your registered addresses.
usdt:writeSending USDT to a registered wallet.
usdt:readReading a USDT send back.

Collections only:

The Collections product's own operations are documented as they come online. Your OnLink contact will confirm which scopes apply to your integration.

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.

On this page