Partner API.
One key, both boards. Everything below runs through exactly the same code a CAPX customer's own order runs through, which is why the refusals are worth reading: they are not partner-specific rules, they are the rules.
Authentication
Send your key as a bearer token on every request. Base URL https://www.capx.broker/api/partner.
Authorization: Bearer cpx_live_…
Keys come in two modes and the mode is in the key itself, so a live key pasted into a test config is obvious on sight. A cpx_test_ key reads everything and creates customers; it cannot move money. Only the hash of a key is stored, so a lost key is rotated, never recovered.
Retries
Every endpoint that moves money accepts Idempotency-Key. The first answer is kept and replayed to any repeat of the same key, with Idempotent-Replay: true on the response. Use a key per intent, not per attempt — that is what makes a dropped connection cost you a retry instead of your customer a second order.
Errors
Failures return { ok: false, code, error }. The code is stable and safe to branch on; the error is written for a person and may change. Several are worth handling by name: kyc_none and kyc_pending (the customer cannot buy yet), insufficient_balance, insufficient_inventory, market_halted, trading_paused, test_mode.
Webhooks
Set a URL on your dashboard and we post customer.created and order.settled to it. Each delivery carries capx-signature: sha256=…, an HMAC of the raw body with your signing secret. Verify it against the raw bytes before parsing — the URL alone is not authentication, and anyone who learns it can post to it.
Markets
What your customers can buy. Both boards come back in one list, each labelled with the currency it actually trades in.
/v1/markets/:symbol/history— Price history for charting, on either board.days- optional, up to 1825. Defaults to 365.
Returns. candles[] of { t, open, high, low, close } with t in seconds, plus venue, currency and source. The same series CAPX draws its own charts from, so a partner's chart and ours cannot disagree.
The two boards differ and the response says so rather than flattening them. A DSE series is in shillings, one point per session the exchange printed. A US series is in dollars from the Chainlink feed's own rounds, and is total-return — the price already carries the token's multiplier, so a split or dividend leaves no step in the line. Read `source`: "dse" is live from the exchange, while "saved" or "oracle" means the exchange was unreachable and this is the last good series or the oracle's own record.
/v1/markets— Every tradeable security, Dar es Salaam and US.Returns. symbol, name, venue, currency (TZS or USD), price, logo, tradable. US entries also carry onchain_supply — a token nobody has minted cannot be routed to, whatever its price feed says.
Backing and attestation
What stands behind a security, and whether everything CAPX owes is actually held. The same checks the public proof page runs and the same one that pauses trading when it fails — not a separate reassurance computed for partners, which would be worth nothing to the people asking.
/v1/markets/:symbol— One security, with the evidence behind it.Returns. The security with its token address, decimals and price, `available_to_trade`, and a `backing` block.
The two boards are backed differently and the response says which. A DSE security is a token CAPX issued against shares a licensed broker holds and attests to — you get the custodian, what they confirm holding, how much is earmarked against tokens, the ratio, and when the attestation expires. Evidence goes stale: read `fresh` and `expires_at`, because a statement from six months ago does not say the shares are there today. A US equity is a Coinbase-issued B20 token CAPX buys and holds, so CAPX attests to nothing — the backing CAPX answers for is its own treasury balance against what its customers are owed, and both are on Base for you to check. Size orders against `available_to_trade`, never `issued` or `onchain_supply`: that is custody less what customers are already owed, less anything an open quote has reserved.
/v1/attestation— Whether everything CAPX owes is held, right now.Returns. status covered | shortfall | unavailable, per-security held against owed in shares, dollar totals, and where the cash sits.
Shares are compared against shares, per security, because that is the only unit in which the question has an answer — a price move cannot leave a position unbacked, only a missing share can. `unavailable` means the check could not be run: treat it as unknown, not as a pass or a fail. When it reports a shortfall CAPX pauses buying on its own, so you do not need to gate on it, though you may want to show it.
Customers
Your customers become ordinary CAPX accounts carrying your id for them. You authenticate them; CAPX never issues them a password.
/v1/users— Create a customer.email- required. Must not already exist on CAPX — an email in use is refused, never adopted.
reference- required. Your own id for this person. Unique within your account.
name- optional
phone- optional
country- optional, ISO-2, defaults to yours
Returns. The customer, including their CAPX id and balances.
Idempotent. Calling again with the same reference returns the existing customer rather than a second one.
/v1/users/link— Connect to somebody who already has a CAPX account.email- required. Their CAPX email.
reference- required. Your own id for them.
action- "request" (default), then "confirm" with their code.
code- on confirm — the six digits CAPX emailed them.
Returns. 202 pending_consent, then the customer once confirmed.
Creating a customer refuses an email already on CAPX, because adopting an account because you named its address would hand you somebody else's balance. This is the consented route: CAPX emails the account holder a code and the link exists once they type it back into your app. The code goes to them, not through you. The same answer comes back whether or not an account exists, so this cannot be used to discover who banks with CAPX. A link grants access and does not move the customer to you — they keep their own account, can connect other apps, and can disconnect you from their CAPX settings without asking.
/v1/users/link— Your active links./v1/users/link— Hand a customer back. Either side can end a link.link_id- required
/v1/users— Your customers, newest first./v1/users/:ref— One customer, by your reference or their CAPX id./v1/users/:ref/positions— What they hold, priced on each board in its own currency.Returns. cash { TZS, USDC } and positions[] with qty, price and value. Nothing is converted into a single currency — a customer who bought in shillings never saw a dollar figure.
Verification
Buying is gated on the customer being verified. By default you file and CAPX reviews, and nothing you send sets the status. Where CAPX has examined your own onboarding and granted reliance, your attestation verifies the customer immediately — ask us about it. Either way CAPX records who attested and when, and can revoke any single customer.
/v1/users/:ref/kyc— File a customer's documents for review.full_name- required
document_type- required — nida, passport, licence
document_number- required
document_image- required. base64, a data: URL is fine.
selfie_image- required. base64.
Returns. Without reliance: 202 and kyc_status pending. With reliance: 200, kyc_status approved, and the customer can buy immediately. The response carries `source`, and `already_verified_by_capx` when CAPX had verified them before you — your attestation is recorded but does not replace theirs.
Images are required when CAPX reviews, because a review needs something to look at. Under reliance they are optional — but send them if you can: the day a regulator asks CAPX to evidence an identification, holding the document is the difference between answering and asking you. This works for linked customers too: somebody who already had a CAPX account but never completed verification is verified by your attestation like anyone else. Two things it will not do to an account that predates you — it never rewrites an approval CAPX itself made, and it never clears a document number already on file that your filing did not mention.
Fees
Everything CAPX charges, and what it would charge for a specific order. Both read the same constants the trades are priced with, so neither can drift from what is actually taken.
/v1/quote— What an order would cost, for a confirm screen.security- required
side- buy or sell
amount- required
currency- TZS or USDC — US board only
Returns. price, qty, amount, fee, fee_currency, fee_bps, fee_percent and net. On a buy, `amount` is what leaves the balance and `net` is what is actually invested; on a sell, `amount` is what arrives.
Priced through the same functions that run the trade, so a preview cannot disagree with the fill. Indicative all the same: quote immediately before confirming. Prices move, and a shilling order on the US board converts at whatever rate the swap reports when it runs.
/v1/fees— The rate card, including your own share.Returns. Trading bps with how it is charged on each board, the broker/CAPX/your split, and what is charged on conversion, deposits and withdrawals — which is nothing from CAPX in all three.
Your share comes out of CAPX's slice, not on top of the customer's fee. Conversion and payout costs are named rather than omitted: CAPX adds nothing, but the nTZS swap carries a spread and the payout rail charges per payout, and a partner who sees no line for them will tell their customer there is no cost.
Orders
One endpoint, both boards. The security decides the venue; a DSE symbol prices in shillings, a US ticker in dollars.
/v1/orders— Buy or sell for a customer.user- required. Your reference, or their CAPX id.
security- required. CRDB, NMB … or AAPLc, NVDAc …
side- buy or sell
amount- shillings on a buy; shares on a sell
currency- TZS or USDC. US board only; ignored on the DSE.
Returns. The settled order with qty, price, amount, currency, venue, fee and fee_currency. The fee is what was actually taken — on the US board it is in dollars even when the order was paid for in shillings, which is why the currency is stated rather than assumed.
Send an Idempotency-Key. These go through the same code a CAPX customer's own order does, so every refusal applies: unverified accounts cannot buy, halted markets stay halted, and inventory that is not held is not sold.
/v1/orders— Your customers' orders, newest first.Transfers
Moving shares between CAPX accounts. Nothing is bought or sold and nothing leaves custody — the same shares stay with the same custodian and the claim on them moves, which is why both boards work identically here.
/v1/transfers— Send shares from one of your customers to another CAPX account.from- required. Your reference or their CAPX id. Must be your customer.
to- required. Your reference for one of yours, or any CAPX email or @handle.
security- required. Either board.
qty- required. Shares, not cash.
note- optional, shown to the recipient.
Returns. The transfer, with `inherited_cost` and `cost_currency`.
The sender must be your customer; the recipient need not be, because people send things to people rather than to customers of the same app. Both must be verified — a transfer is the one way value reaches an account without passing a deposit or a trade, so an unverified recipient would be an unchecked way in, and nothing is held pending somebody finishing signup. The recipient inherits the sender's average cost rather than the day's price: a gift is not a disposal, so the sender realises nothing and the recipient's P&L continues from where the sender's left off. Send an Idempotency-Key.
Dividends and corporate actions
Neither board pays cash. A tokenised equity reflects a dividend by raising its multiplier, so the holder's share count grows instead. CAPX reads that and credits whoever held the claim.
/v1/users/:ref/dividends— Corporate actions credited to one customer.Returns. security, shares_credited, multiplier, created_at.
Show your customer the shares. A 'dividend received' figure in shillings would be a number they cannot act on and did not receive.
Currencies
US equities trade in dollars and a USDC balance buys them directly — no conversion, no extra call. The Dar es Salaam board prices and settles in shillings and reads a shilling balance, so a customer holding only dollars must convert before they can buy CRDB.
/v1/convert— Turn a customer's dollars into shillings.user- required
amount_usdc- or
all- true to convert the whole balance
Returns. The dollars spent and the shillings credited, at the rate the swap actually reported.
One direction only. Shillings become dollars inside a US buy at the moment it needs them; offering that conversion on its own would make CAPX a currency desk.
Settlement
Where to send stablecoin, and in what. Read it rather than hardcoding the address — it is served live from the omnibus account, and a copy in your config is the one field where being out of date means sending money somewhere CAPX is not watching.
/v1/settlement— The CAPX omnibus address on Base, and the tokens it accepts.Returns. chain, omnibus_address, tokens[] with contract addresses and decimals, and the steps to credit a customer.
Send only NTZS or USDC on Base. Anything else is not credited and cannot be returned automatically. omnibus_address is null if it could not be read — try again rather than falling back to a cached value.
Money in and out
Three ways in: a mobile money collection, a bank push, or a stablecoin transfer you have already made to the omnibus wallet. All of them land in the same omnibus as every other customer's money. There is no endpoint that credits a balance directly, and there will not be — a balance that can be written without money arriving is not a balance.
/v1/deposits— Funding history, optionally filtered by ?user=./v1/deposits— Credit a customer from a stablecoin transfer you have already sent.user- required
tx_hash- required. The Base transaction that moved the tokens to the CAPX omnibus wallet.
token- NTZS (default) or USDC
buy_on_arrival- optional { security, side?, amount?, currency? } — the order to place the moment the deposit is credited. Omit amount to spend the whole deposit.
Returns. 202 with status pending while the block is not yet safe, then 201 / status settled once it is. The amount is read from the transaction itself.
For partners whose customers already hold nTZS — no collection, no prompt. The amount credited is whatever the Transfer log actually carries, never the figure in your request, and the recipient must be the omnibus wallet. Nothing is credited on your word; this is read from the chain.
— buy_on_arrival— Hand over the transfer and the trade in one call.If the block is already safe the order executes inline and the response carries it. Otherwise it is held and runs the moment the deposit is credited, and you are told over the webhook — there is no response left to report into by then. Two things to pass on to your customer: the fill is at the price when it executes, not when you submitted, which on a volatile US name is a real difference over the minute it waits; and an order that fails never costs the deposit — the money is credited and stays as their cash, with the reason on the deposit and an order.failed webhook.
— on waiting for safety— Base is an L2, so mined is not final.A transfer is recorded on arrival and credited once the block containing it has been batched to Ethereum — the `safe` tag, about a minute behind the head. Counting confirmations would be the weaker test, since those are built by the same sequencer a reorg would replace. While it waits the deposit reads status pending and the balance is not yet spendable. The hash is claimed from the moment you submit it, so there is nothing to resend and resubmitting returns the same deposit rather than a second one.
/v1/deposits— Or collect from their mobile money, or get bank details for them to push to.user- required
amount_tzs- required
phone- mobile money number to prompt; defaults to the customer's
method- mobile_money (default) or bank_transfer
payer_account_number- required for bank_transfer — this is how the money is matched back to them
Returns. 202 with the deposit, its route and status. A bank transfer also returns `bank`: the account, the one-off reference and when it expires.
Mobile money prompts the phone and settles on its own. A bank transfer takes nothing until the customer sends it — show them the reference verbatim, because that is what matches the money to them.
/v1/withdrawals— Pay a customer out to mobile money or a bank.user- required
amount_tzs- required
phone- mobile money
bank_code + account_number- bank payout
Returns. status: sent or queued, with amount_tzs and fee_tzs. The fee is charged by the payout rail and quoted per payout, so it is whatever the rail reported rather than a rate CAPX applies — null when it did not state one.
Read the status. A queued payout is accepted and goes out automatically, oldest first — showing your customer 'sent' when the body says queued would be misreporting it.
Questions, or ready for a live key? Apply, or open your dashboard.
