Environment
API Key *
Sandbox Key:
WL-SANDBOX-TEST-1234-5678-9012Demo Customer ID:
cmh8x9k5w01nla23ccabmclkq (KYC Approved)
API Base URLs
Create Customer with KYC
Create a new customer and automatically generate a KYC verification link. All customers must complete KYC before creating virtual accounts.
Request Body
POST /api/trustodi/v1/{clientId}/customersPOST /api/trustodi/v1/{clientId}/customers
Both paths resolve to the same handler. Use the X-API-Key header.
type (INDIVIDUAL | BUSINESS), email
phone,
address.streetLine1, address.city, address.subdivision, address.postalCode, address.countryCode,
accountPurpose, sourceOfFunds, expectedMonthlyPaymentsUSD,
governmentIssuedIdentification.{type, countryCode, number, frontImage}
— plus, for INDIVIDUAL: firstName, lastName, dateOfBirth, employmentStatus, mostRecentOccupation
— plus, for BUSINESS: businessName, registrationNumber, registrationDate, businessType, industry, website, description, taxIdentificationNumber.
referenceId, middleName, transliteratedFirstName/MiddleName/LastName, transliteratedAddress,
phoneCountryCode, ipAddress, capabilities, redirectUrl,
accountPurposeExplanation (required when accountPurpose=OTHER),
actingAsIntermediary, isDao (BUSINESS),
governmentIssuedIdentification.{backImage, issuanceDate, expirationDate},
supportingDocuments[].
type: INDIVIDUAL, BUSINESS ·
governmentIssuedIdentification.type: PASSPORT, NATIONAL_ID, DRIVERS_LICENSE, RESIDENCE_PERMIT ·
employmentStatus: EMPLOYED, HOMEMAKER, RETIRED, SELF_EMPLOYED, STUDENT, UNEMPLOYED ·
expectedMonthlyPaymentsUSD: UNDER_FIVE_THOUSAND, FIVE_THOUSAND_TO_TEN_THOUSAND, TEN_THOUSAND_TO_FIFTY_THOUSAND, FIFTY_THOUSAND_PLUS ·
sourceOfFunds: COMPANY_FUNDS, ECOMMERCE_RESELLER, GAMBLING_PROCEEDS, GIFTS, GOVERNMENT_BENEFITS, INHERITANCE, INVESTMENTS_LOANS, PENSION_RETIREMENT, SALARY, SALE_OF_ASSETS_REAL_ESTATE, SAVINGS, SOMEONE_ELSES_FUNDS ·
accountPurpose: 11 values (CHARITABLE_DONATIONS, ECOMMERCE_RETAIL_PAYMENTS, INVESTMENT_PURPOSES, OPERATING_A_COMPANY, OTHER, PAYMENTS_TO_FRIENDS_OR_FAMILY_ABROAD, PERSONAL_OR_LIVING_EXPENSES, PROTECT_WEALTH, PURCHASE_GOODS_AND_SERVICES, RECEIVE_PAYMENT_FOR_FREELANCING, RECEIVE_SALARY) ·
businessType: see contract module.
first_name / firstName) and normalises legacy enum values (LLC → LIMITED_LIABILITY_COMPANY). Errors are returned as { code: "TRUSTODI_*", message, field, details: [...] }. All dates are YYYY-MM-DD; phone is E.164; country codes are ISO 3166-1 alpha-2; document images are base64 data URIs.
redirectUrl / redirect_url field controls where your customer lands after finishing (or exiting) verification. Resolution precedence: per-request redirect_url → your partner-level default (set in Partner Dashboard → API Management → KYC/KYB Completion Redirect) → the TRUSTODI completion page. Set the dashboard default once so customers return to your own site without sending redirect_url on every call.
Get Customer
Retrieve detailed information about a specific customer by their ID.
Path Parameters
Update Customer
Update an existing customer's information.
Path Parameters
Request Body
PUT /api/trustodi/v1/{clientId}/customers/{customerId}PUT /api/trustodi/v1/{clientId}/customers/{customerId}
Both paths resolve to the same handler.
type or email. Any subset of the contract fields may be submitted. Field aliases (camelCase ↔ snake_case, legacy enum values) are accepted on input. Errors are returned as { code: "TRUSTODI_*", message, field, details: [...] }.
Delete Customer
Permanently delete a customer and their associated data. This action cannot be undone.
Path Parameters
Generate KYC Link
Generate a KYC verification link for a customer. The link can be sent to the customer to complete their identity verification.
Request Body
redirect_url on this call →
your partner-level default (set in Partner Dashboard → API Management → KYC/KYB Completion Redirect) →
the TRUSTODI completion page. Set your dashboard default once so customers return to your own site without
sending redirect_url on every request.
Create Customer (Document Sharing)
Create a customer by submitting KYC documents directly. This bypasses the KYC link flow and allows partners to submit customer data and documents programmatically.
redirect_url on this call →
your partner-level default (set in Partner Dashboard → API Management → KYC/KYB Completion
Redirect) → the TRUSTODI completion page. Set your default once in the dashboard so every
customer lands back on your own site without sending redirect_url on each request.
Personal Information
["USD","OTHER"] unlocks US rails but requires enhanced compliance checks and longer approval. ["OTHER"] for non-US only.Address
Government Issued ID
["USD","OTHER"] unlocks US rails but requires enhanced compliance checks and longer approval. ["OTHER"] for non-US only.Required
BUSINESS_FORMATIONBANK_STATEMENTOWNERSHIP_INFORMATIONOptional
BUSINESS_LICENSEOPERATING_LICENSEPROOF_OF_ADDRESSTAX_DOCUMENTMEMORANDUM_OF_ASSOCIATIONSHAREHOLDER_REGISTRYCERTIFICATE_OF_GOOD_STANDINGARTICLES_OF_INCORPORATIONOTHERGet Payment Corridors
Get all available payment corridors with supported currencies, countries, and fees. Use this to build dynamic currency selection in your application.
Query Parameters
corridors- Array of all available corridors with fees and railscurrencies.source- All source currencies availablecurrencies.destination- All destination currencies availabletotal_count- Total number of corridors returned
List Customers
Retrieve a paginated list of all customers under your partner account.
Query Parameters
Get Customer Accounts
Retrieve all accounts (virtual banks, virtual wallets, external banks, external wallets) for a specific customer.
Path Parameters
Get Customer Transactions
Retrieve all transactions for a specific customer.
Path Parameters
Get Customer Quotes
Retrieve all quotes created for a specific customer.
Path Parameters
Get Account
Retrieve details of a specific account by its ID.
Path Parameters
Update Account
Update an existing account's information.
Path Parameters
Request Body
Delete Account
Delete an existing account. This action cannot be undone.
Path Parameters
Create Virtual Bank Account
Create a virtual bank account for receiving fiat deposits. Customer must have completed KYC verification.
Request Body
Liquidation Wallet *
Incoming fiat deposits will be automatically converted to crypto and sent to this wallet.
GET /api/trustodi/v1/customers/{customer_id}/accounts (the wallet's accountId).
Get Virtual Bank Account
Retrieve details of a virtual bank account.
Path Parameters
Create Virtual Wallet
Create a virtual cryptocurrency wallet (deposit address) for a customer to receive crypto deposits.
Request Body
Get Virtual Wallet
Retrieve details of a virtual wallet.
Path Parameters
Create External Bank Account
Register an external bank account for receiving off-ramp payouts. This is the customer's own bank account where they will receive fiat withdrawals.
Request Body
Bank / Account Holder Address *
Bank networks reject placeholder addresses like "N/A" or "Default Address" — please enter a real address.Create External Wallet
Register an external crypto wallet for receiving on-ramp payouts. This is the customer's own wallet where they will receive crypto.
Request Body
Create Payment
Create a new payment. Uses the same flow as the webapp. Select payment type below to use the appropriate endpoint.
Payment Type
Payment Mode
Common Fields
Quote Details
-
-
-
-
On-ramp: Fiat to Crypto
Off-ramp: Crypto to Fiat
Get Payment Status
Retrieve the current status and details of a payment.
Path Parameters
Get Crypto Addresses
Generate crypto deposit addresses for receiving payments. Returns wallet addresses for supported cryptocurrencies and networks.
Request Body
List Payments
Retrieve a paginated list of all payments with optional status filtering.
Query Parameters
Cancel Payment
Cancel a pending payment. Only payments that have not been processed can be cancelled.
Path Parameters
List Transactions
Retrieve a paginated list of all transactions.
Query Parameters
Get Transaction
Retrieve details of a specific transaction.
Path Parameters
Preview Quote
Preview a quote without creating it. Useful for showing estimated costs to users.
Request Body
Create Quote
Create a quote that can be used for payment. Uses the same flow as the webapp. Quotes expire after 60 seconds. Requires a valid customer ID.
Request Body
Get Quote
Retrieve details of a specific quote by its ID.
Path Parameters
Refresh Quote
Refresh an expired quote to get updated rates.
Request Body
Webhook Portal
Manage endpoints, inspect deliveries, replay eventsTRUSTODI delivers real-time event notifications (customer KYC updates, payment status changes, account events) to webhook endpoints you register. Sandbox and production are fully isolated — each opens its own portal scoped to that environment.
Sandbox
TEST MODETest webhook delivery with mock events. No real customer data — safe to experiment.
Production
LIVEReceive real production events for your live customers. Configure signing secrets and retry policies.
Or manage endpoints over the API
All three endpoints share the same authentication as the rest of the white-label API and operate on the environment matched by your API key (sandbox vs production).
POST /api/trustodi/v1/webhooks— register a new endpointGET /api/trustodi/v1/webhooks— list your endpointsDELETE /api/trustodi/v1/webhooks/:webhookId— remove an endpoint
Event catalog
These are the events you can subscribe to today. Each event delivers the common envelope plus one entity object (see Sample payloads below).
customer.created— fires when a new customer is created under your partner account.customer.updated— fires when a customer record changes, including KYC outcomes, profile edits, or status changes.
account.created— fires when a new account (bank or wallet) is provisioned for one of your customers.account.updated— fires when an account record changes (status, balance, or metadata).
payment.created— fires when a new payment is created (onramp, offramp, or move-money).payment.updated— fires when a payment transitions to a new status (e.g. completed or failed).
Event families for crypto trading & invest (transaction.*), staking (staking.*), and external-wallet whitelisting (external_wallet.whitelist.*) are part of an integration that has not launched yet. They are listed here for reference only and are not available to subscribe to today. Don't build against them until they're announced as live.
Sample payloads
Every delivery wraps a common envelope around one entity object. The envelope fields are always present:
{
"event": "customer.created", // the event type
"partner_id": "AFF008", // your partner ID
"environment": "production", // "sandbox" or "production"
"timestamp": "2026-06-12T07:00:00.000Z"
// ...plus one entity object (see below)
}
These are the exact shapes you receive. Fields are omitted when they have no value, so treat every field except the "always present" ones as optional and code defensively. Identify records by the TRUSTODI id (id / account_id), never by any provider id.
customer.created — always present: id, type, kyc_status
{
"event": "customer.created",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T07:00:00.000Z",
"customer": {
"id": "CUST_1700000000000",
"external_id": "your-own-ref-123",
"name": "Jane Doe",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"type": "INDIVIDUAL",
"kyc_status": "NOT_STARTED",
"kyc_url": "https://app.trustodi.com/kyc/abc123"
}
}
customer.updated — fires on KYC status changes; carries updated_at
{
"event": "customer.updated",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T08:00:00.000Z",
"customer": {
"id": "CUST_1700000000000",
"external_id": "your-own-ref-123",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"country": "US",
"type": "INDIVIDUAL",
"kyc_status": "APPROVED",
"updated_at": "2026-06-12T08:00:00.000Z"
}
}
account.created (virtual bank) — always present: account_id, customer_id, account_type, currency
{
"event": "account.created",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T07:00:00.000Z",
"account": {
"account_id": "vba_1700000000000",
"customer_id": "CUST_1700000000000",
"account_type": "VIRTUAL_BANK",
"currency": "EUR",
"nickname": "EUR payouts",
"bank_name": "Example Bank S.A.",
"iban": "DE89370400440532013000",
"account_number": "0532013000",
"status": "ACTIVE",
"is_virtual": true
}
}
account.created (wallet) — wallets carry address + chain instead of bank fields
{
"event": "account.created",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T07:00:00.000Z",
"account": {
"account_id": "wlt_1700000000000",
"customer_id": "CUST_1700000000000",
"account_type": "VIRTUAL_WALLET",
"currency": "USDC",
"chain": "SOLANA",
"address": "7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCT4K2",
"status": "ACTIVE",
"is_virtual": true
}
}
account.updated — carries updated_at
{
"event": "account.updated",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T09:00:00.000Z",
"account": {
"account_id": "vba_1700000000000",
"customer_id": "CUST_1700000000000",
"account_type": "VIRTUAL_BANK",
"currency": "EUR",
"nickname": "EUR payouts (updated)",
"is_primary": true,
"updated_at": "2026-06-12T09:00:00.000Z"
}
}
payment.created — full source/destination breakdown; always present: id, reference, type, status
{
"event": "payment.created",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T10:00:00.000Z",
"payment": {
"id": "TRX-ABC123",
"reference": "TRX-ABC123",
"type": "onramp",
"status": "PENDING",
"source_currency": "USD",
"source_amount": 100,
"destination_currency": "USDC",
"destination_amount": 99.5,
"destination_account_id": "vba_1700000000000",
"destination_rail": "SOLANA",
"quote_id": "quote_abc123",
"total_fees": 0.5,
"exchange_rate": 1.0
}
}
payment.updated — fires on every status change; carries amount + currency (and failure_reason on failure)
{
"event": "payment.updated",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T10:05:00.000Z",
"payment": {
"id": "TRX-ABC123",
"reference": "TRX-ABC123",
"type": "onramp",
"status": "COMPLETED",
"amount": 100,
"currency": "USD"
}
}
Verifying deliveries
Every webhook delivery is signed. When you register an endpoint, the response includes a signing secret (whsec_…) shown only once — store it securely. Each request carries signature headers:
webhook-id— unique message IDwebhook-timestamp— Unix send time (reject deliveries that are too old to prevent replay)webhook-signature— the signature to compare against the secret
Verify the signature on every request before trusting it. Our delivery is built on the open standard webhook signature scheme, and the official verification libraries (available for most languages) check the headers for you in one call — pass the raw request body and your signing secret.
Integration notes
- KYC outcomes arrive as
customer.updated— there is no separatekyc.*event. Watch thekyc_statusfield on the customer object to track approval. - Correlate on our customer ID (e.g.
CUST_…), not the underlying provider's ID. The provider's ID can change and should not be used as your key. - Sandbox and production are fully isolated — each environment has its own endpoints and signing secrets, and every payload carries
environmentso you can route accordingly. - Delivery is at-least-once — failed deliveries are retried, so the same event may arrive more than once. Make your handlers idempotent (de-duplicate on the
webhook-idheader or the entity ID + status). - Some events cannot be fully exercised in sandbox. Deposit-driven events depend on real money movement, so in sandbox you can trigger
customer.*,account.created/updatedandpayment.createdby calling the API, but apayment.updatedthat reachesCOMPLETED— and bank details that only populate after the account is fully provisioned (e.g.iban) — require a real deposit and therefore appear in production. Build against the documented shapes above; the field set is identical across environments.
Register Webhook
Register a new webhook endpoint to receive event notifications.
Request Body
whsec_…). Store it securely — it can't be retrieved again — and use it to verify the signature on every delivery (see Verifying deliveries below).
List Webhooks
Retrieve a list of all registered webhooks.
Delete Webhook
Delete an existing webhook registration.
Path Parameters
Get Corridors
Retrieve a list of all available payment corridors. Corridors define the supported source and destination currency pairs for payments.
Invest: Get Swap Quote
Get a short-lived Invest swap quote. Invest is a crypto-to-crypto product only. The full list of supported assets is sourced live from the trading desk for your environment — call GET /api/partner/invest/config (or just open the asset pickers below, which load it automatically). It spans the major coins plus a wide range of altcoins; the exact set can vary by environment. Any request that includes a fiat leg (USD, EUR, GBP, AED, …) or a fiat-to-fiat pair is rejected with UNSUPPORTED_ASSET. For fiat conversions use the onramp / offramp / move-money endpoints instead.
invest/quote to get a single-use quote_id, indicative buy_amount, effective fee_bps and expires_at, then pass that quote_id to invest/execute. Pricing is a fixed desk fee of 12 bps plus your configured Invest markup; the effective total is returned as fee_bps (100 bps = 1.00%) with your portion broken out as partner_markup_bps. The customer_id must belong to you and be KYC-approved. This route is also available under /api/partner/v1/{clientId}/payments/invest/quote and (for partner-dashboard sessions) at POST /api/partner/customers/:customerId/invest/quote.
Request Body
quote_id- Single-use id passed to executebuy_amount- Amount ofbuy_assetthe customer receivesrate_display/fill_price- Human-readable rate and execution pricenotional_usd- USD notional of the tradefee_bps/fee_usd- Effective total fee (desk + your markup)partner_markup_bps- Your portion of the fee, in bpsexpires_at/ttl_seconds- Quote expiry timestamp and remaining seconds
Invest: Execute Swap
Execute an Invest swap using the quote_id returned by invest/quote. The quote is single-use and must not be expired.
quote_id below. destination is optional — leave it blank to settle into TRUSTODI custody. A successful execute emits the transaction.invest_swap.created webhook event; settlement progress is delivered through your configured webhook subscriptions. Also available at POST /api/partner/customers/:customerId/invest/execute.
Request Body
transaction_id/reference- The created Invest transaction idstatus- Initial status; progress is delivered via webhooksell_asset/buy_asset/sell_amount/buy_amountfee_bps/fee_usd/partner_markup_bps
Invest: Buy (Fiat → Crypto)
Submit a fiat → crypto buy to the TRUSTODI Invest desk in a single call (no separate quote/execute step). Settlement is processed by the desk; status updates are delivered via webhook.
customer_id, source_currency, source_amount, target_asset. Optional: withdraw_to_wallet, source_vbank_account_id. The customer_id must belong to you and be KYC-approved. Also available at POST /api/partner/customers/:customerId/invest/buy.
Request Body
{ success, data: { transaction_id, reference, type: "invest_buy", customer_id, ... }, poweredBy }
Invest: Sell (Crypto → Fiat)
Submit a crypto → fiat sell to the TRUSTODI Invest desk in a single call. Fiat proceeds settle to the customer's nominated bank account; status updates are delivered via webhook.
customer_id, asset, quantity, fiat_currency, destination_bank_account_id. The destination bank account must belong to the customer. Also available at POST /api/partner/customers/:customerId/invest/sell.
Request Body
{ success, data: { transaction_id, reference, type: "invest_sell", customer_id, ... }, poweredBy }
Invest: History
Retrieve your Invest transaction history across all of your customers. This single endpoint returns swap, buy and sell history together — each row carries a type of invest_swap, invest_buy or invest_sell.
partner_commission) and never exposes internal revenue. For a single customer, use GET /api/partner/customers/:customerId/invest/history.
history- Array of Invest transactions, newest firsttype-invest_swap|invest_buy|invest_sellsell_asset/buy_asset/sell_amount/buy_amountfee_usd/fee_bps/partner_markup_bps/partner_commissionnotional_usd/status/created_at
Staking: List Instruments
List the assets your customers can stake, each with the net APR, minimum stake, lockup and unbonding terms offered by the TRUSTODI Invest desk. No request body required.
GET /api/partner/invest/staking/instruments for partner-dashboard sessions.
{ success, data: [ { asset, net_apr, min_stake, lockup_days, unbond_days, reward_frequency } ], poweredBy }
Staking: Open Position
Open a staking position for a customer at the current net APR for the chosen asset. The staked quantity must be at or above the asset's minimum stake (see List Instruments).
customer_id, asset, quantity. The customer_id must belong to you and be KYC-approved. Also available at POST /api/partner/customers/:customerId/invest/staking/open.
Request Body
{ success, data: { position_id, status, asset, quantity, net_apr, unbond_days, opened_at, customer_id }, poweredBy }
Staking: List Positions
List the staking positions held by one of your customers, including accrued and paid rewards. The customer_id is passed as a query parameter.
GET /api/partner/customers/:customerId/invest/staking/positions for partner-dashboard sessions.
Query Parameters
{ success, data: [ { position_id, asset, quantity, net_apr, status, rewards_accrued, rewards_paid, unbond_days, opened_at } ], poweredBy }
Staking: Unstake
Request an unstake against an existing position. Omit quantity (or set it to the full staked amount) for a full exit; provide a smaller quantity for a partial unstake. The position enters its unbonding period before funds become available.
customer_id, position_id. Optional: quantity (partial unstake). Also available at POST /api/partner/customers/:customerId/invest/staking/unstake.
Request Body
{ success, data: { position_id, status, asset, unbond_days, customer_id, ... }, poweredBy }
Error Codes
TRUSTODI uses standard HTTP status codes alongside custom error codes for specific failure scenarios. Failed, refunded, or rejected payments include a failure_code, failure_reason, and failure_description in the response.
HTTP Status Codes
| Code | Description |
|---|---|
200 | Success |
201 | Created |
400 | Bad Request - Invalid parameters or missing required fields |
401 | Unauthorized - Invalid or missing API key |
403 | Forbidden - Access denied |
404 | Not Found |
429 | Too Many Requests - Rate limit exceeded |
500 | Internal Server Error |
Validation Error Codes
| Code | Description |
|---|---|
TRUSTODI_MISSING_REQUIRED_FIELD | A required field is missing from the request |
TRUSTODI_INVALID_FIELD | Field value is invalid or in wrong format |
TRUSTODI_INVALID_ENVIRONMENT | Invalid environment specified |
TRUSTODI_AGE_VALIDATION_FAILED | Individual customer age must be between 18 and 65 years old |
TRUSTODI_CONFIG_ERROR | Configuration error for partner |
TRUSTODI_CUSTOMER_CREATION_FAILED | Failed to create customer |
Authentication & Rate Limit Codes
| Code | Description |
|---|---|
TRUSTODI_AUTH_01 | Authentication failed - invalid or missing API key |
TRUSTODI_AUTH_02 | Invalid credentials |
TRUSTODI_KYC_01 | KYC verification required before this operation |
TRUSTODI_LIMIT_01 | Rate limit exceeded |
TRUSTODI_LIMIT_03 | External account deposit limit exceeded |
Payment Failure Codes
Returned in the failure_code field when a payment fails, is refunded, or is rejected.
| Code | Reason | Description |
|---|---|---|
TRUSTODI_PAY_01 | PAYMENT_FAILED | General payment failure |
TRUSTODI_PAY_02 | PAYMENT_CANCELLED | Payment was cancelled |
TRUSTODI_PAY_03 | FUNDING_RECEIVED_AFTER_QUOTE_EXPIRED | Funds were received after the quote expired |
TRUSTODI_PAY_04 | FUNDS_RETURNED_BY_RECEIVING_BANK | Funds returned by the receiving bank |
TRUSTODI_PAY_05 | RFI_NOT_ANSWERED_IN_TIME | Required information not provided in time |
Account Error Codes
| Code | Reason | Description |
|---|---|---|
TRUSTODI_ACCT_01 | ACCOUNT_CREATION_FAILED | Account creation failed |
TRUSTODI_ACCT_02 | INVALID_EXTERNAL_ACCOUNT_NUMBER | Invalid bank account number |
TRUSTODI_ACCT_03 | INVALID_EXTERNAL_ACCOUNT_HOLDER_NAME | Invalid account holder name |
TRUSTODI_ACCT_04 | INVALID_EXTERNAL_ACCOUNT_HOLDER_ADDRESS | Invalid account holder address |
TRUSTODI_ACCT_05 | EXTERNAL_ACCOUNT_HOLDER_ADDRESS_IS_PMB_OR_PO_BOX | Account holder address cannot be a PO Box or PMB |
Webhook Error Codes
| Code | Description |
|---|---|
TRUSTODI_HOOK_01 | Webhook delivery failed |