{"openapi":"3.1.0","info":{"title":"nTZS Partner API","version":"1.0.0","summary":"Programmable Tanzanian shilling payments — collections, disbursements, wallets and settlement.","description":"The nTZS partner API. Every endpoint here authenticates with a partner API key.\n\n## Before you build\n\n**Start in test mode.** A test key (`ntzs_test_…`) simulates the full lifecycle with no money\nand no chain, and `POST /v1/testmode/advance` settles pending transactions on demand — so an\nintegration can be written, run, advanced and corrected in a loop without waiting on a rail.\nYour test key mirrors your live capabilities, so what works in the sandbox works in production.\n\n**Capabilities gate endpoints, not products.** Your account is granted a set of capabilities\n(`collections`, `disbursements`, `transfers`, `treasury`, `wallets`, `swap`, `ramp`, …) and an\nendpoint outside your grant answers 403 regardless of key validity.\n\n**Quotes are not optional where they exist.** Withdrawals, spend and ramp price themselves\nthrough a quote endpoint. Never recompute a fee client-side: request a quote, show the user the\nname and total it returns, then pass the `quoteId` back. Quotes expire.\n\n**Errors come in three shapes.** Most endpoints put the machine-readable code in `error`;\nidentity and KYC paths put a sentence there and the code in `code`; 401s carry no code at all.\nBranch on `code ?? error` and keep the HTTP status.\n\n**Retries.** Money-moving endpoints accept `Idempotency-Key`. Send one — a 502 can mean the\nrequest was taken, and retrying without a key can pay twice.","contact":{"name":"NEDA Labs","url":"https://www.ntzs.co.tz/developers"},"license":{"name":"Proprietary","url":"https://www.ntzs.co.tz/developers"}},"servers":[{"url":"https://www.ntzs.co.tz","description":"Production. Test keys hit the same host."}],"security":[{"apiKey":[]}],"tags":[{"name":"Users","description":"End-user records and their embedded wallets."},{"name":"Identity","description":"KYC verification and partner attestation."},{"name":"Collections","description":"Pull funds in from mobile money, card or bank."},{"name":"Transfers","description":"Move value between users and to external addresses."},{"name":"Disbursements","description":"Pay out to mobile money or a bank account."},{"name":"Spend","description":"Pay bills and merchant tills."},{"name":"Lookup","description":"Resolve who is behind a number, till or bill account before paying."},{"name":"Swap","description":"Convert between USDC and nTZS."},{"name":"Ramp","description":"Wallet-less settlement: USDC to and from mobile money."},{"name":"Test mode","description":"Drive the sandbox lifecycle deterministically."},{"name":"Platform","description":"Supply and integrity figures."}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Your partner API key as a bearer token. `ntzs_live_…` for production, `ntzs_test_…` for the sandbox."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"A unique key per logical operation. Replaying it returns the original outcome instead of moving money twice."}},"schemas":{"CodeError":{"type":"object","description":"The common shape: `error` carries the machine-readable code, `message` the human sentence.","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable code, e.g. `insufficient_balance`."},"message":{"type":"string"},"details":{"type":"object","additionalProperties":true}}},"SentenceError":{"type":"object","description":"Identity/KYC paths invert the convention: `error` is prose and `code` carries the machine-readable value. Branch on `code ?? error`.","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable sentence."},"code":{"type":"string","description":"Machine-readable code, e.g. `kyc_required`."}}},"BareError":{"type":"object","description":"Authentication failures carry no machine code at all — match on the 401 status.","required":["error"],"properties":{"error":{"type":"string"}}},"User":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"externalId":{"type":"string","description":"Your own system's identifier for this user."},"email":{"type":"string","format":"email"},"name":{"type":["string","null"]},"phone":{"type":["string","null"]},"kycStatus":{"type":"string","enum":["approved","pending_review","rejected","none"]},"walletAddress":{"type":["string","null"],"description":"Null while identity verification is still open — no wallet exists without a verified identity."},"balanceTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"balanceUsdc":{"type":"number"}}},"Deposit":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["submitted","pending","completed","failed","review"]},"amountTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"paymentMethod":{"type":"string","enum":["mobile_money","card","bank_transfer","lipa_namba"]},"instructions":{"type":"string","description":"What to tell the payer next."}}},"Transfer":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["completed","pending","failed"]},"txHash":{"type":["string","null"]},"amountTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"recipientAmountTzs":{"type":"integer","description":"Amount received, after the platform fee."},"feeAmountTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"feeTxHash":{"type":["string","null"]}}},"WithdrawalQuote":{"type":"object","properties":{"quoteId":{"type":["string","null"],"description":"Pass to POST /v1/withdrawals. Null when the balance is insufficient."},"expiresAt":{"type":"string","format":"date-time"},"receiveAmountTzs":{"type":"integer","description":"What the recipient gets."},"totalFeeTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"recipientName":{"type":["string","null"],"description":"Registered name. Null means unconfirmed — show the raw number, do not block."},"payoutRail":{"type":"string"}}},"Withdrawal":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["burned","paid","pending","failed","reverted"]},"receiveAmountTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"totalFeeTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"recipientName":{"type":["string","null"]},"payoutRail":{"type":"string"}}},"NameLookup":{"type":"object","description":"Fail-soft. A null `name` means no confirmation was available, never that the payment should be blocked.","properties":{"phone":{"type":"string"},"network":{"type":"string","enum":["vodacom","airtel","tigo","halotel","ttcl"]},"name":{"type":["string","null"]}}},"MerchantLookup":{"type":"object","description":"Fail-soft, as above. `reason` is present only when `name` is null.","properties":{"kind":{"type":"string","enum":["lipa","bill"]},"target":{"type":"string"},"name":{"type":["string","null"]},"reason":{"type":"string"}}},"Reconciliation":{"type":"object","description":"Platform-wide supply integrity, NOT your account statement. Compares on-chain totalSupply against the sum of every tracked wallet.","properties":{"onChainSupplyTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"dbTotalBalanceTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"difference":{"type":"integer"},"isReconciled":{"type":"boolean","description":"True when |difference| is under 1 TZS."},"walletsChecked":{"type":"integer"},"contractAddress":{"type":"string"},"chain":{"type":"string"}}}}},"paths":{"/api/v1/users":{"post":{"operationId":"createUser","tags":["Users"],"summary":"Create a user and provision a wallet","description":"Requires the `wallets` capability — end-user wallet issuance is granted per partner, and without it this answers 403. Idempotent on `externalId`: calling again returns the existing user rather than creating a second one. For a Tanzanian user, `nidaNumber` and `phone` are required — no wallet is ever issued without a verified identity, so a 202 with `kyc_pending_review` is a normal outcome, not a failure.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["externalId","email"],"properties":{"externalId":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":"string"},"country":{"type":"string","description":"ISO 3166-1 alpha-2. Defaults to TZ.","default":"TZ"},"nidaNumber":{"type":"string","description":"20-digit NIDA. Required for TZ."},"phone":{"type":"string","description":"The user's OWN mobile money line. Required for TZ.","examples":["255712345678"]}}}}}},"responses":{"200":{"description":"The user already existed and was resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"201":{"description":"Created and verified — wallet issued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"202":{"description":"Created, but identity verification is still open — no wallet yet. `code` is `kyc_pending_review` or `kyc_attestation_required`. Wait for the webhook rather than polling hard.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SentenceError"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"409":{"description":"This identity already backs another wallet on your platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SentenceError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/users/{id}":{"get":{"operationId":"getUser","tags":["Users"],"summary":"Read a user, their wallet and balances","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"404":{"description":"No such user under your partner account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SentenceError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/users/{id}/kyc":{"post":{"operationId":"submitKyc","tags":["Identity"],"summary":"Submit or re-submit identity details for a user","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nidaNumber":{"type":"string"},"phone":{"type":"string","description":"Tanzanian mobile number. `2557XXXXXXXX`, `07XXXXXXXX` and `+2557XXXXXXXX` are all accepted.","examples":["255712345678"]},"country":{"type":"string"}}}}}},"responses":{"200":{"description":"Verified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SentenceError"}}}},"202":{"description":"Verification is open — a review or a capture session is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SentenceError"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/users/{id}/kyc/attestation":{"post":{"operationId":"attestKyc","tags":["Identity"],"summary":"Attest a KYC outcome you performed yourself","description":"For partners operating under a reliance agreement: you verified the identity, you report the outcome, and the wallet is issued on the strength of it. Granted per partner — without the grant this answers 403 `kyc_reliance_not_granted`. An API key alone must never be able to manufacture a verified identity.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["approved","rejected"]},"nationalId":{"type":"string"},"fullName":{"type":"string"},"provider":{"type":"string","description":"Who performed the verification."}}}}}},"responses":{"200":{"description":"Recorded; the wallet is issued on an approval.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"409":{"description":"Already decided, or the identity conflicts with an existing case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SentenceError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/deposits":{"post":{"operationId":"createDeposit","tags":["Collections"],"summary":"Collect funds from a payer","description":"Minimum 500 TZS. The payer receives a prompt on their phone for `mobile_money`. A 502 `initiation_uncertain` means we could not confirm whether the prompt was delivered and the collection may still be taken — read the deposit back before retrying.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amountTzs"],"properties":{"userId":{"type":"string","format":"uuid","description":"Omit to collect into your treasury instead of a user wallet."},"amountTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal.","minimum":500},"paymentMethod":{"type":"string","enum":["mobile_money","card","bank_transfer","lipa_namba"],"default":"mobile_money"},"phoneNumber":{"type":"string","description":"Required for mobile_money. Note the field is `phoneNumber`, not `phone`.","examples":["255712345678"]}}}}}},"responses":{"201":{"description":"Collection initiated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deposit"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"502":{"description":"The provider rejected it, or delivery could not be confirmed. Read the deposit back.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SentenceError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/deposits/{id}":{"get":{"operationId":"getDeposit","tags":["Collections"],"summary":"Read a deposit and its current status","description":"Poll this for the status change, or take the webhook. While the deposit is open it echoes the payment instructions so you can re-show them.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The deposit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deposit"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/transfers":{"post":{"operationId":"createTransfer","tags":["Transfers"],"summary":"Move value between users or to an external address","description":"Provide exactly one of `toUserId` or `toAddress`, never both.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fromUserId","amountTzs"],"properties":{"fromUserId":{"type":"string","format":"uuid"},"toUserId":{"type":"string","format":"uuid"},"toAddress":{"type":"string","description":"An Ethereum address on Base."},"amountTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"token":{"type":"string","enum":["nTZS","USDC"],"default":"nTZS"},"metadata":{"type":"object","additionalProperties":true}}}}}},"responses":{"201":{"description":"Transfer executed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transfer"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/withdrawals/quote":{"post":{"operationId":"quoteWithdrawal","tags":["Disbursements"],"summary":"Price a payout before executing it","description":"Give exactly one destination: a phone, or a bank (`bankCode` + `accountNumber`). `amountTzs` is what the recipient receives, net. Show the returned `recipientName` and `totalFeeTzs` to the user, then pass `quoteId` to POST /v1/withdrawals. Never recompute the fee yourself.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId","amountTzs"],"properties":{"userId":{"type":"string","format":"uuid"},"amountTzs":{"type":"integer","description":"Net to the recipient. Minimum 5,000 TZS.","minimum":5000},"phoneNumber":{"type":"string","description":"Tanzanian mobile number. `2557XXXXXXXX`, `07XXXXXXXX` and `+2557XXXXXXXX` are all accepted.","examples":["255712345678"]},"bankCode":{"type":"string","examples":["CRDB"]},"accountNumber":{"type":"string"}}}}}},"responses":{"200":{"description":"A quote. `quoteId` is null when the balance is insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawalQuote"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/withdrawals":{"post":{"operationId":"createWithdrawal","tags":["Disbursements"],"summary":"Execute a payout against a quote","description":"`amountTzs` and the destination must match the quote exactly, or you get `quote_mismatch`. An expired quote returns 409 `quote_stale` — request a new one rather than retrying.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId","amountTzs"],"properties":{"userId":{"type":"string","format":"uuid"},"amountTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"phoneNumber":{"type":"string","description":"Tanzanian mobile number. `2557XXXXXXXX`, `07XXXXXXXX` and `+2557XXXXXXXX` are all accepted.","examples":["255712345678"]},"bankCode":{"type":"string"},"accountNumber":{"type":"string"},"quoteId":{"type":"string","description":"From POST /v1/withdrawals/quote."}}}}}},"responses":{"201":{"description":"Payout accepted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Withdrawal"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"409":{"description":"`quote_stale` — pricing moved. Request a new quote.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/withdrawals/{id}":{"get":{"operationId":"getWithdrawal","tags":["Disbursements"],"summary":"Read a payout and its current status","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The withdrawal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Withdrawal"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/spend/quote":{"post":{"operationId":"quoteSpend","tags":["Spend"],"summary":"Price a bill or till payment","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId","amountTzs"],"properties":{"userId":{"type":"string","format":"uuid"},"amountTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"kind":{"type":"string","enum":["lipa","bill"]},"payNumber":{"type":"string","description":"For kind=lipa."},"utilityCode":{"type":"string","description":"For kind=bill. See GET /v1/spend/billers."},"utilityRef":{"type":"string"}}}}}},"responses":{"200":{"description":"A quote, including the resolved merchant or biller name and the total.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/spend":{"post":{"operationId":"createSpend","tags":["Spend"],"summary":"Pay a bill or merchant till against a quote","description":"A quote is mandatory: without one you get `quote_required`. Persist the returned reference — it is what the biller recognises.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId","quoteId"],"properties":{"userId":{"type":"string","format":"uuid"},"quoteId":{"type":"string"},"network":{"type":"string","description":"A till's mobile network, when `till_network_required` asked for it."}}}}}},"responses":{"201":{"description":"Payment accepted.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"409":{"description":"`quote_stale` — request a new quote.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"502":{"description":"`till_network_required` — the burn was reverted and the balance restored. Retry with `network`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/spend/{id}":{"get":{"operationId":"getSpend","tags":["Spend"],"summary":"Read a spend and its current status","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The spend.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/spend/billers":{"get":{"operationId":"listBillers","tags":["Spend"],"summary":"The biller catalogue","description":"Each entry carries its reference label and validation rules. A fee applies to every payment — never render a biller as free.","responses":{"200":{"description":"Supported billers.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/lookup/recipient-name":{"post":{"operationId":"lookupRecipientName","tags":["Lookup"],"summary":"Resolve the registered name behind a mobile number","description":"For the \"Sending to: JOHN DOE\" line before a user confirms. Fail-soft: `name: null` means no confirmation was available — show the raw number and continue, never block. Rate limited to 30/min per partner and audited per call.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phoneNumber"],"properties":{"phoneNumber":{"type":"string","description":"Tanzanian mobile number. `2557XXXXXXXX`, `07XXXXXXXX` and `+2557XXXXXXXX` are all accepted.","examples":["255712345678"]}}}}}},"responses":{"200":{"description":"Resolved, or `name: null` when unconfirmed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NameLookup"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/lookup/merchant-name":{"post":{"operationId":"lookupMerchantName","tags":["Lookup"],"summary":"Resolve the trading name behind a till or bill account","description":"Fail-soft like the recipient lookup. Bill validation is amount-aware — send `amountTzs` when known. Upstream can take ~25s, so debounce and never put this on a keystroke. Rate limited to 60/min per partner.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["lipa","bill"]},"payNumber":{"type":"string"},"utilityCode":{"type":"string"},"utilityRef":{"type":"string"},"amountTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."}}}}}},"responses":{"200":{"description":"Resolved, or `name: null` with a `reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MerchantLookup"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/lookup/qr":{"post":{"operationId":"lookupQr","tags":["Lookup"],"summary":"Resolve a scanned TANQR payload to a destination","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["payload"],"properties":{"payload":{"type":"string","description":"The raw scanned QR string."}}}}}},"responses":{"200":{"description":"The destination the code resolves to.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/merchant/pay":{"post":{"operationId":"payMerchant","tags":["Spend"],"summary":"Pay a Biashara merchant from a wallet balance","description":"The balance tender at a Biashara checkout: your signed-in user pays the merchant from their own nTZS balance. Settlement is synchronous — the `201` is final. The merchant receives the full amount. A sale is a transfer between two holders: nothing is minted and no deposit is created.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["payerUserId","handle","amountTzs"],"properties":{"payerUserId":{"type":"string","format":"uuid","description":"Your user — you are vouching they are signed in."},"handle":{"type":"string","description":"The merchant's checkout handle."},"amountTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"linkId":{"type":"string","format":"uuid","description":"Optional. Validated against the merchant's payment link; must equal a fixed link's amount."},"payerName":{"type":"string","description":"Optional. Shown in the merchant's sales list."},"payerPhone":{"type":"string","description":"Tanzanian mobile number. `2557XXXXXXXX`, `07XXXXXXXX` and `+2557XXXXXXXX` are all accepted.","examples":["255712345678"]},"externalReference":{"type":"string","maxLength":128,"description":"Optional. Your own order id, opaque to nTZS. Echoed on this response, on every `GET /api/v1/biashara/collections` row and on the `deposit.completed` webhook. Not unique: a retried payment for one order is a second sale with the same reference."}}}}}},"responses":{"201":{"description":"Payment executed — synchronous and final.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"collectionId":{"type":"string","format":"uuid"},"transferId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["completed"]},"paymentMethod":{"type":"string","enum":["ntzs_balance"]},"txHash":{"type":"string"},"amountTzs":{"type":"integer","description":"Whole Tanzanian shillings. Never a decimal."},"merchant":{"type":"object","properties":{"handle":{"type":"string"},"businessName":{"type":"string"}}},"payerUserId":{"type":"string","format":"uuid"},"externalReference":{"type":["string","null"],"description":"Echoed verbatim; `null` when none was sent."},"livemode":{"type":"boolean"}}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/swap":{"post":{"operationId":"createSwap","tags":["Swap"],"summary":"Convert between USDC and nTZS","description":"Responds as a Server-Sent Events stream so you can show progress through each leg.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId","from","to","amount"],"properties":{"userId":{"type":"string","format":"uuid"},"from":{"type":"string","enum":["USDC","USDT","nTZS"]},"to":{"type":"string","enum":["USDC","nTZS"]},"amount":{"type":"number"},"fromChain":{"type":"string","enum":["base","bnb","eth"],"default":"base"}}}}}},"responses":{"200":{"description":"An SSE stream of progress events.","content":{"text/event-stream":{"schema":{"type":"string"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/swap/rate":{"get":{"operationId":"getSwapRate","tags":["Swap"],"summary":"Current USDC/nTZS rate","description":"Public — no API key required. Valid for roughly 30 seconds. Show this to the user before they confirm a swap; do not cache it past its validity. Throttled per source address (60/min by default): cache a quote for its `expiresAt` window rather than re-fetching per keystroke. `liquidityUnknown: true` means the solver balance behind `lowLiquidity` could not be read within the time budget of the quote; the price is unaffected.","security":[],"parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string","enum":["USDC","USDT","nTZS"]}},{"name":"to","in":"query","required":true,"schema":{"type":"string","enum":["USDC","nTZS"]},"description":"Must differ from `from`."},{"name":"amount","in":"query","required":true,"schema":{"type":"number","exclusiveMinimum":0}},{"name":"fromChain","in":"query","required":false,"schema":{"type":"string","enum":["base","bnb"],"default":"base"}},{"name":"toChain","in":"query","required":false,"schema":{"type":"string","enum":["base","bnb"],"default":"base"}}],"responses":{"200":{"description":"The live rate.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Missing parameters, a non-positive amount, an unsupported token pair, or an unknown chain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"404":{"description":"No active pair for this token combination.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"Pricing is temporarily unavailable — retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/ramp/quote":{"post":{"operationId":"quoteRamp","tags":["Ramp"],"summary":"Lock a settlement rate for 60 seconds","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["direction","amount"],"properties":{"direction":{"type":"string","enum":["onramp","offramp"]},"amount":{"type":"number"},"phoneNumber":{"type":"string","description":"Tanzanian mobile number. `2557XXXXXXXX`, `07XXXXXXXX` and `+2557XXXXXXXX` are all accepted.","examples":["255712345678"]}}}}}},"responses":{"200":{"description":"A quote valid for 60s.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/ramp/offramp":{"post":{"operationId":"createOfframp","tags":["Ramp"],"summary":"USDC to mobile money","description":"A 202 is success-in-flight, not a failure. Track it with GET /v1/ramp/{id} or the settlement webhooks.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quoteId","phoneNumber"],"properties":{"quoteId":{"type":"string"},"phoneNumber":{"type":"string","description":"Tanzanian mobile number. `2557XXXXXXXX`, `07XXXXXXXX` and `+2557XXXXXXXX` are all accepted.","examples":["255712345678"]}}}}}},"responses":{"202":{"description":"Accepted and in flight.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/ramp/onramp":{"post":{"operationId":"createOnramp","tags":["Ramp"],"summary":"Mobile money to USDC","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quoteId","phoneNumber"],"properties":{"quoteId":{"type":"string"},"phoneNumber":{"type":"string","description":"Tanzanian mobile number. `2557XXXXXXXX`, `07XXXXXXXX` and `+2557XXXXXXXX` are all accepted.","examples":["255712345678"]}}}}}},"responses":{"202":{"description":"Accepted and in flight.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/ramp/{id}":{"get":{"operationId":"getRampSettlement","tags":["Ramp"],"summary":"Read one settlement","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The settlement.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/ramp/settlements":{"get":{"operationId":"listRampSettlements","tags":["Ramp"],"summary":"List settlements","responses":{"200":{"description":"Settlements.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/ramp/balance":{"get":{"operationId":"getRampBalance","tags":["Ramp"],"summary":"Your USDC settlement float","responses":{"200":{"description":"The float.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/reconcile":{"get":{"operationId":"getReconciliation","tags":["Platform"],"summary":"Platform-wide supply integrity check","description":"NOT your account statement — this compares on-chain totalSupply against every tracked wallet on the platform. It reads each wallet from chain, so latency grows with `walletsChecked`: call it on a schedule, never in a request path.","responses":{"200":{"description":"The comparison.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reconciliation"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/supply":{"get":{"operationId":"getSupply","tags":["Platform"],"summary":"Total nTZS in circulation","responses":{"200":{"description":"Circulating supply.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/testmode":{"get":{"operationId":"getTestModeState","tags":["Test mode"],"summary":"Inspect the sandbox state for this key","responses":{"200":{"description":"Current simulated state.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/testmode/advance":{"post":{"operationId":"advanceTestMode","tags":["Test mode"],"summary":"Settle every pending simulated transaction now","description":"The reason the sandbox is worth building against: no waiting on a rail. Write code, run it, advance, observe the webhook, correct. Test keys only — a live key gets `test_mode_only`.","responses":{"200":{"description":"Everything pending was settled.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/testmode/reset":{"post":{"operationId":"resetTestMode","tags":["Test mode"],"summary":"Wipe every simulated user and transaction on this key","responses":{"200":{"description":"Sandbox reset.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}},"/api/v1/testmode/users/{id}/approve":{"post":{"operationId":"approveTestModeUser","tags":["Test mode"],"summary":"Clear a simulated manual KYC review","description":"Use a NIDA ending 0000 to force a review, then clear it here to exercise the pending-review branch.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Review cleared.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"401":{"description":"Missing, empty, or invalid API key. No machine-readable code is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BareError"}}}},"403":{"description":"Forbidden — a missing capability, unapproved KYB, a source IP outside your allowlist, or a frozen wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"429":{"description":"Rate limited. Respect `Retry-After` and back off exponentially.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}},"503":{"description":"A rail or dependency is unavailable. Nothing moved — retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeError"}}}}}}}}}