Authentication
Every request carries an API key in the X-API-Key header. There are no sessions, no OAuth and no request signing.
Send your key
Put the whole key — everything, including the part after the dot — in X-API-Key. The examples on these pages read the base URL from VERTEXBRIDGE_API_BASE: set it to the base URL for your key's environment (see Live and test).
curl $VERTEXBRIDGE_API_BASE/v1/partner/me \
-H "X-API-Key: $VERTEXBRIDGE_API_KEY"A key sent as Authorization: Bearer … is not read at all, and the request fails with 401 unauthenticated and the message api key required. Keys have a header of their own so that they can never be confused with any other kind of credential.
Anatomy of a key
A key has two parts, joined by a dot:
vb_test_abc123.REPLACE-WITH-YOUR-OWN-KEY- The prefix, before the dot:
vb_live_orvb_test_followed by six characters. It identifies the key and is safe to show, and it is how we refer to a key. - The secret, after the dot. We store only a one-way hash of it. It is shown to you once, when the key is issued, and nobody at VertexBridge can show it to you again — there is no reveal, and no support exception. A lost key is replaced, not recovered.
Keys are issued to your team during onboarding, each with the scopes you ask for. To rotate one, ask us for a new key, deploy it, then ask us to revoke the old one; both work until the old one is revoked, so rotation needs no coordinated cut-over.
Live and test
| Key prefix | Send it to | Moves real money |
|---|---|---|
vb_live_ | https://api.vertexbridge.io — not serving yet | Yes |
vb_test_ | https://sandbox.vertexbridge.io — not serving yet | No |
Every VertexBridge API server serves exactly one environment, and accepts only that environment's keys. A vb_test_ key presented where live keys are served, or a vb_live_ key where test keys are, is refused with 401 unauthenticated and the message invalid api key — so a test key can never move real money.
That is deliberately the same answer an unknown key gets. If a test server told you a live key was real but in the wrong place, anyone holding a stolen key could test it there for free. So when a key you know is valid is refused, check first that it is the right key for the server you are calling. Keep the two keys in separate configuration, so that a deploy can never pair a live key with test code or the other way round.
Before activation
A new account starts as pending and is activated once we have completed our checks on your business. While it is pending, its vb_test_ keys work normally, so you can build and test your integration straight away. Its vb_live_ keys are refused with 403 permission_denied until the account is active. /v1/partner/me tells you which state you are in.
Before your first live key
A live key is issued only once you have signed our API agreement. Test keys need no agreement, so you can build and test your integration before you sign. The order is: test keys, then the agreement, then live keys.
If we publish a new version of the agreement, you sign it again before any further live key is issued. Live keys you already hold keep working in the meantime.
Signing is done by a person, in the dashboard. Your own systems can check where you stand with GET /v1/partner/agreement — before a deploy that expects to issue or rotate a live key, for example. It needs only the read scope.
{
"signed": true,
"current_version": "2026-11-v3",
"signed_at": "2026-11-06T09:14:02Z"
}signedis about the version currently in force, not whether you have ever signed. When we publish an amendment it goes back tofalseuntil you sign again — and live keys you already hold keep working throughout.current_versionnames that version;signed_atis when you signed it, and isnullwhile you have not.- The agreement's text is not in the response. Read it in the dashboard.
Scopes
A key carries only the scopes it was issued with, so a key that reads your balance cannot move money just because both belong to you. An endpoint that needs a scope your key lacks answers 403 permission_denied.
| Scope | Allows |
|---|---|
read | Reading your account, balance and webhook deliveries. |
purchase | Bills and VTU purchases. |
payout | Transfers out to a bank account. |
crypto | Buying and selling crypto. |
identity | Identity verification. |
accounts | Creating virtual accounts for your customers. |
Each scope covers one kind of action, and they are deliberately narrow. accounts is separate from purchase, for example: the part of your system that creates a customer's virtual account and the part that buys airtime are usually different services, and a leaked key for one should not be able to do the other. Ask for the scopes each service needs, and give each service its own key.
These are enforced, not advisory. A key without payout cannot send a payout, or even look up who owns an account, whatever else it carries — it gets 403 permission_denied. The endpoints that exist today, and the scope each one requires:
| Endpoint | Scope required |
|---|---|
GET /v1/partner/me | Any valid key |
GET /v1/partner/balance | read |
GET /v1/partner/catalogue/{product} | read |
GET /v1/partner/catalogue/{product}/{biller_code} | read |
GET /v1/partner/agreement | read |
POST /v1/partner/purchases/airtime | purchase |
POST /v1/partner/purchases/data | purchase |
POST /v1/partner/purchases/electricity | purchase |
POST /v1/partner/purchases/cable | purchase |
POST /v1/partner/purchases/exam_pin | purchase |
GET /v1/partner/purchases/{reference} | purchase |
GET /v1/partner/deliveries | read |
GET /v1/partner/transactions | read |
POST /v1/partner/deliveries/{delivery_id}/replay | read |
GET /v1/partner/webhook-signing | read |
POST /v1/partner/payouts/enquiry | payout |
POST /v1/partner/payouts | payout |
GET /v1/partner/payouts/{reference} | payout |
GET /v1/partner/payouts/banks | payout |
Check a key
GET /v1/partner/me moves no money and needs no scope, so it is the call to make first — after a deploy, after a rotation, or when something fails and you want to rule the key out:
{
"partner": "your-platform",
"name": "Your Platform Ltd",
"status": "active",
"allowed_products": [],
"scopes": ["read"],
"rate_limit_per_minute": 60
}allowed_productslists the products enabled on your account. A product call fails with403until it is listed here.scopesare this key's scopes — not your account's. Two of your keys can answer differently.rate_limit_per_minuteis your account's ceiling. See rate limits.
When authentication fails
| Status | Code | Message | Means |
|---|---|---|---|
| 401 | unauthenticated | api key required | No X-API-Key header reached us. |
| 401 | unauthenticated | invalid api key | The key is unknown, revoked, expired or malformed — or it is a valid key for the other environment. |
| 403 | permission_denied | this key does not carry the read scope | The key is valid; this endpoint needs a scope it lacks. |
| 403 | permission_denied | partner account is suspended; contact your account manager | The key is valid but your account is suspended or closed. The word after is is the account's state: suspended or closed. Every key is refused, live and test. |
| 403 | permission_denied | partner account is pending activation; test keys only until it is active | A vb_live_ key on an account that is not yet activated. Its test keys work — see before activation. |
{
"code": "unauthenticated",
"message": "invalid api key",
"correlation_id": "7ebfa718430843ae94bd61626d2a07be",
"details": {},
"retry_after_seconds": null
}invalid api key is deliberately the same message for every way a key can be wrong. Telling an unknown key from a revoked one would let anyone holding a guessed key learn which of our partners exist. Check, in order: the header name, that the whole key was sent including the part after the dot, and that it is the key for this host.
Rate limits
Each account has a requests-per-minute ceiling, shared by all of its keys and returned by /v1/partner/me. Over it, a request is answered 429 rate_limited with a Retry-After header and the same number in retry_after_seconds. Wait that long, then retry. The limit is set per account — tell us if yours is too low for your traffic.
Keeping keys safe
- Call the API from your servers. A key in a browser, a mobile app or a public repository is a key someone else has.
- Load keys from a secret store or environment variable, never from source code.
- Never log the whole key. Log the prefix — it identifies the key, and it is all we will ever ask you for.
- If a key may have leaked, tell us and have it revoked. Revocation takes effect on the next request.
Next: Errors — the one error shape, and every code you can receive.