Visa & Mastercard Card Issuing API Reference
Issue USD-denominated Visa & Mastercard cards under your brand. Onboard cardholders with KYC/KYB, issue virtual & physical cards, fund the credit line with USDC, manage PINs & limits, and receive HMAC-signed webhooks on every event.
Live https://visacard.codegotech.com/api/v1
This reference covers the API integration for the Visa/Mastercard card issuing programme. If your programme runs in "Core banking Included" mode (a fully managed, branded cardholder portal that Codego hosts for you) there is nothing to integrate here — your cardholders use the portal directly and you manage everything from your whitelabel console.
Quickstart — Sandbox onboarding
Self-serve sandbox access in three steps. You'll get a whitelabel id and a sandbox API key (vcck_sbx_…) by email within minutes. Sandbox runs on a fully isolated database — your test calls never touch the live ledger.
1 · Sign up — get your sandbox API key
Open apikey-visacard-sandbox.codegotech.com and submit the short signup form (name, email, phone, company, website). Within ~30 seconds you'll receive an email containing:
- The sandbox dashboard URL at vcc-whitelabel-sandbox.codegotech.com
- Login email + password
- Your whitelabel id
- Your API key in the form
vcck_sbx_…— shown only once, store it safely - A ready-to-paste
curlsnippet + link to this reference
2 · Open a KYC session for your first cardholder
Every cardholder onboarding starts with a KYC session. You call it from your backend, pass your own externalUserId, and get back a one-time iframeUrl. You drop that URL into an iframe in your app — the cardholder uses it to capture their ID, take a selfie, verify their address. Documents never leave Codego. When the cardholder finishes, you receive a webhook with the KYC outcome and the resolved profile.
Expected response (201 Created):
Note — no userId here, and that's correct. At session-create time the cardholder hasn't been verified yet, so no user exists. The userId you use for every later call (GET /users/{userId}, POST /users/{userId}/cards, …) is delivered in the user.updated webhook once KYC resolves — see KYC outcomes below. Keep your own externalUserId as the correlation key until then.
Sandbox shortcut: every individual KYC outcome in sandbox is auto-approved — the iframe accepts any sample image and resolves the session in seconds. Real KYC review (production) can take 1–2 business days.
Then embed the iframe in your app and listen for completion:
Integration flow
A complete integration follows the seven sequential steps below — the same order as the left navigation. The endpoints in the left navigation are grouped accordingly. Cardholder onboarding is iframe-only by design: your end-user captures their ID and selfie directly inside the Codego KYC iframe — you never receive, store or forward identity documents, which keeps you out of the regulated data-processor scope. You receive only the final structured result via webhook.
Two tracks: individual and business
Only the onboarding differs. Once a cardholder exists — whether they are a private person or an authorised user of a company — funding, card issuance, card management, transactions and webhooks are the same endpoints. Cards for a company's users are issued with the very same POST /users/{userId}/cards, spending against the company collateral instead of a personal one.
| Track | Onboarding | Availability |
|---|---|---|
| Individual | Generate the DeFi wallet, then run the KYC iframe with applicantType: "individual". You get a userId in the user.updated webhook. |
Sandbox and live |
| Business (KYB) | Same iframe with applicantType: "company", which also collects legal entity data, UBOs and incorporation documents. You then read and manage the company through the /companies endpoints and create its authorised users with POST /companies/{companyId}/users. |
Sandbox and live |
| Shared by both | Collateral & funding, card issuance, card management (limits, freeze, PAN/CVC/PIN), transactions & disputes, webhooks. | Sandbox and live |
Step by step
| step | What happens | Endpoints involved |
|---|---|---|
| 1 · Generate the DeFi wallet | Before onboarding, generate the cardholder's self-custody DeFi wallet. The response returns the wallet address plus the mnemonic and privateKey once and only once — they are never stored on our side, so hand them to your end-user or persist them securely yourself. Send your own externalRef (and optionally a label) in the request — we store them against the address, so a wallet is never anonymous. You pass the resulting walletAddress into the KYC/onboarding call in the next step, which is what binds the wallet to the cardholder. See Which wallet belongs to which cardholder. |
POST /wallets · GET /wallets/{address}/balance · GET /wallets/{address}/deposits |
| 2 · Onboard (individual) | Your backend opens a KYC session with your own externalUserId and gets a one-time iframeUrl. For an individual the iframe captures ID + selfie + address proof; for a company (KYB) the same iframe also collects legal entity info, UBOs and incorporation documents. Codego runs identity review and creates the on-chain collateral contract on approval. You receive the outcome via the user.updated (individual) or company.updated (KYB) webhook. |
POST /api/session/create { applicantType: individual|company } · <iframe> embed · GET /applications/{userId} or GET /applications/companies/{companyId} |
| 3 · Fund | Fund the card's USDC collateral. Direct — send USDC/USDT on Base or Arbitrum straight to the collateral contract (instant). Multi-coin top-up — list the supported coins, get the current deposit address for a convert network (direct networks use the collateral contract), and Codego auto-converts to USDC and credits the card in a few minutes. Monitor status + balance. See Fund the card. | GET /users/{userId}/contracts · GET /users/{userId}/balances · GET /users/{userId}/topup/assets · POST /users/{userId}/topup/address · GET /users/{userId}/topup/orders |
| 4 · Issue | Issue a virtual or physical Visa card to an approved cardholder, with per-frequency spending limits. | POST /users/{userId}/cards · GET /cards · GET /cards/{cardId} |
| 5 · Manage | Lock / unlock / cancel cards; adjust limits; render the PAN, CVC or PIN under the encrypted-secrets flow. | PATCH /cards/{cardId} · GET /cards/{cardId}/secrets · GET /cards/{cardId}/pin |
| 6 · Transactions | List and inspect authorisations, captures and settled spend. Open a dispute if needed. | GET /transactions · GET /transactions/{txId} · POST /transactions/{txId}/disputes |
| 7 · Webhooks | Receive real-time notifications when a cardholder's verification status changes and for every transaction of your cardholders and companies. See the Webhooks section for the exact event list and payloads — card, top-up order and dispute state is retrieved by polling their endpoints. | Configured in the partner dashboard. HMAC-signed deliveries. |
Which wallet belongs to which cardholder
A wallet is self-custodial: at the moment you create it the cardholder does not exist yet, so there is no userId to attach it to. The relation is established by you, at two points.
| When | What carries the relation |
|---|---|
| At wallet creation | POST /wallets accepts externalRef — your own user identifier, with externalUserId accepted as an alias — and label. Both are persisted next to the address on our side, so you can always tell which of your users a given address was generated for. |
| At onboarding | You pass that address into the KYC session / application as walletAddress. From that moment the address is bound to the cardholder record and travels with it: GET /applications/{userId} and the user.updated webhook refer to the same cardholder. |
mnemonic and privateKey are returned once and only once and are never stored on our side. If you pre-generate a pool of wallets and try to attribute them afterwards, the mapping exists nowhere else and cannot be recovered. The safe order for each cardholder is: 1) POST /wallets with externalRef set to your user id, 2) persist or surface the secrets, 3) onboard that cardholder with the returned walletAddress. That way there is never an unassigned wallet.Authentication
Every request is authenticated with your whitelabel API key in the X-Api-Key header. Keys look like vcck_sbx_… (sandbox) or vcck_live_… (production) and are issued at onboarding. Keep the key server-side — never expose it in client code.
Each key is scoped to one whitelabel tenant; all resources you create or read are isolated to it. Sandbox and live are fully separated. To rotate a key, contact [email protected].
POST /api/session/create). Every userId you pass to POST /users/{userId}/cards, GET /users/{userId}, /applications/{userId}, etc. must belong to your tenant. Operating on a card or user that belongs to a different key returns 404 (card / user not found), and GET /cards & GET /transactions only ever return resources owned by the calling key. Mixing keys across KYC and card calls is the most common cause of an unexpectedly empty list or a 404.IP allowlist (optional). Your key can be restricted to a set of source IPs.
While the list is empty the key works from anywhere; once you register one or more addresses
(single IPv4 or CIDR, e.g. 203.0.113.10 or 203.0.113.0/24) only those
addresses may use that key, and a request from any other address is refused with
403 — IP not authorised for this API key. The restriction is bound to the key, not
to the network: an address authorised for another integrator cannot be used with your key, and
yours cannot be used with theirs. To register or change your addresses, contact
[email protected].
GET /transactions is additionally limited to 30 requests / minute. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix timestamp in seconds at which the minute window resets). A 429 response carries Retry-After (seconds) — wait at least that long before retrying. For transaction updates, subscribe to the transaction.* webhooks rather than polling the list.SessionId header, and must never be stored.Environments — sandbox & live
| environment | Base URL | Key prefix | Notes |
|---|---|---|---|
| Sandbox | https://vcc-sandbox.codegotech.com/api/v1 | vcck_sbx_… | Test cards, simulated balances. Individual KYC is approved automatically on submission — no special names or sample values are needed. |
| Live | https://visacard.codegotech.com/api/v1 | vcck_live_… | Real Visa rails, real cards, real KYC. Authorisation required — contact [email protected] to enable. |
Both environments share the same endpoint paths, request and response shapes — the same code works in both. Switch hosts, switch keys, and you're done. Webhooks are configured separately per environment.
vcck_live_… key. To activate: 1) switch the base URL to https://visacard.codegotech.com/api/v1, 2) swap the sandbox key for your live key, 3) send us the server IP(s) that will call the live API — live access is IP-allowlisted (default-deny), so calls from non-allowlisted IPs are rejected, 4) reconfigure your webhook URL for the live environment.Fund the card — deposits & multi-coin top-up
A card spends against its USDC collateral. You fund it in one of two ways. Direct: send USDC (or USDT) on Base or Arbitrum straight to the cardholder's collateral contract — instant, no fee. Multi-coin top-up: let the cardholder deposit any supported coin on any supported network (e.g. USDT-TRON, ETH, USDC on Ethereum) to the deposit address we return — Codego auto-converts it to USDC and credits the card in a few minutes, minus a small top-up fee. No amount needs to be declared up front: the cardholder simply sends to the address and the deposit is detected and credited automatically.
GET /users/{userId}/topup/assets. Build your UI from that response and any coin we add later appears automatically — nothing to redeploy on your side.403 (top-up not enabled) and you fund cards with the Direct method only. To enable it, email [email protected]. userId is the cardholder id delivered in the user.updated webhook and must belong to your API key's tenant.Direct funding (instant)
Fetch the collateral contract for the chain you want and send USDC/USDT to that address on that network. The card balance updates when the deposit confirms on-chain (a transaction webhook is delivered). Supported direct networks: Base (chainId 8453) and Arbitrum One (chainId 42161).
GET /users/{userId}/contracts
GET /users/{userId}/balances
1 · List supported coins & networks
Returns every coin/network the cardholder can deposit, with per-network mode, speed, fee and live availability. Render your deposit UI directly from this.
GET /users/{userId}/topup/assets
// 200 OK
{
"assets": [
{
"coin": "USDC",
"name": "USD Coin",
"logoUrl": "https://.../usdc.png",
"networks": [
{ "network": "arbitrum", "label": "Arbitrum One", "mode": "direct",
"speed": "instant", "feePct": 0, "feeFixedUsd": 0, "feeNetworkUsd": 0,
"available": true, "reason": null },
{ "network": "ethereum", "label": "Ethereum (ERC-20)", "mode": "convert",
"speed": "few_minutes", "feePct": 0.5, "feeFixedUsd": 0, "feeNetworkUsd": 3,
"available": true, "reason": null }
]
},
{ "coin": "USDT", "name": "Tether", "logoUrl": "...", "networks": [ /* … */ ] },
{ "coin": "ETH", "name": "Ethereum", "logoUrl": "...", "networks": [ /* … */ ] }
]
}
| Field | Meaning |
|---|---|
| mode | direct = send to the collateral contract (instant, no fee). convert = send to the top-up address; auto-converted to USDC. |
| speed | instant or few_minutes — show it as the ETA badge. |
| feePct | Top-up fee percentage applied to the deposit value (0 on direct networks). |
| feeNetworkUsd | Fixed network/swap fee in USD added on top of the percentage (e.g. 3 for USDC/USDT on Ethereum). 0 on direct networks. |
| available / reason | When available:false, grey the network out and show reason (e.g. network_temporarily_unavailable). |
2 · Get the deposit address
Returns the current deposit address for that coin/network. The address is reusable, but always call this endpoint to fetch it right before you show it — do not hardcode or cache it long-term, as it can change if the coin’s routing changes. For a direct network there is no deposit address: the response tells you to use the cardholder’s collateral contract instead.
POST /users/{userId}/topup/address
{ "coin": "USDT", "network": "tron" }
// 200 OK (convert network)
{ "mode": "convert", "coin": "USDT", "network": "tron",
"depositAddress": "T…", "persistent": true }
// 200 OK (direct network → use the collateral contract)
{ "mode": "direct", "coin": "USDC", "network": "arbitrum", "useExistingDeposit": true }Maintenance / disabled networks. A coin or network can be temporarily switched off. When it is, GET …/topup/assets returns that network with "available": false, "reason": "maintenance" and a human-readable "reason_message" you can show to the user, and POST …/topup/address responds 400 with { "error": "maintenance", "message": "…" }. Other networks stay available — build your UI from the live topup/assets response.
// GET …/topup/assets — a network under maintenance
{ "network": "tron", "label": "Tron (TRC-20)", "mode": "convert",
"available": false, "reason": "maintenance",
"reason_message": "USDT deposits on the Tron network are temporarily unavailable for maintenance." }
// POST …/topup/address on a network under maintenance → 400
{ "error": "maintenance",
"message": "USDT deposits on the Tron network are temporarily unavailable for maintenance." }
3 · (Optional) Quote the net amount
Preview how much USDC a deposit would credit. Optional — the flow works without it (the deposit is credited automatically). For volatile coins the amount is an estimate (the real credit is the amount realised from the conversion).
POST /users/{userId}/topup/quote
{ "coin": "USDC", "network": "ethereum", "amount": 100 }
// 200 OK
{ "coin": "USDC", "network": "ethereum",
"grossAmount": 100, "feePct": 0.5, "feeNetworkUsd": 3,
"feeAmount": 3.5, "netUsdc": 96.5,
"speed": "few_minutes", "needsPrice": false }
// volatile coins (e.g. ETH) also return: "priceUsd", "grossUsd", "estimate": true
4 · Track the deposit
Poll the cardholder's deposits and their live status. Statuses are neutral (no internal mechanics exposed). The card balance also updates via GET /users/{userId}/balances once credited.
GET /users/{userId}/topup/orders?limit=20
// 200 OK
{
"orders": [
{
"orderRef": "top_…",
"coin": "USDT", "network": "tron",
"amountIn": 50, "creditedUsdc": 46.75,
"feePctUsd": 0.25, "feeGasUsd": 0, "feeTotalUsd": 3.25,
"status": "completed", // processing | completed | below_minimum | failed | expired
"progressLabel": "Completed", // Detected | Processing | Almost done | Finalizing | Completed
"etaSec": 0, "step": 4, "steps": 4,
"detectedAt": "…", "creditedAt": "…", "createdAt": "…"
}
]
}
// Single deposit: GET /users/{userId}/topup/orders/{orderRef}
KYC SDK — drop-in iframe
If you don't want to build the document-capture UI yourself, embed the Codego KYC iframe in your app. The wizard handles ID capture, liveness, address proof, UBOs (for KYB) and (for selfies) anti-AI flash + zoom challenges — entirely on Codego infrastructure. Documents never touch your servers.
Two steps: (1) POST to https://kyc-sandbox.codegotech.com/api/session/create from your backend with your KYC sandbox key → you get an iframeUrl. (2) Drop the URL into an <iframe allow="camera"> in your app and listen for postMessage on completion. The session forwards the result to the card issuing API automatically.
KYC outcomes — the user.updated webhook
After the cardholder submits, Codego runs identity review and notifies you with a user.updated webhook. This webhook is where you receive the userId (field body.id) used for all later /users/{userId}/* calls, and it echoes back your externalUserId so you can match it to your own user record. The body.applicationStatus field carries the outcome. Handle these values:
applicationStatus | Meaning & what to do |
|---|---|
| approved | Identity verified. The on-chain collateral contract is created — you can now issue a card. |
| needsVerification | Transient — review still in progress. No action needed; a follow-up webhook will carry the final outcome. |
| manualReview / pending | Under human review (production can take 1–2 business days). No action needed. |
| needsInformation | Action required. The cardholder must fix one specific thing — the reason is in body.applicationReason (e.g. BAD_SELFIE). Re-open the verification (see below) — the iframe will show only the step that needs correcting, not the whole flow. |
| denied | Verification could not be completed. body.applicationReason gives the reason. |
Example user.updated payload (HMAC-signed, delivered to your configured webhook URL):
Map body.id → your record keyed by body.externalUserId, then continue: GET /users/{userId}, fund the contract, and POST /users/{userId}/cards.
vcc-whitelabel-sandbox.codegotech.com → API & Webhooks → Webhook endpoint; live: vcc-whitelabel.codegotech.com. On first save the console shows your signing secret. Every delivery is a POST with header Signature: sha256=HMAC_SHA256(rawBody, secret) and an Idempotency-Key header. Verify the signature against the raw body before trusting the event, and de-dupe on the idempotency key. Sandbox fires the exact same events, in the same envelope, as live. In sandbox the decision on individual applications is made by Codego and is always approved, within seconds of submission — you get one user.updated event and can go straight on to issuing a card.// Verify a delivery (Node)
const crypto = require('crypto');
const expected = 'sha256=' + crypto.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody) // the raw request body, not re-serialised JSON
.digest('hex');
if (req.headers['signature'] !== expected) return res.status(401).end();
Application states, session states and rejection reasons
Two different state machines are involved, and they are easy to confuse. The
application state is the verification decision. The session state is the progress of the
cardholder through the KYC iframe. They move independently: a session can be submitted
while the application is still pending.
applicationStatus vs kycStatus.
The user.updated webhook carries both. In live they always hold the same value —
kycStatus is a historical duplicate of applicationStatus, kept for backwards
compatibility. Build your logic on applicationStatus and treat kycStatus as an
alias. In sandbox both are always approved.
| Application state | Meaning | What to do |
|---|---|---|
| notStarted | The application exists but nothing has been submitted yet. | Open the KYC iframe. |
| pending | Submitted and under automated review. | Wait for the next webhook. |
| manualReview | Escalated to a human reviewer. | Wait — this can take longer. |
| needsVerification | An intermediate state emitted while checks are still running. It carries no reason. | Nothing. Do not treat it as a rejection — wait for the following event, which carries the decision. |
| needsInformation | Something must be re-submitted. The cause is in applicationReason. | Resume the same session (see above) so the cardholder fixes only what was flagged. |
| approved | Verified. This is the only state in which a card can be issued. | Create the card. |
| denied | Rejected. Not recoverable by re-submitting. | Stop. Contact us if you believe it is wrong. |
| locked | Blocked for compliance reasons. | Contact us. |
| canceled | Withdrawn before completion. | Start a new application. |
| exempt | Verification not required for this user. | Proceed as approved. |
approved.
Calling POST /users/{userId}/cards for a user in any other state returns
403 — User exists, but is not approved. This is why you should key your flow on the
application state and not on the session state.| Session state | Meaning |
|---|---|
| created | Session issued, iframe not opened yet. |
| in_progress | The cardholder is filling in data or capturing documents. |
| submitted | Everything was sent and forwarded for verification. The session is closed to further edits while the decision is pending. |
| needs_action | A specific step was rejected and re-opened for that step only. |
| approved | Verification completed successfully. |
| rejected | Verification failed definitively. |
| expired | The session link timed out. Create a new session. |
A session in submitted, approved,
rejected or expired is terminal and cannot be resumed:
resumeSessionId on one of those returns
409 { "error": "not_resumable", "status": "<state>" }. Only
needs_action (and a session still in progress) can be resumed.
Reading the session state — and how it relates to the application
Webhooks are delivered once and never re-sent, so if your endpoint was unavailable the
event is gone. Read the state back whenever you need it:
GET /kyc/sessions/{sessionId}, or GET /kyc/sessions?externalUserId=… when you
did not store our sessionId. The response carries the same fields as the kyc.*
events — status and, on needs_action, the reason, the
steps to redo and the message to show the cardholder. A periodic sweep of
?status=needs_action is enough to catch anything you missed.
applicationStatus is the state of the application on the issuing side. When a
correction is requested by Codego, the session moves to needs_action while the application
stays exactly where it was — it moves only after the cardholder re-submits and Codego forwards the
correction. Do not wait for applicationStatus to become needsInformation in
that case: it never will.| Session state | applicationStatus you may see at the same time | What to do |
|---|---|---|
| created in_progress | No application yet — GET /applications/{userId} has no user id to query until the first submission. | Nothing. The cardholder has not finished the iframe. |
| submitted | pending, manualReview or needsVerification — or still no application, while the submission is being reviewed by Codego. | Wait for the next event. |
| needs_action | Most often no application yet or pending / needsVerification (correction requested by Codego). needsInformation only when the issuing partner asked for it. | Re-open the same session with resumeSessionId. The reason and the step are in the session, not in applicationReason. |
| approved | approved | Issue the card. |
| rejected | denied — or no application, when the rejection happened before the application was created. | Stop. Not recoverable by re-submitting. |
| expired | Unchanged — an expired link does not alter the application. | Create a new session. |
In short: drive the KYC flow on the session status
(kyc.* webhooks, or GET /kyc/sessions), and use
applicationStatus for one decision only — whether a card can be issued
(approved).
Matching a cardholder to your own records
Every object that concerns one of your cardholders carries your own reference,
never a Codego identifier: externalUserId in the kyc.* and
user.updated events and in GET /kyc/sessions; externalId in
GET /applications/{userId} and GET /users/{userId}. In every case it is the
value you sent to POST /api/session/create (or to POST /applications), so the
same key matches a cardholder across webhooks and API.
The field is null in one case only: a cardholder Codego created on your
behalf — for example a profile assigned from the Codego console — where no reference of yours exists.
Use userId for those.
Rejection reasons. When the application is
needsInformation or denied, applicationReason holds one or more
codes, comma-separated (for example "BAD_SELFIE, GRAPHIC_EDITOR, UNSATISFACTORY_PHOTOS").
Match on substrings rather than on the whole string, since codes are combined.
| Reason code | What went wrong | Recoverable |
|---|---|---|
| BAD_SELFIE | The selfie could not be matched to the identity document, or the liveness check failed. | Yes — retake the selfie |
| UNSATISFACTORY_PHOTOS | Images are blurred, cropped, glared or otherwise unreadable. | Yes — retake the photos |
| GRAPHIC_EDITOR | The image carries traces of photo-editing software. Screenshots and re-compressed exports often trigger this. | Yes — photograph the physical document directly |
| BAD_PROOF_OF_IDENTITY | The identity document is not accepted: unsupported type, expired, or not fully visible. | Yes — upload a valid document |
| SUSPICIOUS_DOCUMENT | The document itself is doubted — suspected alteration or forgery. This is not a photo-quality problem: re-uploading the same document will not help. | Rarely |
| PROBLEMATIC_APPLICANT_DATA | The data entered does not match the document (name, date of birth, document number). | Yes — correct the details |
| WRONG_USER_REGION | The applicant's country is not served. | No |
| REGULATIONS_VIOLATIONS | The application conflicts with regulatory requirements. | No |
| FRAUDULENT_PATTERNS | Signals associated with fraudulent applications were detected. | No |
| DUPLICATE | The same person already has an application. | No — use the existing user |
| BLOCKLIST | The applicant appears on a blocking list. | No |
| SPAM | The application was classified as spam. | No |
applicationReason comes back empty. They can still appear on business (KYB) applications in sandbox, and on both tracks in live — so handle them either way.Resuming a needsInformation session (live). Call POST /api/session/create again, passing resumeSessionId set to the original sessionId instead of starting a fresh session. You get back a new iframeUrl that re-opens the same application at exactly the step flagged in review (e.g. just the selfie/liveness). The cardholder re-submits only that document — no new application is created, no data re-entered. On success the status returns to submitted (back in review).
// On a needsInformation webhook, re-open the SAME session — fix only what's needed.
POST https://kyc-live.codegotech.com/api/session/create
{ "resumeSessionId": "<original sessionId>" }
// → { "iframeUrl": "…?resume=selfie", "resume": "selfie", "sessionId": "<same id>" }
approved — documents are accepted as submitted and are never assessed for image quality or authenticity. You will therefore never see needsInformation, needsVerification or denied on an individual application, and the resume flow above is not needed there: no selfie, however poor, will hold one back. Business (KYB) applications are different: in sandbox they are still assessed by the issuing partner, so they can legitimately return needsInformation or denied with a reason — handle those states when you integrate applicantType: "company". In live, both tracks are decided by the issuing partner.Errors
The API uses standard HTTP status codes. 2xx = success; 4xx = client error with a JSON { error, message } body; 5xx = transient — retry with backoff.
| code | Meaning |
|---|---|
| 200 / 201 | Success |
| 400 | Validation failed — see message |
| 401 | Missing or invalid API key |
| 404 | Resource not found |
| 429 | Rate limit exceeded — wait Retry-After seconds (or until X-RateLimit-Reset) before retrying |