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 US dollars into your USD account without the USDT leg, converting those dollars to shillings in your KES account, and paying them out as dollars from your USD account.

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: the KES rails and your USDT address.
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, your USDT balance, and where your USDT is held.

Collections only:

ScopeWhat it unlocks
collections:writeOpening a collection — see Open a collection.
collections:readReading your own collections back, one at a time or listed.
conversions:writeConverting USD in your USD account to KES in your KES account — see Convert USD to KES.
conversions:readQuoting a conversion, and reading a conversion back.

A read scope never implies its write counterpart, and a write scope never implies the read. Ask for both where you need both.

A Collections credential already holds transfers:write

transfers:write, transfers:read and confirmations:write — in the "Available to every partner" table above — are what pay KES out once you have some. They are not USDT-rails-only, so a Collections credential does not need a second product to send a KES bank transfer once conversions:write has turned your USD into KES. The same scopes pay USD out of your USD account, which only a Collections account can do. See Collect in the US, pay out in Kenya.

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.

A route can refuse your whole product, before any scope check

A scope refusal is not the only 403 this API can send you. POST /v1/orders/buy and every other USDT-rails route refuse a Collections credential outright — and the reverse holds for POST /v1/collections against a usdt_rails credential — independently of what scopes the credential carries:

{
  "statusCode": 403,
  "message": "This credential is not authorised for this endpoint — it is not available for your product.",
  "error": "product_not_supported",
  "requestId": "req-1756512000000-a1b2c3d"
}

error is the constant product_not_supported. Unlike insufficient_scope, this refusal names no scope and, deliberately, does not say which product the route belongs to — that would describe a different product's API surface to a credential that holds nothing for it. The fix is not a wider grant: no scope list can make a USDT route answer for a Collections account, or the reverse. This check runs even against a scope forged onto the wrong credential by some means other than normal issuance — it does not merely trust that issuance refused it upstream.

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