{"openapi":"3.0.0","paths":{"/v1/health":{"get":{"operationId":"PartnerHealthController_health","parameters":[],"responses":{"200":{"description":"Service is reachable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerHealthResponseDto"}}}},"401":{"description":"Signature missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Service status","tags":["Partner API"]}},"/v1/quotes":{"post":{"description":"Returns a single-use, time-limited quote. Quotes are only issued off a live rate — a degraded rate source returns 503 rather than a locked rate we could not honour.","operationId":"PartnerQuotesController_createQuote","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateQuoteDto"}}}},"responses":{"201":{"description":"Quote locked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteResponseDto"}}}},"400":{"description":"Both or neither amount supplied, or an amount is malformed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"401":{"description":"Signature missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"422":{"description":"FEE_EXCEEDS_PROCEEDS — the fee equals or exceeds what a sell would pay out, so the trade is not viable at this size. Distinct from the 400 above on purpose: the amount is well-formed and the fix is to trade more, not to correct the request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"503":{"description":"No live rate available, or FEE_PRICING_INVALID — this partner’s stored pricing cannot be applied. Either way no quote is issued, and neither is yours to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Lock a USDT/KES rate","tags":["Partner API"]}},"/v1/wallets":{"post":{"description":"Creates the address in pending_partner_approval and emails a 6-digit code to a registered administrator. The address is not usable until your administrator confirms the code and OnLink approves it. Re-registering the same address is idempotent: the existing registration is returned and no second row is created. If that registration is still awaiting your administrator and its code is no longer usable (expired, or cancelled after too many incorrect attempts), re-registering re-issues a fresh code — this is how you recover such an address. A code that is still live is never replaced, and no code is issued once the address has moved past your administrator’s approval.","operationId":"PartnerWalletsController_register","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterWalletDto"}}}},"responses":{"201":{"description":"Address registered, pending approval","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterWalletResponseDto"}}}},"400":{"description":"Invalid address (format or checksum), chain or asset","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"401":{"description":"Signature missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"409":{"description":"No administrator registered to send the approval code to","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Register a withdrawal address","tags":["Partner API"]},"get":{"operationId":"PartnerWalletsController_list","parameters":[],"responses":{"200":{"description":"Your addresses","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PartnerWalletResponseDto"}}}}}},"security":[{"partner-key":[]}],"summary":"List your registered withdrawal addresses","tags":["Partner API"]}},"/v1/wallets/{id}/confirm":{"post":{"description":"Moves the address to pending_onlink_approval — not to active. OnLink approves every withdrawal address as well, so a correct code alone does not make an address usable.","operationId":"PartnerWalletsController_confirm","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmWalletDto"}}}},"responses":{"200":{"description":"Confirmed; awaiting OnLink review","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmWalletResponseDto"}}}},"400":{"description":"Incorrect code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"403":{"description":"Code expired, cancelled, or too many incorrect attempts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Confirm a withdrawal address with your administrator’s code","tags":["Partner API"]}},"/v1/wallets/{id}":{"delete":{"description":"Immediate and irreversible. An order whose address is revoked before release will not deliver to it.","operationId":"PartnerWalletsController_revoke","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Revoked"},"404":{"description":"Not found, or already revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Revoke a withdrawal address","tags":["Partner API"]}},"/v1/admins":{"post":{"description":"The email domain must match the one configured for your partner account. This is checked server-side against an exact match — subdomains and lookalikes are refused.","operationId":"PartnerWalletsController_addAdmin","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddPartnerAdminDto"}}}},"responses":{"201":{"description":"Administrator registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerAdminResponseDto"}}}},"400":{"description":"Email is not on your configured domain","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Register an administrator who can approve withdrawal addresses","tags":["Partner API"]},"get":{"operationId":"PartnerWalletsController_listAdmins","parameters":[],"responses":{"200":{"description":"Your administrators","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PartnerAdminResponseDto"}}}}}},"security":[{"partner-key":[]}],"summary":"List your active administrators","tags":["Partner API"]}},"/v1/admins/{id}":{"delete":{"description":"Also lapses any approval code already sent to them — a removed administrator’s mailbox must not still hold a live second factor.","operationId":"PartnerWalletsController_removeAdmin","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Removed"},"404":{"description":"Not found, or already removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Remove an administrator","tags":["Partner API"]}},"/v1/orders/buy":{"post":{"description":"Consumes a buy quote and debits the KES directly from your own VA balance — there is nothing to send and no payment instructions to read. Delivery is asynchronous: nothing is sent until the debit is confirmed and the order reaches a terminal state. Register a webhook endpoint rather than assuming a create means the trade is done. Retry-safe: creating twice with the same partnerReference returns the original order rather than a second one.","operationId":"PartnerOrdersController_createBuy","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBuyOrderDto"}}}},"responses":{"202":{"description":"Order created, KES debited from your balance and reserved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BuyOrderResponseDto"}}}},"400":{"description":"Malformed body, an empty partnerReference, or a malformed expectedUsdtAmount","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"401":{"description":"Signature missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"404":{"description":"Unknown quoteId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"409":{"description":"Quote expired, already consumed, or of the wrong side; expectedUsdtAmount does not match the quote; partner suspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"422":{"description":"Over your per-order or rolling daily cap, or your KES balance is insufficient for this quote","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"503":{"description":"The buy leg is disabled, or your KES VA is not ready. The message names which case applies: SWAPS_DISABLED, KILL_SWITCH_ENGAGED, PARTNER_VA_NOT_ALLOCATED, PARTNER_VA_ACTIVATING, PARTNER_VA_SUSPENDED, or KES_DEBIT_FAILED (the quote is consumed — retry needs a new quote).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Create a buy order (KES in, USDT out)","tags":["Partner API"]}},"/v1/orders/sell":{"post":{"description":"Consumes a sell quote and returns your deposit address. Payout is asynchronous: nothing is paid out until you attach the transaction hash of your USDT send with PATCH /v1/orders/{id} and the order reaches a terminal state. A signed webhook tells you when it settles; polling is the fallback. Retry-safe: creating twice with the same partnerReference returns the original order rather than a second one. Your deposit address is the same for every order, so we cannot tell your orders apart by it — the transaction hash is what attributes a deposit.","operationId":"PartnerOrdersController_createSell","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSellOrderDto"}}}},"responses":{"202":{"description":"Order created, awaiting your USDT","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SellOrderResponseDto"}}}},"400":{"description":"Malformed body, or an empty partnerReference","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"401":{"description":"Signature missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"404":{"description":"Unknown quoteId or payoutAccountId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"409":{"description":"Quote expired, already consumed, or of the wrong side; payout destination revoked; partner suspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"422":{"description":"Over your per-order or rolling daily cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"503":{"description":"No USDT deposit address is allocated for your account yet","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Create a sell order (USDT in, KES out)","tags":["Partner API"]}},"/v1/orders/{id}":{"patch":{"description":"This is what attributes your deposit to this order. Attaching a different hash to an order that already has one is refused (409), never an overwrite — the first hash may already have been matched, and re-pointing the order would orphan a real deposit. Re-sending the SAME hash is safe and returns 200, so a retry after a lost response is fine.","operationId":"PartnerOrdersController_attachTxHash","parameters":[{"name":"id","required":true,"in":"path","description":"Your order id","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachTxHashDto"}}}},"responses":{"200":{"description":"Hash attached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachTxHashResponseDto"}}}},"400":{"description":"txHash is not a Tron transaction hash","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"409":{"description":"A different hash is already attached, the hash already funds another of your orders, the order is not a sell order, or it is no longer awaiting USDT","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Attach the transaction hash of your USDT send","tags":["Partner API"]},"get":{"description":"Scoped to your own orders: another partner’s order id returns 404, not 403. The shape follows the order’s side. A BUY order tells you where your KES debit stands. A SELL order carries the deposit address and the transaction hash you attached.","operationId":"PartnerOrdersController_findOne","parameters":[{"name":"id","required":true,"in":"path","description":"Your order id","schema":{"type":"string"}}],"responses":{"200":{"description":"Your order. The shape follows its side — a sell order matches `OrderDetailResponseDto`, a buy order `BuyOrderResponseDto`. Branch on the presence of `depositAddress` versus `kesDebit`, or keep the side you created it with.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OrderDetailResponseDto"},{"$ref":"#/components/schemas/BuyOrderResponseDto"}]}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Read one of your orders","tags":["Partner API"]}},"/v1/funding":{"get":{"description":"The KES rails you send us on (buy leg) and your USDT deposit address (sell leg). Every reference is an explicit field — the KES accountReference is the exact value to put in the M-PESA account-number field or the bank narration. Your USDT deposit address is permanent and shared across all your sell orders, so it cannot identify which order a deposit is for; attach the transaction hash with PATCH /v1/orders/{id}.","operationId":"PartnerFundingController_getFunding","parameters":[],"responses":{"200":{"description":"Deposit instructions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FundingResponseDto"}}}},"401":{"description":"Signature missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"503":{"description":"No USDT deposit address is allocated for your account yet. This is not self-serve — contact OnLink.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Deposit instructions for both legs","tags":["Partner API"]}},"/v1/payout-accounts":{"get":{"description":"The destinations a sell order may name in payoutAccountId. Read-only: destinations are registered by OnLink, not through this API, so your API credential cannot add a place for money to go. Only active destinations are listed. Account numbers are returned as the last four digits only — you select a destination by id and never type its number.","operationId":"PartnerFundingController_listPayoutAccounts","parameters":[],"responses":{"200":{"description":"Your active payout destinations","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PayoutDestinationResponseDto"}}}}},"401":{"description":"Signature missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Your KES payout destinations","tags":["Partner API"]}},"/v1/funding/mpesa-push":{"post":{"description":"Sends an STK Push prompt to the number you name. The payer approves it on their own handset with their PIN — there is no OTP for you to handle and no way for you to complete it on their behalf. The deposit lands in your own KES account, which is resolved from your verified business and is not something you can specify. This returns as soon as the prompt is out, so treat the response as pending: the completion signal is the credit.received webhook, and a prompt the payer declines or ignores produces no event at all. There is no idempotency key. A repeated call is a second prompt, and if the payer approves both then both deposits land and each raises its own credit.received.","operationId":"PartnerFundingController_mpesaPush","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MpesaPushRequestDto"}}}},"responses":{"202":{"description":"The prompt was sent. Nothing has moved yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MpesaPushResponseDto"}}}},"400":{"description":"The phone is not a recognised Kenyan mobile number, or the amount is not a whole number of KES greater than zero.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"401":{"description":"Signature missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"403":{"description":"Your credential was not granted funding:write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"404":{"description":"PARTNER_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"422":{"description":"MPESA_PUSH_REJECTED — the bank answered and refused this request, most often because the number is not M-PESA-registered. Do NOT retry: the same request cannot succeed. Fix the number, or ask the payer to check their line.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"429":{"description":"Rate limited. This route allows 10 requests per minute per partner — lower than the rest of the API, because every request raises a real prompt on a real phone. Read the Retry-After-partner header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"503":{"description":"MPESA_PUSH_UNAVAILABLE — we did not get an answer from the bank, so the prompt MAY OR MAY NOT have reached the payer. There is no idempotency key on this route, so a retry can raise a second prompt: prefer waiting for credit.received, and retry only if no credit arrives. Or one of the account refusals: PARTNER_VA_NOT_ALLOCATED (you have no KES account yet), PARTNER_VA_ACTIVATING (it is being opened), PARTNER_VA_SUSPENDED (it is not currently usable — talk to us).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Ask a payer to send you KES over M-PESA","tags":["Partner API"]}},"/v1/balances":{"get":{"description":"The KES account you hold with us and its available balance. USDT is added when the ledger ships.","operationId":"PartnerBalancesController_getBalances","parameters":[],"responses":{"200":{"description":"Your balances","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BalancesResponseDto"}}}},"401":{"description":"Signature missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"503":{"description":"No KES account is allocated for your business yet, or it is not yet active. This is not self-serve — contact OnLink.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Your balances","tags":["Partner API"]}},"/v1/transfers":{"post":{"description":"Validates the destination, verifies the beneficiary name where the rail supports it, checks your caps and balance, then writes the instruction and sends a confirmation code to your registered person. Nothing reaches the bank until that code is verified via POST /v1/confirmations/:id.","operationId":"PartnerTransfersController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerCreateTransferDto"}}}},"responses":{"202":{"description":"The instruction was created and a confirmation code was sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerTransferResponseDto"}}}},"404":{"description":"PARTNER_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"409":{"description":"PARTNER_SUSPENDED or TRANSFER_IDEMPOTENCY_CONFLICT.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"422":{"description":"TRANSFER_AMOUNT_INVALID, TRANSFER_DESTINATION_INVALID, TRANSFER_NAME_MISMATCH, TRANSFER_EXCEEDS_PER_ORDER_CAP, TRANSFER_EXCEEDS_DAILY_CAP, or TRANSFER_INSUFFICIENT_BALANCE.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"503":{"description":"PARTNER_TRANSFERS_DISABLED, KILL_SWITCH_ENGAGED, CONFIRMATION_CONTACT_MISSING, or one of the VA resolver refusals (PARTNER_VA_NOT_ALLOCATED / _ACTIVATING / _SUSPENDED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Send money from your KES account","tags":["Partner API"]}},"/v1/transfers/{id}":{"get":{"operationId":"PartnerTransfersController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"The transfer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerTransferResponseDto"}}}},"404":{"description":"TRANSFER_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Read a transfer","tags":["Partner API"]}},"/v1/transfers/batch":{"post":{"description":"A loop over POST /v1/transfers behind a single confirmation code (spec §2.3). Each item is validated exactly as a single transfer would be; a failed item is reported with its refusal reason and the rest proceed. Nothing reaches the bank until the one code is verified via POST /v1/confirmations/:id.","operationId":"PartnerTransfersController_createBatch","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerCreateBatchDto"}}}},"responses":{"202":{"description":"The batch was created and a confirmation code was sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerBatchResponseDto"}}}},"422":{"description":"BATCH_TOO_LARGE or BATCH_NO_VALID_ITEMS.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"503":{"description":"PARTNER_BATCH_TRANSFERS_DISABLED or KILL_SWITCH_ENGAGED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Send N transfers under one confirmation","tags":["Partner API"]}},"/v1/transfers/batch/{id}":{"get":{"operationId":"PartnerTransfersController_getBatch","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"The batch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerBatchResponseDto"}}}},"404":{"description":"BATCH_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Read a batch, with every item outcome","tags":["Partner API"]}},"/v1/confirmations/{id}":{"post":{"description":"Verifies the code sent to your registered confirmation contact and, once correct, hands the instruction off for execution. The response reflects the instruction status immediately after this call (confirmed) — poll GET /v1/transfers/:id for what happens next.","operationId":"PartnerConfirmationsController_confirm","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerConfirmDto"}}}},"responses":{"200":{"description":"The code was correct. Execution has been handed off.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerConfirmationResultDto"}}}},"403":{"description":"CONFIRMATION_LOCKED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"404":{"description":"CONFIRMATION_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"409":{"description":"CONFIRMATION_ALREADY_VERIFIED, CONFIRMATION_EXPIRED, CONFIRMATION_CANCELLED, or TRANSFER_NOT_CONFIRMABLE.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"422":{"description":"CONFIRMATION_CODE_INVALID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Confirm a money-movement instruction","tags":["Partner API"]}},"/v1/confirmations/{id}/resend":{"post":{"description":"Re-issues the code on the shared escalating resend ladder. Refused while the previous code is still within its cooldown window.","operationId":"PartnerConfirmationsController_resend","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"202":{"description":"A fresh code was sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerResendResultDto"}}}},"403":{"description":"CONFIRMATION_LOCKED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"404":{"description":"CONFIRMATION_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"409":{"description":"CONFIRMATION_ALREADY_VERIFIED, CONFIRMATION_EXPIRED, or CONFIRMATION_CANCELLED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"429":{"description":"CONFIRMATION_RESEND_TOO_SOON.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Resend the confirmation code","tags":["Partner API"]}},"/v1/usdt/sends":{"post":{"description":"Validates the destination against your wallet allowlist (only an active wallet may receive value), checks the amount against our minimum and your caps, then writes the instruction and sends a confirmation code to your registered person. Nothing reaches the chain until that code is verified via POST /v1/confirmations/:id.","operationId":"PartnerUsdtSendsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerCreateUsdtSendDto"}}}},"responses":{"202":{"description":"The instruction was created and a confirmation code was sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerUsdtSendResponseDto"}}}},"404":{"description":"PARTNER_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"409":{"description":"PARTNER_SUSPENDED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"422":{"description":"USDT_SEND_DESTINATION_NOT_ALLOWLISTED, USDT_SEND_BELOW_MINIMUM, USDT_SEND_EXCEEDS_PER_ORDER_CAP, or USDT_SEND_EXCEEDS_DAILY_CAP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"503":{"description":"PARTNER_USDT_SENDS_DISABLED or KILL_SWITCH_ENGAGED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Send USDT from your wallet to a registered destination","tags":["Partner API"]}},"/v1/usdt/sends/{id}":{"get":{"operationId":"PartnerUsdtSendsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"The send.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerUsdtSendResponseDto"}}}},"404":{"description":"USDT_SEND_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Read a USDT send","tags":["Partner API"]}},"/v1/collections":{"post":{"description":"Mints a reference and a dedicated USD deposit account. expectedAmount is advisory — a mismatch is reported later, never refused here.","operationId":"PartnerCollectionsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerCreateCollectionDto"}}}},"responses":{"201":{"description":"The collection was opened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerCollectionResponseDto"}}}},"404":{"description":"PARTNER_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"409":{"description":"PARTNER_SUSPENDED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"422":{"description":"COLLECTION_CURRENCY_UNSUPPORTED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}},"503":{"description":"PARTNER_COLLECTIONS_DISABLED, COLLECTIONS_WALLET_NOT_PROVISIONED, or BRIDGE_UNAVAILABLE.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Open a collection","tags":["Partner API"]},"get":{"operationId":"PartnerCollectionsController_list","parameters":[{"name":"reference","required":false,"in":"query","schema":{"example":"DEMO-7K3Q2M","type":"string"}},{"name":"status","required":false,"in":"query","schema":{"example":"open","type":"string","enum":["open","received","crediting","credited","refunded","expired"]}},{"name":"from","required":false,"in":"query","description":"Inclusive lower bound on creation time, ISO 8601.","schema":{"example":"2026-09-01T00:00:00.000Z","type":"string"}},{"name":"to","required":false,"in":"query","description":"Exclusive upper bound on creation time, ISO 8601.","schema":{"example":"2026-10-01T00:00:00.000Z","type":"string"}}],"responses":{"200":{"description":"Your collections, newest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PartnerCollectionResponseDto"}}}}}},"security":[{"partner-key":[]}],"summary":"List your collections","tags":["Partner API"]}},"/v1/collections/{id}":{"get":{"operationId":"PartnerCollectionsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"The collection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerCollectionResponseDto"}}}},"404":{"description":"COLLECTION_NOT_FOUND.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponseDto"}}}}},"security":[{"partner-key":[]}],"summary":"Read a collection","tags":["Partner API"]}}},"info":{"title":"OnLink Partner API","description":"USDT/KES partner API. Every request is signed over its raw bytes — see the security scheme. Signing string: METHOD, path (query included), timestamp, nonce and the sha256 of the body, newline-joined.","version":"1.0","contact":{}},"tags":[{"name":"Partner API","description":""}],"servers":[{"url":"https://sandbox.onlink.africa","description":"Sandbox"}],"components":{"securitySchemes":{"partner-key":{"type":"apiKey","in":"header","name":"X-OnLink-Key"}},"schemas":{"PartnerHealthResponseDto":{"type":"object","properties":{"status":{"type":"string","description":"Always the exact string `ok`. Absolute, never hedged: if the plane is not serving you get an HTTP error, not a degraded value in this field.","enum":["ok"],"example":"ok"}},"required":["status"]},"PartnerErrorResponseDto":{"type":"object","properties":{"statusCode":{"type":"number","description":"HTTP status code, repeated in the body.","example":409},"message":{"type":"string","description":"What went wrong. For the refusals this API defines, this is the stable short code to branch on — e.g. `QUOTE_EXPIRED_OR_CONSUMED`, `PAYOUT_ACCOUNT_NOT_PAYABLE`, `TX_HASH_ALREADY_ATTACHED`, `PARTNER_SUSPENDED`. Validation failures instead read `Validation failed on: <fields>`.","example":"QUOTE_EXPIRED_OR_CONSUMED"},"error":{"type":"string","description":"The status name, or the label the thrown exception supplied. Never a stack trace and never an upstream provider’s own error text.","example":"CONFLICT"},"requestId":{"type":"string","description":"Correlation id for this exact request, repeated as the `X-Request-Id` header on this error response. Quote it when asking OnLink about a failure — it is the only handle that resolves to your request in our logs. Note that a SUCCESSFUL response carries neither the header nor a body field: both are written on the error path only. To hold a handle on a call that succeeded, send your own `X-Request-Id`; an inbound value is echoed back here if that request then fails.","example":"req-1756512000000-a1b2c3d"},"errorCode":{"type":"string","description":"Present only when the refusal carries a machine-readable code distinct from `message`. Absent on most partner refusals.","example":"ORDER_EXCEEDS_DAILY_CAP"},"retryAfterSeconds":{"type":"number","description":"Seconds to wait before retrying, mirrored into the standard `Retry-After` header. Set only when a refusal supplies one — and the partner RATE LIMIT does not: that 429 comes from the named `partner` throttler, which carries its window in `Retry-After-partner` (the throttler suffixes the header with its own name) and puts nothing in the body. Read that header for a rate-limit backoff, not this field.","example":30}},"required":["statusCode","message","error","requestId"]},"CreateQuoteDto":{"type":"object","properties":{"side":{"type":"string","enum":["buy","sell"],"description":"buy: you pay KES and receive USDT (on-ramp). sell: you send USDT and receive KES (off-ramp)."},"kesAmount":{"type":"string","description":"KES amount as a decimal string, max 2 decimal places. Provide exactly one of kesAmount or usdtAmount.","example":"10000.00"},"usdtAmount":{"type":"string","description":"USDT amount as a decimal string, max 6 decimal places (Tron USDT precision). Provide exactly one of kesAmount or usdtAmount.","example":"100.000000"}},"required":["side"]},"QuoteFeeDto":{"type":"object","properties":{"kesAmount":{"type":"string","description":"The fee charged on this trade, KES, always 2 decimal places. On a **buy** it is ADDED to what you pay; on a **sell** it is DEDUCTED from what you receive. `\"0.00\"` means no fee. Never a JSON number.","example":"24339.30"},"currency":{"type":"string","description":"Always `KES`. The fee is charged in KES on both sides.","enum":["KES"],"example":"KES"},"percentBps":{"type":"integer","description":"The percentage applied, in basis points — `190` is 1.90%. Applied to the `kesAmount` PRINCIPAL, not to the total, and rounded UP to the cent. An integer, not a string: unlike the amounts, it is not money.","example":190},"flatKesAmount":{"type":"string","description":"The flat component applied, KES, 2 decimal places. Charged on every order regardless of size. `\"0.00\"` means no flat component.","example":"0.30"},"capKesAmount":{"type":"string","description":"The ceiling applied to the whole fee, KES, 2 decimal places — or `null` when the fee is UNCAPPED. **`null` and `\"0.00\"` are different**: `null` means there is no ceiling, `\"0.00\"` means the fee is capped at nothing and this trade is free.","example":null,"nullable":true}},"required":["kesAmount","currency","percentBps","flatKesAmount","capKesAmount"]},"QuoteResponseDto":{"type":"object","properties":{"quoteId":{"type":"string","description":"The quote id. Single-use: pass it to POST /v1/orders/sell, which consumes it.","format":"uuid","example":"f1c0a5d2-3b4e-4a71-9c8d-0e1f2a3b4c5d"},"side":{"type":"string","description":"Echoes the side you asked for. `buy` prices the on-ramp, `sell` the off-ramp — a sell order will not consume a buy quote (409).","enum":["buy","sell"],"example":"sell"},"kesAmount":{"type":"string","description":"The trade PRINCIPAL in KES, decimal string, always 2 decimal places (KES cents). Never a JSON number. Whichever leg you did not supply is the derived one, and it rounds in OnLink’s favour. `kesAmount × rate ↔ usdtAmount` holds exactly — **the fee is not folded into either.** What moves is `kesTotalAmount`.","example":"1281000.00"},"usdtAmount":{"type":"string","description":"USDT leg, decimal string, always 6 decimal places (Tron USDT precision). Never a JSON number.","example":"10000.000000"},"rate":{"type":"string","description":"KES per USDT at 2 decimal places, the rate this quote is locked at. Quotes are only ever issued off a live rate source.","example":"128.10"},"kesTotalAmount":{"type":"string","description":"The KES that actually moves, decimal string, 2 decimal places. **buy:** `kesAmount + fee.kesAmount` — pay this, and the payment instructions quote it. **sell:** `kesAmount − fee.kesAmount` — this is what we pay you. This is the figure that appears on a bank statement, so it is the one to reconcile against.","example":"1305339.30"},"fee":{"description":"The disclosed fee on this trade, as a named component beside the rate. Zero everywhere when this partner is not priced.","allOf":[{"$ref":"#/components/schemas/QuoteFeeDto"}]},"expiresAt":{"type":"string","description":"When the locked rate stops being executable, ISO 8601 UTC. Short by design — a long-lived locked rate is a free option against us.","format":"date-time","example":"2026-08-31T09:31:30.000Z"},"settlementEstimateSeconds":{"type":"integer","description":"How long settlement is expected to take once your funds are confirmed, in whole seconds. **A different clock from `expiresAt`** — that is how long you have to send, this is how long settlement then takes. One of a small set of published bands rather than a continuously varying figure, so treat it as a band and not as a precise duration. An integer, not a string: unlike the amounts, it is not money.","enum":[1800,21600,86400],"example":21600}},"required":["quoteId","side","kesAmount","usdtAmount","rate","kesTotalAmount","fee","expiresAt","settlementEstimateSeconds"]},"RegisterWalletDto":{"type":"object","properties":{"chain":{"type":"string","enum":["tron"],"description":"Only tron at launch."},"asset":{"type":"string","enum":["USDT"],"description":"Only USDT at launch."},"address":{"type":"string","description":"The withdrawal address. Validated for base58check format AND checksum before it is stored.","example":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"},"label":{"type":"string","description":"Your own label for this address. Never used for routing.","maxLength":120}},"required":["chain","asset","address"]},"RegisterWalletResponseDto":{"type":"object","properties":{"walletId":{"type":"string","description":"The registration id. Use it for POST /v1/wallets/{id}/confirm and DELETE /v1/wallets/{id}.","format":"uuid","example":"b8e4d1c7-2f39-4a05-8d6b-1c2e3f4a5b60"},"chain":{"type":"string","description":"Network. Tron only at launch.","enum":["tron"],"example":"tron"},"asset":{"type":"string","description":"Asset. USDT only at launch.","enum":["USDT"],"example":"USDT"},"address":{"type":"string","description":"The withdrawal address, **in full** — exactly as stored, checksum already verified. Compare the entire string when you reconcile: a truncated rendering makes a lookalike address indistinguishable from the real one, and USDT sent to the wrong address is an irrecoverable burn.","example":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"},"label":{"type":"string","description":"Your own label, echoed back. Never used for routing. `null` if you sent none.","nullable":true,"example":"Settlement wallet — Tron"},"status":{"type":"string","description":"Where the address sits in the two-party approval. **`active` is the only deliverable value** — a correct administrator code moves it to `pending_onlink_approval`, and OnLink’s own review is what makes it usable. `revoked` is terminal.","enum":["pending_partner_approval","pending_onlink_approval","active","revoked"],"example":"pending_partner_approval"},"screeningStatus":{"type":"string","description":"Address-screening outcome. Never defaults to a clear verdict: a newly registered address reads `pending` until a screen has actually run.","enum":["pending","clear","flagged"],"example":"pending"},"createdAt":{"type":"string","description":"When the address was registered, ISO 8601 UTC.","format":"date-time","example":"2026-08-31T09:30:00.000Z"},"approvalSentTo":{"type":"string","description":"The administrator mailbox the 6-digit code was sent to, **masked** (`j•••@yourcompany.com`). Masked rather than whole so the response is not an address-harvesting oracle for anyone holding a leaked credential. `null` when no code was issued — an idempotent re-registration of an address that is already past your administrator’s approval.","nullable":true,"example":"o•••@yourcompany.com"},"nextStep":{"type":"string","description":"Plain-language next action, keyed to `status`. Prose for a human reading the response — branch on `status`, not on this string.","example":"A 6-digit approval code was emailed to a registered administrator. Confirm with POST /v1/wallets/{id}/confirm. The address is NOT usable until OnLink also approves it."}},"required":["walletId","chain","asset","address","label","status","screeningStatus","createdAt","approvalSentTo","nextStep"]},"ConfirmWalletDto":{"type":"object","properties":{"otp":{"type":"string","description":"The 6-digit code emailed to your registered administrator. Numeric only.","example":"123456"}},"required":["otp"]},"ConfirmWalletResponseDto":{"type":"object","properties":{"walletId":{"type":"string","description":"The registration id. Use it for POST /v1/wallets/{id}/confirm and DELETE /v1/wallets/{id}.","format":"uuid","example":"b8e4d1c7-2f39-4a05-8d6b-1c2e3f4a5b60"},"chain":{"type":"string","description":"Network. Tron only at launch.","enum":["tron"],"example":"tron"},"asset":{"type":"string","description":"Asset. USDT only at launch.","enum":["USDT"],"example":"USDT"},"address":{"type":"string","description":"The withdrawal address, **in full** — exactly as stored, checksum already verified. Compare the entire string when you reconcile: a truncated rendering makes a lookalike address indistinguishable from the real one, and USDT sent to the wrong address is an irrecoverable burn.","example":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"},"label":{"type":"string","description":"Your own label, echoed back. Never used for routing. `null` if you sent none.","nullable":true,"example":"Settlement wallet — Tron"},"status":{"type":"string","description":"Where the address sits in the two-party approval. **`active` is the only deliverable value** — a correct administrator code moves it to `pending_onlink_approval`, and OnLink’s own review is what makes it usable. `revoked` is terminal.","enum":["pending_partner_approval","pending_onlink_approval","active","revoked"],"example":"pending_partner_approval"},"screeningStatus":{"type":"string","description":"Address-screening outcome. Never defaults to a clear verdict: a newly registered address reads `pending` until a screen has actually run.","enum":["pending","clear","flagged"],"example":"pending"},"createdAt":{"type":"string","description":"When the address was registered, ISO 8601 UTC.","format":"date-time","example":"2026-08-31T09:30:00.000Z"},"nextStep":{"type":"string","description":"Plain-language next action. On success this says the address is awaiting OnLink review and is **not yet usable** — a correct code alone never makes an address deliverable.","example":"Approved by your administrator. The address is now awaiting OnLink review and is NOT yet usable for delivery."}},"required":["walletId","chain","asset","address","label","status","screeningStatus","createdAt","nextStep"]},"PartnerWalletResponseDto":{"type":"object","properties":{"walletId":{"type":"string","description":"The registration id. Use it for POST /v1/wallets/{id}/confirm and DELETE /v1/wallets/{id}.","format":"uuid","example":"b8e4d1c7-2f39-4a05-8d6b-1c2e3f4a5b60"},"chain":{"type":"string","description":"Network. Tron only at launch.","enum":["tron"],"example":"tron"},"asset":{"type":"string","description":"Asset. USDT only at launch.","enum":["USDT"],"example":"USDT"},"address":{"type":"string","description":"The withdrawal address, **in full** — exactly as stored, checksum already verified. Compare the entire string when you reconcile: a truncated rendering makes a lookalike address indistinguishable from the real one, and USDT sent to the wrong address is an irrecoverable burn.","example":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"},"label":{"type":"string","description":"Your own label, echoed back. Never used for routing. `null` if you sent none.","nullable":true,"example":"Settlement wallet — Tron"},"status":{"type":"string","description":"Where the address sits in the two-party approval. **`active` is the only deliverable value** — a correct administrator code moves it to `pending_onlink_approval`, and OnLink’s own review is what makes it usable. `revoked` is terminal.","enum":["pending_partner_approval","pending_onlink_approval","active","revoked"],"example":"pending_partner_approval"},"screeningStatus":{"type":"string","description":"Address-screening outcome. Never defaults to a clear verdict: a newly registered address reads `pending` until a screen has actually run.","enum":["pending","clear","flagged"],"example":"pending"},"createdAt":{"type":"string","description":"When the address was registered, ISO 8601 UTC.","format":"date-time","example":"2026-08-31T09:30:00.000Z"}},"required":["walletId","chain","asset","address","label","status","screeningStatus","createdAt"]},"AddPartnerAdminDto":{"type":"object","properties":{"email":{"type":"string","description":"Administrator email. MUST be on the domain configured for your partner account — enforced server-side.","example":"ops@yourcompany.com"},"name":{"type":"string","description":"Administrator name, for the audit trail.","maxLength":200}},"required":["email","name"]},"PartnerAdminResponseDto":{"type":"object","properties":{"adminId":{"type":"string","description":"The administrator id. Use it for DELETE /v1/admins/{id}.","format":"uuid","example":"c9f2a3b4-5d6e-4708-9a1b-2c3d4e5f6071"},"email":{"type":"string","description":"Administrator email, returned whole. It is an address you supplied on your own configured domain, so echoing it discloses nothing you do not hold — unlike `approvalSentTo`, which is masked because it accompanies a live second factor.","example":"ops@yourcompany.com"},"name":{"type":"string","description":"Administrator name, as you registered it.","example":"Amina Otieno"},"status":{"type":"string","description":"Lifecycle. Removal is recorded rather than deleted, so a removed administrator reads `removed` and never disappears from the audit trail — but only `active` administrators receive approval codes, and GET /v1/admins lists only those.","enum":["active","removed"],"example":"active"}},"required":["adminId","email","name","status"]},"CreateBuyOrderDto":{"type":"object","properties":{"quoteId":{"type":"string","description":"A quote id from POST /v1/quotes with side=buy. Single-use, and the amounts are taken from it — they are not restated here."},"partnerReference":{"type":"string","description":"Your own reconciliation handle. Unique per partner, and RETRY-SAFE: creating twice with the same reference returns the original order rather than a second one.","maxLength":120},"expectedUsdtAmount":{"type":"string","description":"Optional. If supplied it must equal the quote’s usdtAmount exactly, or the order is refused (409). This catches a bug on your side where one number is displayed and a different one is ordered.","example":"10.000000"}},"required":["quoteId","partnerReference"]},"BuyKesDebitDto":{"type":"object","properties":{"status":{"type":"string","description":"Where your KES debit stands.","enum":["reserved","confirmed","failed"],"example":"reserved"}},"required":["status"]},"BuyOrderResponseDto":{"type":"object","properties":{"orderId":{"type":"string","description":"The order id. Use it for GET /v1/orders/{id}.","format":"uuid","example":"9c1e4b07-6d52-4a83-91fe-40ab72c5d318"},"status":{"type":"string","description":"Where the order is now. A newly created buy order is `funds_reserved` — your KES has already been debited and is awaiting the bank’s confirmation. `settled`, `rejected` and `expired` are the terminal states. **Delivery is asynchronous — subscribe to webhooks rather than polling this field to completion.**","enum":["awaiting_payment","awaiting_usdt","payment_matched","usdt_received","funds_reserved","awaiting_approval","rejected","sending","paying_out","settled","expired","review"],"example":"funds_reserved"},"kesAmount":{"type":"string","description":"The trade PRINCIPAL in KES, decimal string, always 2 decimal places (KES cents). Taken from the quote — never restated by you, never a JSON number. **This is not what was debited**: the debit is `kesTotalAmount`, which adds the disclosed fee.","example":"1281000.00"},"usdtAmount":{"type":"string","description":"USDT you will receive, decimal string, always 6 decimal places. Never a JSON number. Derived from the PRINCIPAL — the fee does not change it.","example":"10000.000000"},"rate":{"type":"string","description":"KES per USDT the order is priced at, from the consumed quote.","example":"128.1000"},"kesTotalAmount":{"type":"string","description":"**What was actually debited: principal + fee.** This is the figure that appears on your statement, and the one to reconcile against. It equals `kesAmount` exactly when no fee applies.","example":"1305339.30"},"fee":{"description":"The disclosed fee on this order, snapshotted from the quote when the order was created. It is not recomputed later, so it is exactly what you were shown. See /docs/concepts/fees.","allOf":[{"$ref":"#/components/schemas/QuoteFeeDto"}]},"expiresAt":{"type":"string","description":"ISO 8601 UTC. Informational only on this leg — nothing expires it.","format":"date-time","example":"2026-09-02T09:30:00.000Z"},"kesDebit":{"description":"Where your KES debit stands.","allOf":[{"$ref":"#/components/schemas/BuyKesDebitDto"}]}},"required":["orderId","status","kesAmount","usdtAmount","rate","kesTotalAmount","fee","expiresAt","kesDebit"]},"CreateSellOrderDto":{"type":"object","properties":{"quoteId":{"type":"string","description":"A quote id from POST /v1/quotes with side=sell. Single-use, and the amounts are taken from it — they are not restated here."},"partnerReference":{"type":"string","description":"Your own reconciliation handle. Unique per partner, and RETRY-SAFE: creating twice with the same reference returns the original order rather than a second one.","maxLength":120},"payoutAccountId":{"type":"string","description":"Which of your registered KES destinations to pay out to. Destinations are set up by OnLink, not through this API — see GET /v1/funding."}},"required":["quoteId","partnerReference","payoutAccountId"]},"SellOrderResponseDto":{"type":"object","properties":{"orderId":{"type":"string","description":"The order id. Use it for PATCH and GET /v1/orders/{id}.","format":"uuid","example":"3a7c1f90-8b2d-4e56-9a01-cd23ef456789"},"status":{"type":"string","description":"Where the order is now. A newly created sell order is `awaiting_usdt`; attaching the hash moves it on once your deposit is attributed to this order. `settled`, `rejected` and `expired` are the terminal states. **Settlement is asynchronous — subscribe to webhooks rather than polling this field to completion.**","enum":["awaiting_payment","awaiting_usdt","payment_matched","usdt_received","funds_reserved","awaiting_approval","rejected","sending","paying_out","settled","expired","review"],"example":"awaiting_usdt"},"kesAmount":{"type":"string","description":"The trade PRINCIPAL in KES, decimal string, always 2 decimal places (KES cents). Taken from the quote — never restated by you, never a JSON number. **This is not what you receive**: we pay `kesTotalAmount`, which is this figure less the disclosed fee.","example":"1281000.00"},"usdtAmount":{"type":"string","description":"USDT to send us, decimal string, always 6 decimal places. Never a JSON number.","example":"10000.000000"},"rate":{"type":"string","description":"KES per USDT the order is priced at, from the consumed quote.","example":"128.1000"},"kesTotalAmount":{"type":"string","description":"**What we pay you: principal − fee.** This is the figure that appears on your bank statement, so it is the one to reconcile against.","example":"1256660.70"},"fee":{"description":"The disclosed fee on this order, snapshotted from the quote when the order was created — not recomputed later, so it is exactly what you were shown. See /docs/concepts/fees.","allOf":[{"$ref":"#/components/schemas/QuoteFeeDto"}]},"expiresAt":{"type":"string","description":"Deposit deadline, ISO 8601 UTC. The same instant as `sendBefore`. This is the deposit window, not the quote TTL — the rate is already locked.","format":"date-time","example":"2026-09-01T09:30:00.000Z"},"depositAddress":{"type":"string","description":"Your Tron (TRC-20) deposit address, **in full** — send USDT here. It is permanent and identical for every one of your orders, so it cannot tell your orders apart: attach the transaction hash with PATCH /v1/orders/{id}. Compare the whole string, never a prefix: a lookalike address differing only in the middle is an irrecoverable burn.","example":"TQiBwkXtUUNygiLdSwdeaZCKagwPsD1a7C"},"chain":{"type":"string","description":"Deposit network. Tron only at launch.","enum":["tron"],"example":"tron"},"sendBefore":{"type":"string","description":"The deposit deadline again, under the name the integration guide uses. The same instant as `expiresAt`; both are present so you need not know they are one field.","format":"date-time","example":"2026-09-01T09:30:00.000Z"}},"required":["orderId","status","kesAmount","usdtAmount","rate","kesTotalAmount","fee","expiresAt","depositAddress","chain","sendBefore"]},"AttachTxHashDto":{"type":"object","properties":{"txHash":{"type":"string","description":"The Tron transaction hash of your USDT send: 64 hex characters, with or without a 0x prefix. This is what attributes the deposit to this order — we never infer it from the amount, because your deposit address is the same for every order.","example":"5d6a3c7b1e4f2a908c6d5b4a3928170f6e5d4c3b2a1908f7e6d5c4b3a2918070"}},"required":["txHash"]},"AttachTxHashResponseDto":{"type":"object","properties":{"orderId":{"type":"string","description":"The order the hash is now attached to.","format":"uuid","example":"3a7c1f90-8b2d-4e56-9a01-cd23ef456789"},"status":{"type":"string","description":"The order status after attaching. Still `awaiting_usdt` until our matcher confirms the deposit on-chain — attaching a hash is a claim, not a receipt.","enum":["awaiting_payment","awaiting_usdt","payment_matched","usdt_received","funds_reserved","awaiting_approval","rejected","sending","paying_out","settled","expired","review"],"example":"awaiting_usdt"},"txHash":{"type":"string","description":"The hash as stored: lower-cased, `0x` stripped. Re-sending the same hash returns this unchanged (200); a DIFFERENT hash is refused (409), never an overwrite.","example":"5d6a3c7b1e4f2a908c6d5b4a3928170f6e5d4c3b2a1908f7e6d5c4b3a2918070"}},"required":["orderId","status","txHash"]},"OrderDetailResponseDto":{"type":"object","properties":{"orderId":{"type":"string","description":"The order id. Use it for PATCH and GET /v1/orders/{id}.","format":"uuid","example":"3a7c1f90-8b2d-4e56-9a01-cd23ef456789"},"status":{"type":"string","description":"Where the order is now. A newly created sell order is `awaiting_usdt`; attaching the hash moves it on once your deposit is attributed to this order. `settled`, `rejected` and `expired` are the terminal states. **Settlement is asynchronous — subscribe to webhooks rather than polling this field to completion.**","enum":["awaiting_payment","awaiting_usdt","payment_matched","usdt_received","funds_reserved","awaiting_approval","rejected","sending","paying_out","settled","expired","review"],"example":"awaiting_usdt"},"kesAmount":{"type":"string","description":"The trade PRINCIPAL in KES, decimal string, always 2 decimal places (KES cents). Taken from the quote — never restated by you, never a JSON number. **This is not what you receive**: we pay `kesTotalAmount`, which is this figure less the disclosed fee.","example":"1281000.00"},"usdtAmount":{"type":"string","description":"USDT to send us, decimal string, always 6 decimal places. Never a JSON number.","example":"10000.000000"},"rate":{"type":"string","description":"KES per USDT the order is priced at, from the consumed quote.","example":"128.1000"},"kesTotalAmount":{"type":"string","description":"**What we pay you: principal − fee.** This is the figure that appears on your bank statement, so it is the one to reconcile against.","example":"1256660.70"},"fee":{"description":"The disclosed fee on this order, snapshotted from the quote when the order was created — not recomputed later, so it is exactly what you were shown. See /docs/concepts/fees.","allOf":[{"$ref":"#/components/schemas/QuoteFeeDto"}]},"expiresAt":{"type":"string","description":"Deposit deadline, ISO 8601 UTC. The same instant as `sendBefore`. This is the deposit window, not the quote TTL — the rate is already locked.","format":"date-time","example":"2026-09-01T09:30:00.000Z"},"depositAddress":{"type":"string","description":"Your Tron (TRC-20) deposit address, **in full** — send USDT here. It is permanent and identical for every one of your orders, so it cannot tell your orders apart: attach the transaction hash with PATCH /v1/orders/{id}. Compare the whole string, never a prefix: a lookalike address differing only in the middle is an irrecoverable burn.","example":"TQiBwkXtUUNygiLdSwdeaZCKagwPsD1a7C"},"chain":{"type":"string","description":"Deposit network. Tron only at launch.","enum":["tron"],"example":"tron"},"sendBefore":{"type":"string","description":"The deposit deadline again, under the name the integration guide uses. The same instant as `expiresAt`; both are present so you need not know they are one field.","format":"date-time","example":"2026-09-01T09:30:00.000Z"},"txHash":{"type":"string","description":"The Tron transaction hash you attached, normalised to lower-case with any `0x` prefix stripped. `null` until you attach one.","nullable":true,"example":"5d6a3c7b1e4f2a908c6d5b4a3928170f6e5d4c3b2a1908f7e6d5c4b3a2918070"}},"required":["orderId","status","kesAmount","usdtAmount","rate","kesTotalAmount","fee","expiresAt","depositAddress","chain","sendBefore","txHash"]},"KesFundingRailDto":{"type":"object","properties":{"rail":{"type":"string","description":"Which KES rail these instructions are for.","enum":["mpesa_paybill","bank_transfer"],"example":"mpesa_paybill"},"paybill":{"type":"string","description":"The M-PESA shortcode. Present on the `mpesa_paybill` rail only — absent, not null, on the bank rail.","example":"444174"},"accountReference":{"type":"string","description":"**The exact value the payer must quote**, verbatim. On the paybill rail this goes in M-PESA’s \"Account Number\" field; on the bank rail it is the beneficiary account number. This is your own KES balance account at OnLink — funding it tops up your balance generally, and is not tied to any single order.","example":"46013001348902"},"beneficiaryName":{"type":"string","description":"The beneficiary name a bank transfer must be addressed to. Included ONLY when the account carries a name our bank has confirmed back to us. If it is absent, ask OnLink rather than composing one — the receiving bank matches on this string.","example":"ONLINK MASTER_EXAMPLECO"},"currency":{"type":"string","description":"Always KES on this leg.","enum":["KES"],"example":"KES"},"instructions":{"type":"string","description":"Human-readable instructions, consistent with the explicit fields above rather than a substitute for them. Integrate against the fields; show this to a person.","example":"M-PESA > Lipa na M-PESA > Pay Bill > Business number 444174 > Account number 46013001348902. This tops up your KES balance — it is not tied to any single order, and there is no reference to quote per order."}},"required":["rail","accountReference","currency","instructions"]},"UsdtFundingRailDto":{"type":"object","properties":{"chain":{"type":"string","description":"Deposit network. Tron (TRC-20) only.","enum":["tron"],"example":"tron"},"asset":{"type":"string","description":"Deposit asset. USDT only.","enum":["USDT"],"example":"USDT"},"address":{"type":"string","description":"Your permanent Tron deposit address, **in full**. Identical for every one of your sell orders, so it cannot identify which order a deposit is for — attach the transaction hash with PATCH /v1/orders/{id}. Compare the whole string when you verify it: a lookalike differing only in the middle is an irrecoverable burn.","example":"TQiBwkXtUUNygiLdSwdeaZCKagwPsD1a7C"},"memo":{"type":"string","description":"Always `null`. Stated rather than omitted, because an integrator who sees no `memo` field cannot tell whether we forgot it or Tron has none — and a deposit sent without a REQUIRED memo is lost.","nullable":true,"example":null},"instructions":{"type":"string","description":"Human-readable instructions for the USDT send.","example":"Send USDT on the Tron (TRC-20) network to this address. It is your permanent deposit address and is the same for every sell order, so we cannot tell your orders apart by it — after sending, attach the transaction hash with PATCH /v1/orders/{id}. No memo or tag is used."}},"required":["chain","asset","address","memo","instructions"]},"FundingResponseDto":{"type":"object","properties":{"kes":{"description":"The KES rails you send us on (buy leg). **May be an empty array** — a partner with no activated collection account gets `[]` rather than a placeholder reference, because publishing a reference nothing is watching is money arriving nowhere.","type":"array","items":{"$ref":"#/components/schemas/KesFundingRailDto"}},"usdt":{"description":"Your USDT deposit address (sell leg).","allOf":[{"$ref":"#/components/schemas/UsdtFundingRailDto"}]}},"required":["kes","usdt"]},"PayoutDestinationResponseDto":{"type":"object","properties":{"payoutAccountId":{"type":"string","description":"The id to pass as `payoutAccountId` on POST /v1/orders/sell. Destinations are registered by OnLink out of band, never through this API, so this is a selection rather than an instruction.","format":"uuid","example":"dd9f1e6a-0000-4000-8000-000000000003"},"accountNumberLast4":{"type":"string","description":"The **last four digits only** of the destination account number. There is no route on this API that returns more, by design — you select a destination by id and never type its number.","example":"6789"},"accountName":{"type":"string","description":"The beneficiary name exactly as the receiving bank holds it. Returned whole so you can tell two destinations at the same bank apart.","example":"EXAMPLE TRADING LIMITED"},"bankCode":{"type":"string","description":"The receiving bank’s code. **`null` means an on-us destination at our own bank, not missing data** — do not render it as an error or a gap.","nullable":true,"example":null},"label":{"type":"string","description":"The label OnLink recorded for this destination, or `null`. Never used for routing.","nullable":true,"example":"Primary settlement"}},"required":["payoutAccountId","accountNumberLast4","accountName","bankCode","label"]},"MpesaPushRequestDto":{"type":"object","properties":{"phone":{"type":"string","description":"The payer mobile number to prompt, in any recognised Kenyan format — +254712345678, 254712345678, 0712345678 or 0112345678. Normalised server-side to the form the bank requires, so you can send the number as you hold it.","example":"254712345678"},"amount":{"type":"string","description":"Amount in WHOLE KES, as a decimal string, at most 150,000 — the M-PESA per-transaction limit. M-PESA does not process decimals on an inbound customer-to-business push, so \"100.50\" is refused rather than rounded, and a leading zero is refused rather than normalised.","example":"1000","pattern":"^[1-9]\\d{0,9}$"}},"required":["phone","amount"]},"MpesaPushResponseDto":{"type":"object","properties":{"pushId":{"type":"string","description":"An opaque handle for this push. Quote it to support if a prompt never arrived or a deposit never landed. There is no route that reads it, and it is NOT the transactionId on the credit.received event — that id belongs to a record created later, when the money actually clears.","example":"SBXTX0000001"},"status":{"type":"string","description":"Always pending. The payer has been prompted on their handset and nothing has moved. The completion signal is the credit.received webhook; a prompt the payer declines or ignores produces no event at all.","enum":["pending"],"example":"pending"}},"required":["pushId","status"]},"KesBalanceDto":{"type":"object","properties":{"available":{"type":"string","description":"Available balance, KES major units — a decimal STRING, never a number.","example":"150000.50"},"accountNumber":{"type":"string","description":"The KES account number this balance belongs to.","example":"46013001348902"},"currency":{"type":"string","description":"Always KES on this leg.","enum":["KES"],"example":"KES"},"asOf":{"type":"string","description":"ISO-8601 instant the balance was fetched. May be up to 30 seconds old — balances are cached for that long to reduce upstream calls.","example":"2026-09-08T12:00:00.000Z"}},"required":["available","accountNumber","currency","asOf"]},"BalancesResponseDto":{"type":"object","properties":{"kes":{"$ref":"#/components/schemas/KesBalanceDto"}},"required":["kes"]},"SwiftDestinationDto":{"type":"object","properties":{"bic":{"type":"string","description":"Beneficiary bank BIC/SWIFT code (8 or 11 characters).","example":"BARCKENXXXX"},"bankCity":{"type":"string","description":"Beneficiary bank city.","example":"Nairobi"},"bankCountry":{"type":"string","description":"Beneficiary bank country, spelled out in FULL — never an ISO code.","example":"Kenya"},"chargeType":{"type":"string","description":"Who bears the SWIFT charge. Defaults to OUR (you pay).","enum":["OUR","BEN","SHA"],"example":"OUR"},"accountCurrency":{"type":"string","description":"ISO currency of the beneficiary account. Defaults to KES.","example":"KES"}},"required":["bic","bankCity","bankCountry"]},"TransferDestinationDto":{"type":"object","properties":{"accountNumber":{"type":"string","description":"Beneficiary account number. For mpesa, a Kenyan mobile number (any recognised local or international format — normalised server-side); for the other rails, the beneficiary account number.","example":"254712345678"},"accountName":{"type":"string","description":"Beneficiary name as you believe it to be — verified server-side against the rail before the transfer is created.","example":"Jane Wanjiku"},"bankCode":{"type":"string","description":"Beneficiary bank code. Required for pesalink, rtgs, eft. Not allowed for mpesa, swift, internal.","example":"11"},"bankName":{"type":"string","description":"Beneficiary bank name. Required for rtgs, eft, swift. Not allowed for mpesa, pesalink, internal.","example":"Equity Bank"},"branchCode":{"type":"string","description":"Beneficiary bank branch code. Optional for rtgs, eft. Not allowed for mpesa, pesalink, swift, internal.","example":"001"},"swift":{"description":"Beneficiary bank SWIFT detail. Required for swift; not allowed on any other rail.","allOf":[{"$ref":"#/components/schemas/SwiftDestinationDto"}]}},"required":["accountNumber","accountName"]},"TransferAmountDto":{"type":"object","properties":{"currency":{"type":"string","description":"Send currency. Only KES is supported today.","enum":["KES"],"example":"KES"},"value":{"type":"string","description":"Decimal string, KES major units, up to 2 decimal places, GREATER than zero. Never a JSON number.","example":"1500.00"}},"required":["currency","value"]},"PartnerCreateTransferDto":{"type":"object","properties":{"rail":{"type":"string","description":"The rail to send on.","enum":["mpesa","pesalink","rtgs","eft","swift","internal"],"example":"mpesa"},"destination":{"description":"Where the money is going.","allOf":[{"$ref":"#/components/schemas/TransferDestinationDto"}]},"amount":{"description":"How much to send, in the send currency.","allOf":[{"$ref":"#/components/schemas/TransferAmountDto"}]},"partnerReference":{"type":"string","description":"Your own reference for this transfer, echoed back on every read.","example":"PS-TRF-000123"},"idempotencyKey":{"type":"string","description":"A key unique to your account. A retried create with the same key and the same request body returns the original transfer rather than creating a second one; the same key with a DIFFERENT request body is refused.","example":"trf-2026-09-11-000123"},"paymentPurposeId":{"type":"string","description":"Payment purpose code. Required for rtgs, eft, swift. Not allowed for mpesa, pesalink, internal.","example":"SALA"},"senderAddress":{"type":"string","description":"Your registered address. Required for rtgs, eft, swift. Not allowed for mpesa, pesalink, internal.","example":"1 Kenyatta Ave, Nairobi"},"remark":{"type":"string","description":"Optional free-text remark. Allowed on every rail.","example":"September rent"}},"required":["rail","destination","amount","idempotencyKey"]},"TransferDestinationEchoDto":{"type":"object","properties":{"accountNumber":{"type":"string","description":"The account number you supplied.","example":"254712345678"},"accountName":{"type":"string","description":"The account name you supplied.","example":"Jane Wanjiku"},"bankCode":{"type":"string","description":"The bank code you supplied, when the rail carries one.","example":"11"}},"required":["accountNumber","accountName"]},"ConfirmationDto":{"type":"object","properties":{"id":{"type":"string","description":"The confirmation id. POST the code your registered person receives back to `/v1/confirmations/:id` to authorise the transfer.","example":"df9f1e6a-0000-4000-8000-000000000030"},"channel":{"type":"string","description":"How the confirmation code was delivered.","enum":["email","sms"],"example":"email"},"deliveredTo":{"type":"string","description":"Masked delivery address or number. Never the raw contact.","example":"o***@partner.co.ke"},"expiresAt":{"type":"string","format":"date-time","description":"ISO-8601 instant the confirmation code expires.","example":"2026-09-11T00:15:00.000Z"},"resendAvailableAt":{"type":"string","format":"date-time","description":"ISO-8601 instant a resend becomes available.","example":"2026-09-11T00:00:15.000Z"}},"required":["id","channel","deliveredTo","expiresAt","resendAvailableAt"]},"PartnerTransferResponseDto":{"type":"object","properties":{"id":{"type":"string","description":"The transfer id.","example":"bd9f1e6a-0000-4000-8000-000000000010"},"status":{"type":"string","description":"The transfer status.","enum":["awaiting_confirmation","confirmed","executing","processing","settled","failed","expired","cancelled"],"example":"awaiting_confirmation"},"rail":{"type":"string","description":"The rail this transfer moves on.","enum":["mpesa","pesalink","rtgs","eft","swift","internal"],"example":"mpesa"},"amount":{"description":"The amount sent.","allOf":[{"$ref":"#/components/schemas/TransferAmountDto"}]},"fee":{"description":"Sender-borne fee, quoted at create time from the published fee sheet and never recomputed later. \"0.00\" for the internal rail, an unknown fee, or a SWIFT transfer where the beneficiary bears the charge.","allOf":[{"$ref":"#/components/schemas/TransferAmountDto"}]},"destination":{"description":"An echo of what you supplied — never the resolved bank name.","allOf":[{"$ref":"#/components/schemas/TransferDestinationEchoDto"}]},"nameVerification":{"type":"string","description":"Whether the beneficiary name could be verified against the rail. `unavailable` covers both \"this rail has no verification\" (rtgs, eft, swift) and a provider outage — see verifiedAccountName.","enum":["verified","unavailable"],"example":"verified"},"verifiedAccountName":{"type":"string","description":"The resolved beneficiary name, present only when nameVerification is verified.","example":"JANE WANJIKU"},"partnerReference":{"type":"string","description":"Your own reference, echoed back.","example":"PS-TRF-000123"},"failureCode":{"type":"string","description":"A classified failure code, present only once failed.","example":"PROVIDER_REJECTED"},"createdAt":{"type":"string","format":"date-time","description":"ISO-8601 instant the transfer was created.","example":"2026-09-11T00:00:00.000Z"},"confirmedAt":{"type":"string","format":"date-time","description":"ISO-8601 instant the confirmation code was verified.","example":"2026-09-11T00:02:00.000Z"},"settledAt":{"type":"string","format":"date-time","description":"ISO-8601 instant the transfer settled.","example":"2026-09-11T00:05:00.000Z"},"confirmation":{"description":"Present only while status is awaiting_confirmation.","allOf":[{"$ref":"#/components/schemas/ConfirmationDto"}]}},"required":["id","status","rail","amount","fee","destination","nameVerification","createdAt"]},"PartnerBatchItemDto":{"type":"object","properties":{"rail":{"type":"string","description":"The rail to send on.","enum":["mpesa","pesalink","rtgs","eft","swift","internal"],"example":"mpesa"},"destination":{"description":"Where the money is going.","allOf":[{"$ref":"#/components/schemas/TransferDestinationDto"}]},"amount":{"description":"How much to send, in the send currency.","allOf":[{"$ref":"#/components/schemas/TransferAmountDto"}]},"partnerReference":{"type":"string","description":"Your own reference for this transfer, echoed back on every read.","example":"PS-TRF-000123"},"idempotencyKey":{"type":"string","description":"A key unique to your account. A retried create with the same key and the same request body returns the original transfer rather than creating a second one; the same key with a DIFFERENT request body is refused.","example":"trf-2026-09-11-000123"},"paymentPurposeId":{"type":"string","description":"Payment purpose code. Required for rtgs, eft, swift. Not allowed for mpesa, pesalink, internal.","example":"SALA"},"senderAddress":{"type":"string","description":"Your registered address. Required for rtgs, eft, swift. Not allowed for mpesa, pesalink, internal.","example":"1 Kenyatta Ave, Nairobi"},"remark":{"type":"string","description":"Optional free-text remark. Allowed on every rail.","example":"September rent"}},"required":["rail","destination","amount","idempotencyKey"]},"PartnerCreateBatchDto":{"type":"object","properties":{"idempotencyKey":{"type":"string","description":"A key unique to your account, for the WHOLE batch. A retried create with the same key returns the original batch rather than submitting it twice. Each item ALSO carries its own idempotencyKey — that is what dedupes the individual payments.","example":"batch-2026-09-16-000001"},"items":{"description":"The transfers to submit, each shaped exactly like POST /v1/transfers. A batch is a loop over the single-transfer behaviour behind one confirmation — an item that fails validation is reported failed and the rest proceed.","type":"array","items":{"$ref":"#/components/schemas/PartnerBatchItemDto"}}},"required":["idempotencyKey","items"]},"PartnerBatchItemResultDto":{"type":"object","properties":{"transferId":{"type":"string","description":"The transfer id, present when the item was accepted. Read it with GET /v1/transfers/:id for its full detail.","example":"bd9f1e6a-0000-4000-8000-000000000010"},"idempotencyKey":{"type":"string","description":"Your own reference for this item — echoes the item's own idempotencyKey.","example":"trf-2026-09-16-000123"},"accepted":{"type":"boolean","description":"Whether this item was accepted into the batch.","example":true},"failureCode":{"type":"string","description":"Present only when accepted is false — one of the single-transfer refusal codes (TRANSFER_NAME_MISMATCH, TRANSFER_EXCEEDS_DAILY_CAP, …).","example":"TRANSFER_NAME_MISMATCH"}},"required":["idempotencyKey","accepted"]},"PartnerBatchResponseDto":{"type":"object","properties":{"id":{"type":"string","description":"The batch id.","example":"be9f1e6a-0000-4000-8000-000000000040"},"status":{"type":"string","description":"The batch status.","enum":["awaiting_confirmation","confirmed","processing","completed","failed","expired"],"example":"awaiting_confirmation"},"itemCount":{"type":"number","description":"Total items submitted, valid or not."},"acceptedCount":{"type":"number","description":"Items that reached a successful terminal state."},"failedCount":{"type":"number","description":"Items refused at submit or failed by the executor."},"items":{"description":"Every item's outcome, in the order submitted.","type":"array","items":{"$ref":"#/components/schemas/PartnerBatchItemResultDto"}},"createdAt":{"type":"string","format":"date-time","description":"ISO-8601 instant the batch was created.","example":"2026-09-16T00:00:00.000Z"},"confirmedAt":{"type":"string","format":"date-time","description":"ISO-8601 instant the confirmation code was verified.","example":"2026-09-16T00:02:00.000Z"},"confirmation":{"description":"Present only while status is awaiting_confirmation.","allOf":[{"$ref":"#/components/schemas/ConfirmationDto"}]}},"required":["id","status","itemCount","acceptedCount","failedCount","items","createdAt"]},"PartnerConfirmDto":{"type":"object","properties":{"code":{"type":"string","description":"The 6-digit code delivered to your registered confirmation contact.","example":"482913"}},"required":["code"]},"PartnerConfirmationResultDto":{"type":"object","properties":{"confirmationId":{"type":"string","description":"The confirmation id.","example":"df9f1e6a-0000-4000-8000-000000000030"},"subjectType":{"type":"string","description":"What this confirmation authorises.","enum":["transfer","usdt_send","batch"],"example":"transfer"},"subjectId":{"type":"string","description":"The id of the instruction this confirmation authorises.","example":"bd9f1e6a-0000-4000-8000-000000000010"},"status":{"type":"string","description":"The instruction's status after this call: 'confirmed' — execution has been handed off.","enum":["awaiting_confirmation","confirmed","executing","processing","settled","failed","expired","cancelled","awaiting_confirmation","confirmed","processing","settled","failed","expired","awaiting_confirmation","confirmed","processing","completed","failed","expired"],"example":"confirmed"},"verifiedAt":{"type":"string","format":"date-time","description":"ISO-8601 instant the code was verified.","example":"2026-09-11T00:02:00.000Z"}},"required":["confirmationId","subjectType","subjectId","status","verifiedAt"]},"PartnerResendResultDto":{"type":"object","properties":{"confirmationId":{"type":"string","description":"The confirmation id.","example":"df9f1e6a-0000-4000-8000-000000000030"},"expiresAt":{"type":"string","format":"date-time","description":"ISO-8601 instant the new code expires.","example":"2026-09-11T00:15:00.000Z"},"resendAvailableAt":{"type":"string","format":"date-time","description":"ISO-8601 instant a further resend becomes available.","example":"2026-09-11T00:00:30.000Z"}},"required":["confirmationId","expiresAt","resendAvailableAt"]},"PartnerCreateUsdtSendDto":{"type":"object","properties":{"destinationWalletId":{"type":"string","description":"The id of a registered, active destination wallet (see GET /v1/wallets).","example":"fa9f1e6a-0000-4000-8000-000000000050"},"usdtAmount":{"type":"string","description":"Decimal string, USDT, up to 6 decimal places, GREATER than zero. Never a JSON number.","example":"25.500000"},"partnerReference":{"type":"string","description":"Your own reference for this send, echoed back on every read.","example":"PS-SEND-000123"},"idempotencyKey":{"type":"string","description":"A key unique to your account. A retried create with the same key returns the original send rather than creating a second one.","example":"send-2026-09-16-000123"}},"required":["destinationWalletId","usdtAmount","idempotencyKey"]},"PartnerUsdtSendResponseDto":{"type":"object","properties":{"id":{"type":"string","description":"The send id.","example":"ed9f1e6a-0000-4000-8000-000000000040"},"status":{"type":"string","description":"The send status.","enum":["awaiting_confirmation","confirmed","processing","settled","failed","expired"],"example":"awaiting_confirmation"},"destinationWalletId":{"type":"string","description":"The destination wallet id you registered.","example":"fa9f1e6a-0000-4000-8000-000000000050"},"usdtAmount":{"type":"string","description":"The amount sent, in USDT.","example":"25.500000"},"partnerReference":{"type":"string","description":"Your own reference, echoed back.","example":"PS-SEND-000123"},"failureReason":{"type":"string","description":"A classified failure code, present only once failed.","example":"USDT_SEND_INSUFFICIENT_LEDGER_BALANCE"},"createdAt":{"type":"string","format":"date-time","description":"ISO-8601 instant the send was created.","example":"2026-09-16T00:00:00.000Z"},"confirmedAt":{"type":"string","format":"date-time","description":"ISO-8601 instant the confirmation code was verified.","example":"2026-09-16T00:02:00.000Z"},"confirmation":{"description":"Present only while status is awaiting_confirmation.","allOf":[{"$ref":"#/components/schemas/ConfirmationDto"}]}},"required":["id","status","destinationWalletId","usdtAmount","createdAt"]},"PartnerCreateCollectionDto":{"type":"object","properties":{"currency":{"type":"string","description":"Currency a contributor deposits in. Only USD is supported today.","enum":["USD"],"example":"USD"},"partnerReference":{"type":"string","description":"Your own reference for this collection (e.g. a community or campaign id). Stored for your audit trail; it is never used to look anything up — use the reference OnLink mints and returns for that.","example":"sunday-offering-2026-09"},"payerLabel":{"type":"string","description":"A human-readable label shown to your own operators (e.g. in an admin listing) — never sent to the contributor.","example":"St Mark's Sunday offering"},"expectedAmount":{"type":"string","description":"Advisory only — a mismatch against the real deposit is reported, never refused. Decimal string, USD major units, up to 2 decimal places.","example":"100.00"}},"required":["currency","partnerReference","payerLabel"]},"PartnerCollectionDepositInstructionsDto":{"type":"object","properties":{"beneficiaryName":{"type":"string","example":"Sandbox Business"},"bankName":{"type":"string","example":"Bank of Nowhere"},"routingNumber":{"type":"string","example":"021000021"},"accountNumber":{"type":"string","example":"****0350"},"rails":{"description":"Every inbound rail this ONE account accepts.","example":["ach_push","fednow","wire"],"type":"array","items":{"type":"string"}},"depositMessage":{"type":"string","description":"Present ONLY when this collection shares a deposit account with others (the default). We mint this — never composed by you — and your contributor MUST quote it in their transfer for the deposit to be attributed; omitting it risks a significant delay in processing or the funds being returned. Absent when this collection has a dedicated account, which needs no reference from the contributor.","example":"BRGBFRC25ZJZ4RAVXEZK"}},"required":["beneficiaryName","bankName","routingNumber","accountNumber","rails"]},"PartnerCollectionFeeDto":{"type":"object","properties":{"percentBps":{"type":"number","example":175},"flatMinor":{"type":"number","example":30},"capMinor":{"type":"number","description":"NULL means uncapped.","example":2000,"nullable":true},"currency":{"type":"string","example":"USD"}},"required":["percentBps","flatMinor","currency"]},"PartnerCollectionResponseDto":{"type":"object","properties":{"id":{"type":"string","example":"a1b2c3d4-0000-4000-8000-000000000001"},"reference":{"type":"string","example":"DEMO-7K3Q2M"},"status":{"type":"string","enum":["open","received","credited","refunded","expired"],"example":"open"},"currency":{"type":"string","example":"USD"},"depositInstructions":{"description":"Present when this collection is still open and its deposit instructions could be refreshed. Absent (never invented) when that read failed — see the field-by-field docblock above.","allOf":[{"$ref":"#/components/schemas/PartnerCollectionDepositInstructionsDto"}]},"fee":{"$ref":"#/components/schemas/PartnerCollectionFeeDto"},"expectedAmount":{"type":"string","example":"100.00"},"grossAmount":{"type":"string","example":"100.00"},"feeAmount":{"type":"string","example":"1.18"},"netAmount":{"type":"string","example":"98.82"},"createdAt":{"type":"string","example":"2026-09-22T17:38:46.915Z"}},"required":["id","reference","status","currency","fee","createdAt"]}}}}