# Business Activities Source: https://docs.lumx.io/additional-information/company-types Business categories collected on the businessActivity field during KYB onboarding When you onboard a business customer, Lumx collects a `businessActivity` value that classifies the entity for AML risk profiling. It describes what the business does and the regulated category it falls under (VASP, payment institution, fund, marketplace, etc.). The value you pick drives the level of due diligence applied and may surface extra document requirements during verification. See [Identity Verification](/compliance/identity-verification) and [Prohibited business activities](/compliance/prohibited-activities) for the controls that follow. ## Accepted values `businessActivity` takes a single value from the list below. Any other value returns `400`. The groups are for reading only and aren't part of the API. > [accordion] Crypto and digital assets > > ```text theme={null} > VASP > LIQUIDITY_PROVIDER_OTC_DESK > CRYPTO_MSB > STABLECOIN_ISSUER > NON_CUSTODIAL_WALLET_PROVIDER > CUSTODIAN > CRYPTO_CARDS > DEFI_DAO_FOUNDATION > NFT_MARKETPLACE_TOKEN_PROJECT > P2P_MERCHANTS > ATM_KIOSK_OPERATOR > CRYPTO_MINER > GAMING_METAVERSE_P2E > ``` > [accordion] Payments and financial services > > ```text theme={null} > PAYMENT_INSTITUTION > PAYMENT_PROCESSOR_GATEWAY > FOREIGN_EXCHANGE_FX > NON_CRYPTO_MSB > BANKING_CORRESPONDENT > BANK_FINANCIAL_INSTITUTION > LOAN_OR_FINANCE_COMPANY > INSURANCE_COMPANY > ``` > [accordion] Funds, investment, and holding vehicles > > ```text theme={null} > INVESTMENT_FUND > INVESTMENT_ADVISOR_MANAGER > VENTURE_CAPITAL_PRIVATE_EQUITY > SOVEREIGN_WEALTH_FUND > HOLDING_SPV_FAMILY_OFFICE > TRUST > COMPANY_FORMATION_CSP > ``` > [accordion] Commerce and trade > > ```text theme={null} > ECOMMERCE_MARKETPLACE > IMPORTER_EXPORTER_TRADE > WHOLESALER > RETAILER > JEWELRY_RETAIL > COMMODITIES_OIL_AND_GAS > PRECIOUS_METALS_AND_STONES > ART_ANTIQUITIES_COLLECTIBLES > PAWN_SHOPS > TOBACCO_ALCOHOL > MARIJUANA_CANNABIS > ARMS_INDUSTRY > PHARMACEUTICAL_DRUG_DISTRIBUTION > ``` > [accordion] Industry and services > > ```text theme={null} > SOFTWARE_DEVELOPMENT > TRAVEL_AGENCY_TOURISM > REAL_ESTATE_LEASING > LOGISTICS_CUSTOMS_TRANSPORT > GENERAL_MANUFACTURING > PROFESSIONAL_MARKETING_SERVICES > PAYROLL_PEO_HR_SERVICES > MINING_AND_EXTRACTION > CONSTRUCTION_SKILLED_TRADE > MEDIA_ARTS_ENTERTAINMENT > ADULT_ENTERTAINMENT > GAMBLING_CASINO_BETTING > AGRICULTURE_FISHING_HUNTING > HEALTHCARE_SOCIAL_ASSISTANCE > EDUCATIONAL_SERVICES > ACCOMMODATION_FOOD_SERVICE > ``` > [accordion] Public sector and non-profit > > ```text theme={null} > CHARITY_NGO_NON_PROFIT > UTILITIES_PUBLIC_ADMIN > ``` > [accordion] Other > > ```text theme={null} > OTHER > ``` > [!NOTE] > > Several of the values above trigger Enhanced Due Diligence rather than rejection — see [High-risk business activities](/compliance/prohibited-activities#high-risk-business-activities). Activities listed under [Prohibited business activities](/compliance/prohibited-activities#prohibited-business-activities) are accepted at the schema level but will be rejected during compliance review. When in doubt, pick the value that best describes the business and let underwriting decide. ## Previous questionnaire Customers created before October 28, 2026 keep the previous questionnaire, where the business category goes in `companyType` instead of `businessActivity`. Keep sending `companyType` for those customers, with one of the values below. See [Onboarding fields](/additional-information/onboarding-fields#previous-questionnaire) for the rest of the previous questionnaire. > [accordion] Financial services and money movement > > | **Code** | **Description** | > | :- | :- | > | `BANK_US` | US-chartered bank. | > | `BANK_FOREIGN` | Bank chartered outside the United States. | > | `BANKING_CORRESPONDENT` | Correspondent banking relationship. | > | `BROKER_DEALER_OTC_DESK` | Broker-dealer or over-the-counter trading desk. | > | `FOREIGN_EXCHANGE_MARKET_FX` | FX market participant. | > | `INSURANCE_COMPANIES` | Insurance carrier or underwriter. | > | `INVESTMENT_ADVISOR_ASSET_MANAGER` | Registered investment advisor or asset manager. | > | `LOAN_OR_FINANCE_COMPANY` | Lender or non-bank finance company. | > | `NON_BANK_CUSTODIANS` | Non-bank custody provider. | > | `PAYMENT_GATEWAY` | Payment gateway operator. | > | `THIRD_PARTY_PAYMENT_PROCESSOR` | Third-party payment processor. | > | `U_S_NON_CRYPTO_MSB` | US-registered Money Services Business (non-crypto). | > | `FOREIGN_NON_CRYPTO_MSB` | Foreign Money Services Business (non-crypto). | > [accordion] Crypto and blockchain > > | **Code** | **Description** | > | :- | :- | > | `BLOCKCHAIN_SOFTWARE_COMPANY` | Builds blockchain or Web3 software. | > | `DEFI_EXCHANGE` | Decentralized exchange. | > | `MINER` | Cryptocurrency miner. | > | `NFT_MARKETPLACE` | NFT marketplace operator. | > | `TOKEN_PROJECT` | Token issuer or project treasury. | > | `U_S_CRYPTO_MSB` | US-registered crypto Money Services Business. | > | `FOREIGN_CRYPTO_MSB` | Foreign crypto Money Services Business. | > | `ATM_KIOSK_OPERATOR` | Operator of crypto or cash ATM kiosks. | > [accordion] Funds and investment vehicles > > | **Code** | **Description** | > | :- | :- | > | `COMMODITIES_FIRMS` | Commodities trading firm. | > | `HIGH_NET_WORTH_INDIVIDUALS` | HNW individual holding entity. | > | `PENSION_FUNDS` | Pension or retirement fund. | > | `POOLED_INVESTMENT_VEHICLE_FUND` | Pooled investment vehicle or fund. | > | `SPECIAL_PURPOSE_VEHICLE` | Special purpose vehicle (SPV). | > | `TRUST_PERSONAL` | Personal or family trust. | > | `TRUST_CORPORATE` | Corporate trust. | > | `WEALTH_HOLDING_VEHICLE` | Wealth holding entity. | > [accordion] Government and supranational > > | **Code** | **Description** | > | :- | :- | > | `GOVERNMENT_AGENCY_US` | US federal, state, or local government agency. | > | `GOVERNMENT_AGENCY_FOREIGN` | Foreign government agency. | > | `SUPRANATIONAL_BODIES` | Supranational organization (e.g., World Bank, IMF). | > | `CHARITY_NGO_NON_PROFIT` | Charity, NGO, or non-profit organization. | > [accordion] Commerce, industry, and services > > | **Code** | **Description** | > | :- | :- | > | `WHOLESALE_OF_DURABLE_GOODS` | Wholesaler of durable goods. | > | `WHOLESALE_OF_NON_DURABLE_GOODS` | Wholesaler of non-durable goods. | > | `RETAILER_OF_DURABLE_GOODS` | Retailer of durable goods. | > | `RETAILER_OF_NON_DURABLE_GOODS` | Retailer of non-durable goods. | > | `CONSTRUCTION_OR_SKILLED_TRADE_BUSINESSES` | Construction or skilled-trade business. | > | `PROFESSIONAL_SERVICES` | Professional services firm. | > | `OTHER` | Doesn't fit any of the categories above. Use sparingly. Compliance may follow up for details. | ## Related resources > [card] [Business verification](/guides/business-verification) > > Full KYB onboarding flow. > [card] [Prohibited activities](/compliance/prohibited-activities) > > Categories Lumx does not service. > [card] [Identity Verification](/compliance/identity-verification) > > KYC and KYB requirements per customer type. # Document Types Source: https://docs.lumx.io/additional-information/document-types Accepted document types for KYC and KYB verification uploads When uploading verification documents to Lumx, the `type` field must match one of the values below. The right document depends on the customer type (`INDIVIDUAL` vs `BUSINESS`) and the stage of verification. For the full flow, see [Identity Verification](/compliance/identity-verification), [Individual verification](/guides/individual-verification), and [Business verification](/guides/business-verification). ## Identity documents (individuals) Used for KYC. Most require a `side` field (`FRONT_SIDE` or `BACK_SIDE`). | **Code** | **Description** | **Side required** | | :- | :- | :- | | `ID_CARD` | National identity card or equivalent government-issued ID. | Yes | | `PASSPORT` | Passport biographic page. | Yes | | `DRIVERS_LICENSE` | Driver's license. | Yes | | `SELFIE` | Live photo of the individual for biometric matching. | No | ## Proof of address | **Code** | **Description** | | :- | :- | | `PROOF_OF_ADDRESS` | Generic proof-of-address document. | | `BANK_STATEMENT` | Recent bank statement (typically within the last 90 days). | | `DELIVERY_RECEIPT` | Postal or courier delivery receipt showing the customer's address. | ## Financial documents | **Code** | **Description** | | :- | :- | | `INCOME_TAX_RETURN` | Personal or corporate tax return. | | `SIGNED_BALANCE_SHEET` | Signed balance sheet for a business customer. | | `SIGNED_INCOME_STATEMENT` | Signed income statement (P\&L) for a business customer. | ## Corporate documents (businesses) Used for KYB. | **Code** | **Description** | | :- | :- | | `INCORPORATION_ARTICLES` | Articles of incorporation or equivalent constitutional document. | | `SHAREHOLDER_REGISTRY` | Registry listing shareholders and their ownership. | | `DIRECTORS_REGISTRY` | Registry listing directors and officers. | | `SIGNED_CORPORATE_STRUCTURE_CHART` | Signed chart showing the corporate structure and ownership tree. | | `POWER_OF_ATTORNEY` | Power of attorney granting representation authority. | | `REGULATED_ACTIVITY_DOCUMENT` | License or proof of regulated activity (required when `isRegulatedActivity: true`). | ## Limit request supporting documents Used as the supporting document of a [limit-increase request](/guides/limit-increase-requests). | **Code** | **Description** | | :- | :- | | `LIMIT_REQUEST_BANK_STATEMENT` | Bank statement justifying the requested limits. | | `LIMIT_REQUEST_TAX_RETURN` | Personal or corporate tax return justifying the requested limits. | | `LIMIT_REQUEST_FINANCIAL_STATEMENTS` | Financial statements of a business customer justifying the requested limits. | ## Other | **Code** | **Description** | | :- | :- | | `ADDITIONAL_INFORMATION` | Used to attach extra context requested via [RFI](/compliance/rfi). | | `OTHER` | Anything that doesn't fit a category above. Include context in the request. | ## Document status Once uploaded, each document moves through this lifecycle: | **Status** | **Meaning** | | :- | :- | | `NOT_SENT` | Required document hasn't been uploaded yet. | | `PENDING` | Document is queued for review. | | `APPROVED` | Document accepted. | | `TEMPORARY_REJECTION` | Document needs to be resubmitted. Reasons come back in the verification response. See [Request for Information](/compliance/rfi). | | `FINAL_REJECTION` | Document permanently rejected. The customer cannot proceed. | > [!NOTE] > > Document-level status uses `TEMPORARY_REJECTION` even though customer-level verification status was renamed to `RFI` in v2.11.0. The two fields are independent. > [!NOTE] > > Uploads are capped at 50 MB. Accepted file formats: JPG, PNG, and PDF. ## Related resources > [card] [Identity Verification](/compliance/identity-verification) > > KYC and KYB requirements per customer type. > [card] [Individual verification](/guides/individual-verification) > > Step-by-step KYC integration. > [card] [Business verification](/guides/business-verification) > > Step-by-step KYB integration. > [card] [Request for Information](/compliance/rfi) > > Resolve RFIs raised on documents during review. # Onboarding Fields Source: https://docs.lumx.io/additional-information/onboarding-fields Self-disclosure enums collected on customer creation and additional information requests When you onboard a customer or submit additional information, Lumx collects a set of self-disclosure fields that drive AML risk scoring and routing into Enhanced Due Diligence. This page lists the values each field accepts. Values outside these lists return `400`. For the full request schemas, see [Create a customer](/api-reference/customers/create-a-customer) and [Send additional information](/api-reference/customers/send-additional-information). For the broader onboarding flow, see [Individual verification](/guides/individual-verification) and [Business verification](/guides/business-verification). > [!NOTE] > > Customers created before October 28, 2026 answer the previous questionnaire, which uses different fields and values. See [Previous questionnaire](#previous-questionnaire). ## USD amounts `monthlyIncomeInUSD` (individuals and their associated parties) and `monthlyRevenueInUSD` (businesses) take a non-negative amount in USD as a string, with up to 18 integer digits and up to 2 decimal places. For example, `"8000"` or `"8000.50"`. ## Individuals ### Professional situation `professionalSituation`. Single value. The free-text `professionalOccupation` field accompanies it to capture the specific role (e.g., "Software Engineer"). | **Code** | **Description** | | :- | :- | | `EMPLOYEE` | Salaried employee. | | `SELF_EMPLOYED` | Self-employed or sole proprietor. | | `BUSINESS_OWNER` | Owns a business. | | `GOVERNMENT_OFFICIAL` | Government official. | | `INVESTOR` | Investor. | | `STUDENT` | Student. | | `RETIRED` | Retired. | | `HOMEMAKER` | Homemaker. | | `UNEMPLOYED` | Currently unemployed. | | `OTHER` | Doesn't fit the categories above. | ### Source of funds `sourceOfFunds`. The origin of the customer's funds. Single value. | **Code** | **Description** | | :- | :- | | `SALARY_EMPLOYMENT_INCOME` | Salary or employment income. | | `BUSINESS_INCOME_DIVIDENDS` | Business income or dividends. | | `SAVINGS_INVESTMENT_RETURNS` | Savings or investment returns. | | `PENSION_RETIREMENT` | Pension or retirement income. | | `SALE_OF_ASSETS_REAL_ESTATE` | Sale of assets or real estate. | | `INHERITANCE_GIFT` | Inheritance or gift. | | `LOAN` | Borrowed funds. | | `GOVERNMENT_BENEFITS` | Government benefits. | | `GAMBLING_LOTTERY_PROCEEDS` | Gambling or lottery proceeds. | | `ROYALTIES_INTELLECTUAL_PROPERTY` | Royalties or intellectual property income. | | `THIRD_PARTY_FUNDS` | Funds that belong to a third party. | | `OTHER` | Doesn't fit the categories above. | ### Transaction counterparties `transactionCounterparties`. Who the customer expects to send money to or receive it from. Array. | **Code** | **Description** | | :- | :- | | `SELF` | The customer's own accounts. | | `FAMILY` | Family members (e.g., remittances). | | `FRIENDS` | Personal acquaintances. | | `EMPLOYER` | The customer's employer. | | `MERCHANTS_BUSINESSES` | Merchants or businesses. | | `GOVERNMENT` | Government entities. | | `OTHER` | Doesn't fit the categories above. | ## Businesses ### Business activity `businessActivity`. What the business does. Single value. See [Business Activities](/additional-information/company-types) for the accepted values. ### Monthly transaction volume `monthlyTransactionVolumeInUSD`. The expected monthly volume, as a range in USD. Single value. | **Code** | **Range** | | :- | :- | | `UP_TO_100K` | Up to \$100K. | | `100K_TO_1M` | $100K to $1M. | | `1M_TO_5M` | $1M to $5M. | | `5M_TO_10M` | $5M to $10M. | | `10M_TO_20M` | $10M to $20M. | | `20M_TO_30M` | $20M to $30M. | | `OVER_30M` | Over \$30M. | ### Source of funds `sourceOfFunds`. The origin of the business's funds. Single value. | **Code** | **Description** | | :- | :- | | `OPERATING_SERVICE_REVENUE` | Operating or service revenue. | | `INVESTMENT_RETURNS_CAPITAL_GAINS` | Investment returns or capital gains. | | `ASSET_BUSINESS_SALE` | Sale of assets or of a business. | | `SHAREHOLDER_EQUITY_RAISE` | Shareholder contributions or equity raise. | | `LOAN` | Borrowed funds. | | `INTERCOMPANY_TRANSFER` | Transfer from a related company. | | `GRANT` | Government or institutional grant. | | `CRYPTOCURRENCY_DIGITAL_ASSETS` | Cryptocurrency or digital assets. | | `ROYALTIES_AFFILIATE_INCOME` | Royalties or affiliate income. | | `LEGAL_SETTLEMENT_SUCCESSION` | Legal settlement or succession. | | `GAMBLING_REVENUE` | Gambling revenue. | | `CHARITABLE_CONTRIBUTIONS` | Charitable contributions. | | `OTHER` | Doesn't fit the categories above. | > [!NOTE] > > When more than one source of funds applies, pick the primary one. Compliance may request supporting evidence regardless. See [Document Types](/additional-information/document-types). ### Transaction counterparties `transactionCounterparties`. Who the business expects to send money to or receive it from. Array. | **Code** | **Description** | | :- | :- | | `OWN_ACCOUNTS_INTERCOMPANY` | The business's own accounts or group companies. | | `SHAREHOLDERS_PARTNERS` | Shareholders or partners. | | `CUSTOMERS` | The business's clients or buyers. | | `MERCHANTS` | Merchants. | | `SUPPLIERS_VENDORS` | Suppliers or vendors. | | `EMPLOYEES` | Payroll, salaries, or reimbursements. | | `CONTRACTORS` | Independent contractors or freelancers. | | `FINANCIAL_INSTITUTIONS_VASP` | Financial institutions or virtual asset service providers. | | `INVESTMENT_COUNTERPARTIES` | Investment counterparties. | | `GOVERNMENT_TAX_AUTHORITIES` | Government or tax authorities. | | `AFFILIATED_COMPANIES` | Affiliated companies. | | `OTHER` | Doesn't fit the categories above. | ## Previous questionnaire Customers created before October 28, 2026 keep answering the previous questionnaire. Their additional information uses `jurisdictions` and `involvedActivities` and, for businesses, `companyType`, `annualRevenue`, `monthlyTransactionVolume`, and `complianceAndAML`, with the values below. A body that doesn't match the customer's questionnaire returns `400` with `VALIDATION_ERROR`. See the [changelog](/changelog#october-7-2026) for the full list of differences. > [accordion] jurisdictions > > Regions where transactions are expected to occur. Array. Submit every region that applies. This field is separate from the customer's country of residence or incorporation. > > | **Code** | **Description** | > | :- | :- | > | `BRA` | Brazil. | > | `USA` | United States. | > | `EU` | European Union. | > | `CANADA` | Canada. | > | `CHINA` | China. | > | `MEXICO` | Mexico. | > | `OTHERS` | Any other region. Use alongside the specific countries above when activity spans further. | > [accordion] involvedActivities > > Self-disclosure of involvement with high-risk activities. Array. Must include `NONE` if none apply. Disclosing any value other than `NONE` does not pre-approve the activity. > > | **Code** | **Description** | > | :- | :- | > | `NONE` | None of the activities below apply. | > | `ADULT_ENTERTAINMENT` | Adult-content businesses or services. | > | `DRUGS` | Drugs, controlled substances, or unlicensed pharmaceuticals. | > | `FIREARMS` | Weapons, ammunition, or related products. | > | `GAMBLING` | Gambling, betting, or games of chance. | > | `MARIJUANA` | Cannabis-related business. | > | `TUMBLING` | Crypto mixers, tumblers, or anonymity-enhancing services. | > [accordion] transactionCounterparties > > Who the customer expects to send money to or receive it from. Array. > > | **Code** | **Description** | > | :- | :- | > | `SELF` | Transfers between the customer's own accounts. | > | `MERCHANTS_SUPPLIERS` | Vendors, suppliers, or service providers. | > | `CUSTOMERS` | The customer's own clients or buyers. | > | `EMPLOYEES` | Payroll, salaries, or reimbursements. | > | `CONTRACTORS` | Independent contractors or freelancers. | > | `FRIENDS` | Personal acquaintances (individual customers only). | > | `FAMILY` | Family members (individual customers only, e.g. remittances). | > [accordion] sourceOfFunds > > The origin of the customer's funds. Single value. > > | **Code** | **Description** | **Typical use** | > | :- | :- | :- | > | `EMPLOYMENT` | Salary or wages. | Individuals. | > | `SAVINGS` | Accumulated savings. | Individuals. | > | `WINNINGS` | Lottery, gambling, or contest winnings. | Individuals. | > | `MARITAL` | Funds received through marriage (dowry, alimony, settlement). | Individuals. | > | `REAL_ESTATE` | Sale or rental of real property. | Individuals and businesses. | > | `TRUST` | Distribution from a trust. | Individuals. | > | `INVESTMENT` | Returns from investments. | Individuals and businesses. | > | `COMPANY` | Operating revenue. | Businesses. | > | `COMPANY_CAPITAL` | Capital contributions to the business. | Businesses. | > | `LOAN` | Borrowed funds. | Individuals and businesses. | > | `PRIVATE_CAPITAL` | Private equity or venture capital. | Businesses. | > | `GRANT` | Government or institutional grant. | Businesses, NGOs. | > | `OTHER` | Doesn't fit the categories above. Use sparingly. | — | > [accordion] professionalSituation (individuals) > > | **Code** | **Description** | > | :- | :- | > | `EMPLOYEE` | Salaried employee. | > | `SELF_EMPLOYED` | Self-employed or sole proprietor. | > | `UNEMPLOYED` | Currently unemployed. | > | `RETIRED` | Retired. | > | `OTHER` | Doesn't fit the categories above. | > [accordion] companyType (businesses) > > See [Previous questionnaire](/additional-information/company-types#previous-questionnaire) on the Business Activities page. ## Related resources > [card] [Business Activities](/additional-information/company-types) > > Accepted values for `businessActivity` on KYB. > [card] [Supported countries](/compliance/supported-countries) > > Jurisdiction tiers and EDD scope. > [card] [Prohibited activities](/compliance/prohibited-activities) > > Activity categories Lumx does not service. > [card] [Document Types](/additional-information/document-types) > > Supporting evidence Lumx may request during review. # Purpose Codes Source: https://docs.lumx.io/additional-information/purpose-codes Transaction purpose values accepted on on-ramp and off-ramp requests Every on-ramp and off-ramp request takes a `purpose` code declaring why funds are moving. Lumx uses the purpose for AML monitoring, regulatory reporting, and to match the transaction against the correct destination [holder relationship](/concepts/destinations#holder-relationships). ## Accepted values | **Code** | **When to use** | | :- | :- | | `PERSONAL_ACCOUNT` | Customer moving funds to or from an external account they own. Only valid with destinations whose `holder.relationship` is `SELF`. | | `INVESTMENT` | Capital allocated to securities, funds, or other investment vehicles. | | `REAL_ESTATE` | Purchase, sale, or rental payments related to real property. | | `TRADE_TRANSACTIONS` | Cross-border payments for goods or services: supplier invoices, import/export settlement. | | `TAX` | Tax remittance to a domestic or foreign tax authority. | | `LOAN` | Loan disbursement, repayment, or inter-company capital injection. | | `BILLS` | Routine bill payment: utilities, subscriptions, recurring services. | | `EXPENSES_REIMBURSEMENT` | Reimbursing employees or contractors for business expenses. | | `PROFESSIONAL_SERVICES` | Payments for consulting, legal, accounting, or other professional services. | > [!WARNING] > > `PERSONAL_ACCOUNT` only works with `SELF`-relationship destinations. Using it with any other relationship returns a validation error. ## Choosing the right code Pick based on the economic substance of the transaction, not the rail or destination type. Two heuristics: * If the customer and destination holder are the same legal entity, use `PERSONAL_ACCOUNT`. * Otherwise, pick the code that best describes the underlying commercial activity. When more than one applies, go with the more specific one: `TAX` over `BILLS`, `EXPENSES_REIMBURSEMENT` over `PROFESSIONAL_SERVICES`. Misclassifying purpose can trigger [RFI](/compliance/rfi) or transaction holds during compliance review. ## Related resources > [card] [Transactions](/concepts/transactions) > > Full transaction lifecycle and request schemas. > [card] [Destinations](/concepts/destinations) > > Holder relationships that pair with each purpose code. > [card] [Treasury management](/guides/use-cases/treasury-management) > > Worked example of purpose codes across an inter-company flow. > [card] [Request for Information](/compliance/rfi) > > What happens when purpose doesn't match the underlying activity. # Sender of Record Source: https://docs.lumx.io/additional-information/sender-of-record Who appears as the sender on the recipient's bank statement Every off-ramp lands on the recipient's bank statement under a sender name. On rails where Lumx supports named accounts, the sender shown is the customer: the legal name registered during KYC/KYB. On rails where named accounts aren't supported yet, the sender shown is either Lumx or our banking partner's name. | **Rail** | **Country/Region** | **Sender of record** | | :- | :- | :- | | PIX | 🇧🇷 Brazil | Lumx | | SPEI | 🇲🇽 Mexico | Customer | | ACH | 🇺🇸 United States | Customer | | FEDWIRE | 🇺🇸 United States | Customer | | SEPA | 🇪🇺 Europe | Customer | | FPS | 🇬🇧 United Kingdom | Customer | | SWIFT | 🌐 International | Customer | > [!NOTE] > > For the full list of supported rails and settlement times, see [Coverage](/get-started/coverage). # Payment rail cut-off times Source: https://docs.lumx.io/additional-information/sla-and-cutoffs Daily deadlines and settlement windows for each supported payment rail Use this page to set expectations with your users about how long each payment rail takes to settle. The timelines below are processing targets, not contractual guarantees. Actual delivery depends on banking partners and the receiving institution. For onboarding timelines (KYC/KYB review), see [Identity Verification](/compliance/identity-verification). For account activation timelines, see [Accounts](/concepts/accounts#activation-timelines). ## Cut-off times by rail Cut-off times define the daily deadline for processing a transaction on each rail. Requests submitted after the cut-off, on weekends, or on bank holidays are processed on the next business day in the rail's jurisdiction. | **Currency** | **Rail** | **Cut-off (local TZ)** | **Settlement** | **Operating days** | | :- | :- | :- | :- | :- | | BRL | PIX | None | Instant | 24/7/365 | | MXN | SPEI | None | Instant | 24/7/365 | | EUR | SEPA | None. Payments of €100k or more between 4:30 PM and 2:30 AM UK time route to SEPA Credit Transfer | Instant; SEPA Credit Transfer: same or next business day | Instant: 24/7/365; SEPA Credit Transfer: business days | | GBP | FPS | None | Instant, up to £1M per payment | 24/7/365 | | USD | ACH | 2:00 PM ET | 1–2 business days | US business days | | USD | FEDWIRE | 3:00 PM ET | Same business day (typically within hours) | US business days | | USD | SWIFT | 3:00 PM ET | 1–5 business days | Subject to intermediary bank hours | > [!NOTE] > > Lumx routes each SEPA payment to SEPA Instant or SEPA Credit Transfer automatically. You always send `rail: "SEPA"` and can't choose the scheme. > [!NOTE] > > All times are in the local timezone of the payment rail's operating region. Plan cross-border transactions with timezone differences in mind. An off-ramp triggered at 4:00 PM in São Paulo lands well after FEDWIRE's 3:00 PM ET cut-off. ## What can add to these timelines The targets above assume a clean path. A few things that can extend processing time: * Compliance review. Transactions flagged by Lumx's monitoring (RFI) are held pending review. See [Request for Information](/compliance/rfi). * Bank holidays. Each rail follows its own jurisdiction's calendar. See [Bank holidays](#bank-holidays) below. * Intermediary banks (SWIFT). International wires may pass through correspondent banks. Each one can add hours or days and may deduct fees outside Lumx's control. * Large transactions. Transfers above standard thresholds may trigger extra risk review, typically adding up to two business hours. * Receiving institution. The beneficiary's bank can hold funds for its own internal review. Lumx considers the off-ramp complete once funds leave our banking partner. ## Bank holidays Each rail follows its own jurisdiction's calendar. Transactions submitted on a recognized holiday are queued for the next business day. Lumx doesn't replicate the dates here, since they're maintained by each rail's central bank. | **Rail** | **Calendar** | | :- | :- | | ACH, FEDWIRE, SWIFT | US federal holidays ([Federal Reserve K.8](https://www.federalreserve.gov/aboutthefed/k8.htm)) | | SEPA Credit Transfer | TARGET2 closing days, published by the ECB | | SPEI | Días inhábiles bancarios, published by Banxico | | PIX | None. Runs 24/7/365. Brazilian holidays may still affect dispute windows. | | FPS | None. Runs 24/7/365. | ## Related resources > [card] [Coverage](/get-started/coverage) > > Supported currencies, rails, and countries. > [card] [Transactions](/concepts/transactions) > > Transaction lifecycle and statuses. > [card] [Webhooks](/developer/webhooks) > > Subscribe to status changes for transactions. > [card] [Request for Information](/compliance/rfi) > > Resolve RFIs that pause transactions. # Tax IDs by Country Source: https://docs.lumx.io/additional-information/tax-ids-by-country Identification numbers accepted in the taxId field for individual customers by country of residence The `taxId` value submitted when creating an individual customer must be the local tax identifier for the customer's country of residence. This page lists the identifiers Lumx accepts per country. > [!NOTE] > > `passport`, `national_id`, and `other` are valid fallback options for every country except the United States. Use the country-specific identifier when available. > [!NOTE] > > If your country isn't listed or you're unsure which identifier to submit, contact [compliance@lumx.io](mailto:compliance@lumx.io) before submitting the customer. ## Accepted identifiers | **Country** | **Code** | **Identification number** | | :- | :- | :- | | Albania | AL | `tin` — Tax Identification Number | | Andorra | AD | `other` — provide a description of the document | | Angola | AO | `nif` — Número de Identificação Fiscal | | Antigua and Barbuda | AG | `other` — provide a description of the document | | Argentina | AR | `cuil` — Código Único de Identificación Laboral; `cdi` — Código de Identificación; `dni` — Documento Nacional de Identidad | | Armenia | AM | `tin` — Tax Identification Number | | Australia | AU | `tfn` — Tax File Number | | Austria | AT | `si` — Social Insurance Number | | Azerbaijan | AZ | `voen` — State Taxpayer Identification Number | | Bahamas | BS | `other` — provide a description of the document | | Bahrain | BH | `cpr` — Central Population Registry Number | | Barbados | BB | `nrn` — National Registration Number | | Belgium | BE | `nrn` — National Register Number | | Belize | BZ | `other` — provide a description of the document | | Bosnia and Herzegovina | BA | `jmbg` — Unique Master Citizen Number | | Botswana | BW | `tin` — Tax Identification Number | | Brazil | BR | `cpf` — Cadastro de Pessoas Físicas | | Bulgaria | BG | `ucn` — Unified Civil Number | | Cambodia | KH | `tin` — Tax Identification Number | | Cameroon | CM | `nif` — Número de Identificação Fiscal | | Canada | CA | `sin` — Social Insurance Number | | Chile | CL | `rut` — Registro Único Tributario | | China | CN | `ricn` — Resident Identity Card Number | | Colombia | CO | `nit` — Número de Identificación Tributaria; `rut` — Registro Único Tributario | | Comoros | KM | `nif` — Número de Identificação Fiscal | | Costa Rica | CR | `tin` — Tax Identification Number | | Côte d'Ivoire | CI | `nif` — Número de Identificação Fiscal | | Croatia | HR | `oib` — Personal Identification Number | | Cyprus | CY | `tin` — Tax Identification Number | | Czech Republic | CZ | `rc` — Residence Code Number | | Denmark | DK | `cpr` — Central Person Register Number | | Dominica | DM | `tin` — Tax Identification Number | | Dominican Republic | DO | `tin` — Tax Identification Number | | El Salvador | SV | `nit` — Número de Identificación Tributaria | | Estonia | EE | `ik` — Individual Code | | Ethiopia | ET | `tin` — Tax Identification Number | | Fiji | FJ | `tin` — Tax Identification Number | | Finland | FI | `hetu` — Finnish Personal Identity Code | | France | FR | `spi` — Social Security Number | | Georgia | GE | `tin` — Tax Identification Number | | Germany | DE | `steuer_id` — Steueridentifikationsnummer | | Ghana | GH | `tin` — Tax Identification Number | | Greece | GR | `aom` — Αριθμός Μητρώου (Social Security Number) | | Guatemala | GT | `nit` — Número de Identificación Tributaria | | Haiti | HT | `nif` — Número de Identificação Fiscal | | Honduras | HN | `rtn` — Registro Tributario Nacional | | Hungary | HU | `tin` — Tax Identification Number | | Iceland | IS | `tin` — Tax Identification Number | | India | IN | `pan` — Permanent Account Number | | Indonesia | ID | `npwp` — Nomor Pokok Wajib Pajak | | Iraq | IQ | `tin` — Tax Identification Number | | Ireland | IE | `ppsn` — Personal Public Service Number | | Israel | IL | `tin` — Tax Identification Number | | Italy | IT | `cf` — Codice Fiscale | | Jamaica | JM | `trn` — Taxpayer Registration Number | | Japan | JP | `mn` — My Number (Individual Number) | | Jordan | JO | `tin` — Tax Identification Number | | Kazakhstan | KZ | `iin` — Individual Identification Number | | Kenya | KE | `pin` — Personal Identification Number | | Kuwait | KW | `tin` — Tax Identification Number | | Kyrgyzstan | KG | `inn` — Individual Taxpayer Number | | Laos | LA | `tin` — Tax Identification Number | | Latvia | LV | `pk` — Person's Code | | Liberia | LR | `tin` — Tax Identification Number | | Lithuania | LT | `ak` — Personal Code | | Luxembourg | LU | `matricule` — Matricule Number (Social Security Number) | | Madagascar | MG | `nif` — Número de Identificação Fiscal | | Malawi | MW | `tin` — Tax Identification Number | | Malaysia | MY | `itr` — Income Tax Reference Number | | Malta | MT | `tin` — Tax Identification Number | | Marshall Islands | MH | `other` — provide a description of the document | | Mauritania | MR | `nif` — Número de Identificação Fiscal | | Mauritius | MU | `nicn` — National Identity Card Number | | Mexico | MX | `rfc` — Registro Federal de Contribuyentes; `curp` — Clave Única de Registro de Población; `ine` — Instituto Nacional Electoral | | Moldova | MD | `idnp` — Identification Number of the Person | | Monaco | MC | `other` — provide a description of the document | | Montenegro | ME | `jmbg` — Unique Master Citizen Number | | Mozambique | MZ | `nuit` — Número Único de Identificação Tributária | | Namibia | NA | `tin` — Tax Identification Number | | Netherlands | NL | `bsn` — Burgerservicenummer (Citizen Service Number) | | New Zealand | NZ | `ird` — Inland Revenue Department Number | | Nicaragua | NI | `ruc` — Registro Único de Contribuyentes | | Nigeria | NG | `tin` — Tax Identification Number; `nin` — National Identification Number; `bvn` — Bank Verification Number | | Norway | NO | `fn` — Fødselsnummer (Personal Identification Number) | | Oman | OM | `tin` — Tax Identification Number | | Pakistan | PK | `ntn` — National Tax Number | | Panama | PA | `ruc` — Registro Único de Contribuyentes | | Paraguay | PY | `ruc` — Registro Único de Contribuyentes | | Peru | PE | `ruc` — Registro Único de Contribuyentes | | Philippines | PH | `tin` — Tax Identification Number | | Poland | PL | `pesel` — Personal Identification Number | | Portugal | PT | `nif` — Número de Identificação Fiscal | | Qatar | QA | `qid` — Qatar ID | | Romania | RO | `cnp` — Cod Numeric Personal | | Rwanda | RW | `tin` — Tax Identification Number | | Saint Kitts and Nevis | KN | `other` — provide a description of the document | | Saint Lucia | LC | `tin` — Tax Identification Number | | Saint Vincent and the Grenadines | VC | `tin` — Tax Identification Number | | Samoa | WS | `other` — provide a description of the document | | Saudi Arabia | SA | `tin` — Tax Identification Number; `rp` — Iqama (Residency Permit) | | Senegal | SN | `tin` — Tax Identification Number | | Serbia | RS | `jmbg` — Unique Master Citizen Number | | Seychelles | SC | `tin` — Tax Identification Number | | Singapore | SG | `nric` — National Registration Identity Card; `fin` — Foreign Identification Number | | Slovakia | SK | `rc` — Rodné Číslo (Personal Identification Number) | | Slovenia | SI | `tin` — Tax Identification Number | | Somalia | SO | `tin` — Tax Identification Number | | South Africa | ZA | `itr` — Income Tax Reference Number | | South Korea | KR | `rrn` — Resident Registration Number | | Spain | ES | `nif` — Número de Identificación Fiscal; `nie` — Número de Identificación de Extranjeros | | Sri Lanka | LK | `nic` — National Identity Card Number | | Suriname | SR | `tin` — Tax Identification Number | | Sweden | SE | `tin` — Tax Identification Number | | Switzerland | CH | `avs` — AHV Number; `ahv` — Old Age and Survivors Insurance Number | | Tanzania | TZ | `tin` — Tax Identification Number | | Thailand | TH | `tin` — Tax Identification Number | | Togo | TG | `nif` — Número de Identificação Fiscal | | Trinidad and Tobago | TT | `bir` — Business Identification Number | | Tunisia | TN | `mf` — Matricule Fiscal | | Turkey | TR | `tckn` — Turkish Citizenship Number | | Uganda | UG | `tin` — Tax Identification Number | | Ukraine | UA | `rnokpp` — Registration Number of the Taxpayer | | United Arab Emirates | AE | `emirates_id` — National Identity Card | | United Kingdom | GB | `nino` — National Insurance Number; `utr` — Unique Taxpayer Reference Number | | United States | US | `ssn` — Social Security Number; `itin` — Individual Taxpayer Identification Number | | Uruguay | UY | `rut` — Registro Único Tributario; `ci` — Cédula de Identidad | | Uzbekistan | UZ | `inn` — Individual Identification Number | | Vanuatu | VU | `other` — provide a description of the document | | Vietnam | VN | `mst` — Mã Số Thuế (Tax Code) | | Yemen | YE | `tin` — Tax Identification Number | | Zambia | ZM | `tpin` — Taxpayer Identification Number | | Zimbabwe | ZW | `tin` — Tax Identification Number | ## Related resources > [card] [Identity Verification](/compliance/identity-verification) > > Data requirements and verification statuses per customer type. > [card] [Supported countries](/compliance/supported-countries) > > Jurisdiction tiers and EDD scope. > [card] [Onboarding Fields](/additional-information/onboarding-fields) > > Self-disclosure enums collected at onboarding. > [card] [Document Types](/additional-information/document-types) > > Document types accepted on customer uploads. # Read all accounts Source: https://docs.lumx.io/api-reference/accounts/read-all-accounts /openapi/api-production.yaml get /accounts This endpoint reads all accounts. # Create an autoconversion rule Source: https://docs.lumx.io/api-reference/autoconversion-rules/create-an-autoconversion-rule /openapi/api-production.yaml post /autoconversion-rules This endpoint creates an autoconversion rule. Every fiat deposit matched by the rule is automatically converted to the target asset and delivered on-chain. # Read all autoconversion rules Source: https://docs.lumx.io/api-reference/autoconversion-rules/read-all-autoconversion-rules /openapi/api-production.yaml get /autoconversion-rules This endpoint reads all autoconversion rules, newest first. # Read an autoconversion rule Source: https://docs.lumx.io/api-reference/autoconversion-rules/read-an-autoconversion-rule /openapi/api-production.yaml get /autoconversion-rules/{id} This endpoint reads an autoconversion rule, including its deposit details. # Simulate a deposit Source: https://docs.lumx.io/api-reference/autoconversion-rules/simulate-a-deposit /openapi/api-production.yaml post /autoconversion-rules/simulate-deposit This endpoint simulates a deposit to test autoconversion rules end-to-end. Sandbox only — returns 404 in production. # Create a customer Source: https://docs.lumx.io/api-reference/customers/create-a-customer /openapi/api-production.yaml post /customers This endpoint creates a customer. # Create an associated party Source: https://docs.lumx.io/api-reference/customers/create-an-associated-party /openapi/api-production.yaml post /customers/{id}/associated-parties This endpoint creates an associated party (UBO, shareholder, or representative) for a business customer. Individual associated parties of customers created on or after October 28, 2026 require `monthlyIncomeInUSD`. Business associated parties, and associated parties of customers created before October 28, 2026, keep the previous body, where `monthlyIncomeInUSD` is not accepted and returns `400`. # Create TOS acceptance link Source: https://docs.lumx.io/api-reference/customers/create-tos-acceptance-link /openapi/api-production.yaml post /customers/{id}/tos This endpoint generates a terms of service acceptance URL for a customer. # Read a customer Source: https://docs.lumx.io/api-reference/customers/read-a-customer /openapi/api-production.yaml get /customers/{id} This endpoint reads a customer. # Read a limit request Source: https://docs.lumx.io/api-reference/customers/read-a-limit-request /openapi/api-production.yaml get /customers/{id}/limit-requests/{limitRequestId} This endpoint reads a single limit-increase request for a customer. # Read a verification Source: https://docs.lumx.io/api-reference/customers/read-a-verification /openapi/api-production.yaml get /customers/{id}/verifications/{verificationId} This endpoint reads a customer's verification process. # Read all associated parties Source: https://docs.lumx.io/api-reference/customers/read-all-associated-parties /openapi/api-production.yaml get /customers/{id}/associated-parties This endpoint reads all associated parties of a business customer. # Read all customers Source: https://docs.lumx.io/api-reference/customers/read-all-customers /openapi/api-production.yaml get /customers This endpoint reads all customers. # Read all limit requests Source: https://docs.lumx.io/api-reference/customers/read-all-limit-requests /openapi/api-production.yaml get /customers/{id}/limit-requests This endpoint reads all limit-increase requests for a customer. # Read an associated party Source: https://docs.lumx.io/api-reference/customers/read-an-associated-party /openapi/api-production.yaml get /customers/{id}/associated-parties/{associatedPartyId} This endpoint reads an associated party of a business customer. # Request a limit increase Source: https://docs.lumx.io/api-reference/customers/request-a-limit-increase /openapi/api-production.yaml post /customers/{id}/limit-requests This endpoint creates a limit-increase request for a customer. Upload the supporting document first with POST /customers/{id}/documents and pass the returned documentId as supportingDocumentId. Each request raises the limits of one transaction type (`type`), and approval changes only that type's limits. A customer can have one pending request per type; creating a second pending request for the same type returns `409` with code `LIMIT_REQUEST_ALREADY_PENDING`. # Send additional information Source: https://docs.lumx.io/api-reference/customers/send-additional-information /openapi/api-production.yaml patch /customers/{id}/additional-information This endpoint updates a customer with additional information required for KYC/KYB verification. Customers created on or after October 28, 2026 answer the current questionnaire. Customers created before October 28, 2026 keep answering the previous questionnaire, whose body is unchanged. Sending the body that does not match the customer's questionnaire returns `400` with code `VALIDATION_ERROR`. # Start a verification Source: https://docs.lumx.io/api-reference/customers/start-a-verification /openapi/api-production.yaml post /customers/{id}/verifications This endpoint starts a customer's verification process. For BUSINESS customers, ensure all documents and associated parties are uploaded before starting verification. For INDIVIDUAL customers, verification starts automatically after liveness check. # Upload a document Source: https://docs.lumx.io/api-reference/customers/upload-a-document /openapi/api-production.yaml post /customers/{id}/documents This endpoint uploads a document for a customer's verification, or a supporting document for a limit-increase request. For the latter, use the returned documentId as supportingDocumentId when creating the limit request. # Create a destination Source: https://docs.lumx.io/api-reference/destinations/create-a-destination /openapi/api-production.yaml post /destinations This endpoint creates a destination. # Read a destination Source: https://docs.lumx.io/api-reference/destinations/read-a-destination /openapi/api-production.yaml get /destinations/{id} This endpoint reads a destination. # Read all destinations Source: https://docs.lumx.io/api-reference/destinations/read-all-destinations /openapi/api-production.yaml get /destinations This endpoint reads all destinations. # Get an exchange rate Source: https://docs.lumx.io/api-reference/exchange-rates/get-an-exchange-rate /openapi/api-production.yaml post /exchange-rates This endpoint returns an exchange rate quote between two currencies. # Create a partner fee Source: https://docs.lumx.io/api-reference/partner-fees/create-a-partner-fee /openapi/api-production.yaml post /partner-fees This endpoint creates a partner fee. # Read a partner fee Source: https://docs.lumx.io/api-reference/partner-fees/read-a-partner-fee /openapi/api-production.yaml get /partner-fees/{id} This endpoint reads a partner fee. # Read all partner fees Source: https://docs.lumx.io/api-reference/partner-fees/read-all-partner-fees /openapi/api-production.yaml get /partner-fees This endpoint reads all partner fees. # Off-ramp Source: https://docs.lumx.io/api-reference/transactions/off-ramp /openapi/api-production.yaml post /transactions/off-ramp This endpoint converts stablecoins to fiat. # On-ramp Source: https://docs.lumx.io/api-reference/transactions/on-ramp /openapi/api-production.yaml post /transactions/on-ramp This endpoint converts fiat to stablecoin. # Read a transaction Source: https://docs.lumx.io/api-reference/transactions/read-a-transaction /openapi/api-production.yaml get /transactions/{id} This endpoint reads a transaction. # Read all transactions Source: https://docs.lumx.io/api-reference/transactions/read-all-transactions /openapi/api-production.yaml get /transactions This endpoint reads all transactions. # Transfer Source: https://docs.lumx.io/api-reference/transactions/transfer /openapi/api-production.yaml post /transactions/transfer This endpoint starts a transfer of funds between customers or external wallets. # Changelog Source: https://docs.lumx.io/changelog Stay up to date with the latest updates and improvements to Lumx **October 21, 2026** v2.23.0 ### SPEI destinations require holder details > [!WARNING] > > This is a breaking change and it's already live. SPEI destination requests without these fields are rejected. **Enhancement:** 1. **Required holder fields for SPEI destinations** [Create a destination](/api-reference/destinations/create-a-destination) with `rail: "SPEI"` now requires a `holder` with `type` (`BUSINESS` or `INDIVIDUAL`), `taxId`, and `legalName` for business holders or `name` for individual holders. `relationship` and `address` are now optional. ```json Request (POST /destinations, excerpt) theme={null} { "rail": "SPEI", "holder": { "type": "INDIVIDUAL", "name": "Carlos Hernández", "taxId": "HEGC900515ABC" } } ``` **October 7, 2026** v2.22.0 ### Preparing for our Brazil VASP license 💜 We're getting close to applying for our VASP license in Brazil, and part of that preparation reaches the API. Transaction limits will be split by transaction type, and the onboarding questionnaire gets simpler, with monthly income and revenue questions and fixed lists for most answers. Your customers will also accept terms of service with each Lumx legal entity they work with, and your webhooks will tell you when those terms change. Here is what changes and what your integration needs to handle. > [!WARNING] > > We'll roll out these breaking changes on October 28, 2026: terms of service before verification, transaction limits per transaction type, the required `type` on limit requests, and the new questionnaire fields and accepted values. Please update your integration before then. **Enhancement:** 1. **Terms of service come before verification** Customers accept the terms of service before they can start identity verification. `verification.link` is only returned once the `TERMS_OF_SERVICE` requirement is `APPROVED`, so read the customer again after acceptance to get it. See [Terms of Service](/compliance/terms-of-service). 2. **Transaction limits per transaction type** `transactionLimits` now returns separate limits for `onramp`, `offramp` and `transfer`. Each one has the same `single`, `daily` and `monthly` structure you already know. Update your integration to read the bucket that matches the transaction you're creating. See [Transaction limits](/compliance/transaction-limits). ```json Before theme={null} "transactionLimits": { "single": { "max": "1000.00" }, "daily": { "max": "5000.00", "used": "1200.00", "remaining": "3800.00" }, "monthly": { "max": "20000.00", "used": "1200.00", "remaining": "18800.00" } } ``` ```json After theme={null} "transactionLimits": { "onramp": { "single": { "max": "1000.00" }, "daily": { "max": "5000.00", "used": "1200.00", "remaining": "3800.00" }, "monthly": { "max": "20000.00", "used": "1200.00", "remaining": "18800.00" } }, "offramp": { "single": { ... }, "daily": { ... }, "monthly": { ... } }, "transfer": { "single": { ... }, "daily": { ... }, "monthly": { ... } } } ``` [Limit increase requests](/guides/limit-increase-requests) follow the same split. `POST /customers/{id}/limit-requests` now requires a `type` field (`ONRAMP`, `OFFRAMP` or `TRANSFER`), and each type can have its own pending request. Limit request responses include `type`, which is `null` for requests created before this change. ```json Request (POST /customers/{id}/limit-requests, excerpt) theme={null} { "type": "OFFRAMP", "requested": { "single": "5000.00", "daily": "10000.00", "monthly": "50000.00" } } ``` 3. **Individual additional information** We updated the questionnaire behind customer verification to meet new regulatory requirements. `PATCH /customers/{id}/additional-information` for individuals now requires `monthlyIncomeInUSD`, and `professionalSituation`, `sourceOfFunds` and `transactionCounterparties` only accept the values listed under Accepted values below. ```json Request (PATCH /customers/{id}/additional-information) theme={null} { "type": "INDIVIDUAL", "phone": "+5511999999999", "firstName": "Maria", "middleName": "Silva", "lastName": "Souza", "address": { "country": "BRA", "line1": "Rua A, 100", "city": "São Paulo", "state": "SP", "postalCode": "01000-000" }, "professionalSituation": "BUSINESS_OWNER", "professionalOccupation": "Engineer", "monthlyIncomeInUSD": "8000", "monthlyVolumeInUSD": "5000", "transactionVolumeInUSD": "1000", "sourceOfFunds": "SALARY_EMPLOYMENT_INCOME", "transactionCounterparties": ["SELF", "FAMILY"] } ``` 4. **Business additional information** For businesses, `monthlyRevenueInUSD` is now required and `monthlyTransactionVolumeInUSD` takes a range instead of free text. `businessActivity`, `sourceOfFunds` and `transactionCounterparties` only accept the values listed under Accepted values below. ```json Request (PATCH /customers/{id}/additional-information) theme={null} { "type": "BUSINESS", "phone": "+5511999999999", "address": { "country": "BRA", "line1": "Av. B, 200", "city": "São Paulo", "state": "SP", "postalCode": "01000-000" }, "website": "https://acme.com", "businessActivity": "PAYMENT_INSTITUTION", "servicesProvided": "Cross-border payments for small businesses", "monthlyRevenueInUSD": "250000", "monthlyTransactionVolumeInUSD": "1M_TO_5M", "sourceOfFunds": "OPERATING_SERVICE_REVENUE", "transactionCounterparties": ["CUSTOMERS", "SUPPLIERS_VENDORS"], "isRegulatedActivity": true, "regulatedActivityDetails": "Licensed payment institution" } ``` 5. **Individual associated parties** UBOs, shareholders and representatives that are individuals now require `monthlyIncomeInUSD` on `POST /customers/{id}/associated-parties`, and you'll also get it back when you read associated parties. Business associated parties stay the same. ```json Request (POST /customers/{id}/associated-parties) theme={null} { "type": "INDIVIDUAL", "roles": ["UBO", "REPRESENTATIVE"], "email": "joao@acme.com", "taxId": "12345678909", "name": "João Lima", "birthDate": "1985-04-12", "address": { "line1": "Rua C, 10", "city": "São Paulo", "state": "SP", "postalCode": "01000-000", "country": "BRA" }, "ownershipPercentage": 40, "monthlyIncomeInUSD": "12000" } ``` #### Accepted values > [accordion] professionalSituation (individual) > > ```text theme={null} > EMPLOYEE > SELF_EMPLOYED > BUSINESS_OWNER > GOVERNMENT_OFFICIAL > INVESTOR > STUDENT > RETIRED > HOMEMAKER > UNEMPLOYED > OTHER > ``` > [accordion] sourceOfFunds (individual) > > ```text theme={null} > SALARY_EMPLOYMENT_INCOME > BUSINESS_INCOME_DIVIDENDS > SAVINGS_INVESTMENT_RETURNS > PENSION_RETIREMENT > SALE_OF_ASSETS_REAL_ESTATE > INHERITANCE_GIFT > LOAN > GOVERNMENT_BENEFITS > GAMBLING_LOTTERY_PROCEEDS > ROYALTIES_INTELLECTUAL_PROPERTY > THIRD_PARTY_FUNDS > OTHER > ``` > [accordion] transactionCounterparties (individual) > > ```text theme={null} > SELF > FAMILY > FRIENDS > EMPLOYER > MERCHANTS_BUSINESSES > GOVERNMENT > OTHER > ``` > [accordion] sourceOfFunds (business) > > ```text theme={null} > OPERATING_SERVICE_REVENUE > INVESTMENT_RETURNS_CAPITAL_GAINS > ASSET_BUSINESS_SALE > SHAREHOLDER_EQUITY_RAISE > LOAN > INTERCOMPANY_TRANSFER > GRANT > CRYPTOCURRENCY_DIGITAL_ASSETS > ROYALTIES_AFFILIATE_INCOME > LEGAL_SETTLEMENT_SUCCESSION > GAMBLING_REVENUE > CHARITABLE_CONTRIBUTIONS > OTHER > ``` > [accordion] transactionCounterparties (business) > > ```text theme={null} > OWN_ACCOUNTS_INTERCOMPANY > SHAREHOLDERS_PARTNERS > CUSTOMERS > MERCHANTS > SUPPLIERS_VENDORS > EMPLOYEES > CONTRACTORS > FINANCIAL_INSTITUTIONS_VASP > INVESTMENT_COUNTERPARTIES > GOVERNMENT_TAX_AUTHORITIES > AFFILIATED_COMPANIES > OTHER > ``` > [accordion] monthlyTransactionVolumeInUSD (business) > > ```text theme={null} > UP_TO_100K > 100K_TO_1M > 1M_TO_5M > 5M_TO_10M > 10M_TO_20M > 20M_TO_30M > OVER_30M > ``` > [accordion] businessActivity (business) > > ```text theme={null} > SOFTWARE_DEVELOPMENT > VASP > PAYMENT_INSTITUTION > PAYMENT_PROCESSOR_GATEWAY > LIQUIDITY_PROVIDER_OTC_DESK > CRYPTO_MSB > STABLECOIN_ISSUER > NON_CUSTODIAL_WALLET_PROVIDER > CUSTODIAN > CRYPTO_CARDS > DEFI_DAO_FOUNDATION > NFT_MARKETPLACE_TOKEN_PROJECT > P2P_MERCHANTS > ATM_KIOSK_OPERATOR > CRYPTO_MINER > GAMING_METAVERSE_P2E > FOREIGN_EXCHANGE_FX > NON_CRYPTO_MSB > BANKING_CORRESPONDENT > BANK_FINANCIAL_INSTITUTION > ECOMMERCE_MARKETPLACE > IMPORTER_EXPORTER_TRADE > TRAVEL_AGENCY_TOURISM > INVESTMENT_FUND > INVESTMENT_ADVISOR_MANAGER > VENTURE_CAPITAL_PRIVATE_EQUITY > SOVEREIGN_WEALTH_FUND > HOLDING_SPV_FAMILY_OFFICE > TRUST > COMPANY_FORMATION_CSP > LOAN_OR_FINANCE_COMPANY > INSURANCE_COMPANY > COMMODITIES_OIL_AND_GAS > PRECIOUS_METALS_AND_STONES > REAL_ESTATE_LEASING > LOGISTICS_CUSTOMS_TRANSPORT > WHOLESALER > GENERAL_MANUFACTURING > RETAILER > JEWELRY_RETAIL > PROFESSIONAL_MARKETING_SERVICES > PAYROLL_PEO_HR_SERVICES > PHARMACEUTICAL_DRUG_DISTRIBUTION > CHARITY_NGO_NON_PROFIT > GAMBLING_CASINO_BETTING > ART_ANTIQUITIES_COLLECTIBLES > PAWN_SHOPS > MINING_AND_EXTRACTION > CONSTRUCTION_SKILLED_TRADE > MEDIA_ARTS_ENTERTAINMENT > TOBACCO_ALCOHOL > MARIJUANA_CANNABIS > ADULT_ENTERTAINMENT > ARMS_INDUSTRY > AGRICULTURE_FISHING_HUNTING > HEALTHCARE_SOCIAL_ASSISTANCE > EDUCATIONAL_SERVICES > ACCOMMODATION_FOOD_SERVICE > UTILITIES_PUBLIC_ADMIN > OTHER > ``` Values outside these lists return `400`, including values we accepted before this change. Customers you onboarded earlier keep their previous answers in `additionalInformation`. **Deprecated:** 1. **Removed from individual additional information:** `jurisdictions`, `involvedActivities`. 2. **Removed from business additional information:** `jurisdictions`, `involvedActivities`, `annualRevenue`, `complianceAndAML`, `monthlyVolumeInUSD`, `transactionVolumeInUSD`. Send the expected monthly volume in `monthlyTransactionVolumeInUSD` instead. 3. **Renamed in business additional information:** `companyType` is now `businessActivity`, with the new list of values above. **New feature:** > [!NOTE] > > Terms of service will go live next week. If in doubt, contact the Lumx operations team. 1. **Terms of service webhooks** Customers now sign the terms of service of each Lumx legal entity separately. If your project has webhooks configured, you'll receive these new events automatically. You don't need to subscribe to anything. | Event | When it fires | | - | - | | `customer.tos.pending` | The customer needs to accept new terms before `graceUntil` | | `customer.tos.updated` | The terms changed, but the customer doesn't need to accept them again | | `customer.tos.signed` | The customer signed terms of service | | `customer.missing_tos` | `graceUntil` passed without acceptance and the customer moved to `MISSING_TOS` | `customer.tos.pending` brings everything you need to send the customer to the new terms. `tosLink` is the acceptance page and stops working at `expiresAt`. `graceUntil` is the deadline to accept. `url` points to the terms themselves. If the link expires, generate a new one with [Create TOS acceptance link](/api-reference/customers/create-tos-acceptance-link). ```json customer.tos.pending theme={null} { "eventId": "550e8400-e29b-41d4-a716-446655440000", "eventType": "customer.tos.pending", "data": { "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "tosLink": "https://dashboard.example.com/tos/...", "expiresAt": "2026-08-28T00:00:00.000Z", "graceUntil": "2026-08-21T00:00:00.000Z", "url": "https://provider.example.com/terms" } } ``` `customer.tos.updated` is a heads-up only. Share the updated `url` with your customer if you'd like, there's nothing for them to sign. ```json customer.tos.updated theme={null} { "eventId": "550e8400-e29b-41d4-a716-446655440000", "eventType": "customer.tos.updated", "data": { "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "url": "https://provider.example.com/terms" } } ``` `customer.tos.signed` fires every time the customer signs: during onboarding, after accepting new terms, and when recovering from `MISSING_TOS`. ```json customer.tos.signed theme={null} { "eventId": "550e8400-e29b-41d4-a716-446655440000", "eventType": "customer.tos.signed", "data": { "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "signedAt": "2026-08-11T12:00:00.000Z" } } ``` If the customer doesn't accept by `graceUntil`, their verification status moves to `MISSING_TOS` and they can't create on-ramps, off-ramps or transfers until they sign. You'll receive `customer.missing_tos` with `statusReason.reason` set to `TOS_GRACE_EXPIRED`. ```json customer.missing_tos (excerpt) theme={null} { "eventId": "550e8400-e29b-41d4-a716-446655440000", "eventType": "customer.missing_tos", "data": { "id": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b", "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "status": "missing_tos", "statusReason": { "reason": "TOS_GRACE_EXPIRED" } } } ``` We're here to help your team through these changes. Thank you for building with us! 💜 **October 1, 2026** v2.20.0 ### GBP accounts and FPS are live 🇬🇧 **New feature:** 1. **GBP accounts** Request `GBP` in the `accounts` array when you [create a customer](/guides/create-a-customer) to provision a GBP virtual account, subject to your project's allowed currencies. Track it with `GET /accounts`. ```json Request theme={null} { "accounts": ["GBP"] } ``` ```json Response (GET /accounts) theme={null} { "data": [ { "id": "4e8a1d20-9c7b-4f33-b512-7d0e9f1a2b3c", "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "status": "ACTIVE", "currency": "GBP", "createdAt": "2026-09-29T13:58:40.217Z", "updatedAt": "2026-09-29T14:02:05.931Z" } ] } ``` 2. **FPS on-ramps** On-ramps accept `rail: "FPS"` (Faster Payments Service) with `sourceCurrency: "GBP"`. The response returns the sort code and account number to pay into under `state.payment.identifier`. ```json Request theme={null} { "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "rail": "FPS", "sourceCurrency": "GBP", "sourceAmount": "1000.00", "targetCurrency": "USDC", "purpose": "PERSONAL_ACCOUNT" } ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174012", "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "type": "ON_RAMP", "request": { "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "rail": "FPS", "sourceCurrency": "GBP", "sourceAmount": "1000.00", "targetCurrency": "USDC", "purpose": "PERSONAL_ACCOUNT" }, "state": { "status": "AWAITING_FUNDS", "payment": { "rail": "FPS", "identifier": { "sortCode": "040509", "accountNumber": "00126007" }, "holder": { "name": "Lumx UK Ltd" } } }, "metadata": {}, "createdAt": "2026-10-01T15:30:00Z", "updatedAt": "2026-10-01T15:30:05Z" } ``` 3. **FPS destinations** Create UK bank account destinations with `rail: "FPS"` and `currency: "GBP"`. The identifier is the 6-digit `sortCode` and 8-digit `accountNumber`. `bank` and `holder` are required, but their addresses only need `country`. SEPA destinations follow the same rule. ```json Request theme={null} { "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "UK Supplier Account", "type": "EXTERNAL", "rail": "FPS", "currency": "GBP", "identifier": { "sortCode": "040509", "accountNumber": "00125977" }, "bank": { "name": "Mock Bank", "address": { "country": "GBR" } }, "holder": { "type": "INDIVIDUAL", "relationship": "SUPPLIER", "name": "William Default", "taxId": "QQ123456C", "email": "william@example.com", "address": { "country": "GBR" } } } ``` ```json Response theme={null} { "id": "7a2c9e14-5b3f-4d81-9e6a-0f1b2c3d4e5f", "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "UK Supplier Account", "type": "EXTERNAL", "rail": "FPS", "currency": "GBP", "identifier": { "sortCode": "040509", "accountNumber": "00125977" }, "bank": { "name": "Mock Bank", "address": { "country": "GBR" } }, "holder": { "type": "INDIVIDUAL", "relationship": "SUPPLIER", "name": "William Default", "taxId": "QQ123456C", "email": "william@example.com", "address": { "country": "GBR" }, "verification": { "status": "UNDER_VERIFICATION", "level": "BASIC" } }, "createdAt": "2026-10-01T15:30:22.456Z", "updatedAt": "2026-10-01T15:30:22.456Z" } ``` 4. **EUR and GBP autoconversion rules** Create autoconversion rules for EUR and GBP accounts. `sourceDepositInfo` returns the SEPA (`iban`, `bic`) or FPS (`sortCode`, `accountNumber`) details to share with the sender. As with USD, deposits are matched to the account, so each customer can have one active EUR rule and one active GBP rule at a time. See [Autoconversion Rules](/guides/autoconversion-rules). ```json Request theme={null} { "accountId": "4e8a1d20-9c7b-4f33-b512-7d0e9f1a2b3c", "targetCurrency": "USDC", "blockchain": "POLYGON", "purpose": "INVESTMENT", "name": "GBP → USDC investment" } ``` ```json Response (GBP) theme={null} { "id": "2c6e5a71-8b3d-4f99-ae42-1d5f6b7c8d9e", "accountId": "4e8a1d20-9c7b-4f33-b512-7d0e9f1a2b3c", "partnerFeeId": null, "status": "ACTIVE", "provider": "OPENPAYD", "sourceCurrency": "GBP", "targetCurrency": "USDC", "blockchain": "POLYGON", "purpose": "INVESTMENT", "sourceDepositInfo": [ { "rail": "FPS", "sortCode": "040075", "accountNumber": "82624944" } ], "createdAt": "2026-09-29T14:07:52.104Z" } ``` ```json Response (EUR) theme={null} { "id": "1b5d4f60-7a2c-4e88-9d31-0c4f5a6b7c8d", "accountId": "9f1c2b7e-3d4a-4c5e-8f10-2a6b7c8d9e01", "partnerFeeId": null, "status": "ACTIVE", "provider": "OPENPAYD", "sourceCurrency": "EUR", "targetCurrency": "USDC", "blockchain": "POLYGON", "purpose": "INVESTMENT", "sourceDepositInfo": [ { "rail": "SEPA", "iban": "GB29NWBK60161331926819", "bic": "OPCVGB2LXXX" } ], "createdAt": "2026-09-29T14:03:11.482Z" } ``` **Enhancement:** 1. **SEPA routing and cut-off times** SEPA payments settle instantly 24/7/365. Payments of €100k or more submitted between 4:30 PM and 2:30 AM UK time route to SEPA Credit Transfer and settle on the same or next business day. Lumx picks the scheme automatically, so you keep sending `rail: "SEPA"`. See [Payment rail cut-off times](/additional-information/sla-and-cutoffs). 2. **Immediate EUR account activation** EUR accounts now move to `ACTIVE` right after the customer is `APPROVED`, instead of waiting up to 1 business day for a banking partner review. GBP accounts activate the same way. See [Activation timelines](/concepts/accounts#activation-timelines). For complete coverage details, see [Coverage](/get-started/coverage). **August 19, 2026** v2.19.0 ### New account statuses and lifecycle 🏦 **New feature:** 1. **New account statuses** Accounts now report the full lifecycle: `AWAITING_ONBOARDING` (account created, customer not yet verified), `REQUESTED` (customer approved, provisioning requested), `REJECTED` (account application rejected), and `INACTIVE` (customer deactivated), in addition to the existing `PROVISIONING`, `RFI`, and `ACTIVE`. The `CLOSED` status was merged into `INACTIVE`, and the `account.closed` webhook event was removed. See [Accounts](/concepts/accounts) for the lifecycle. 2. **New webhook events** `account.awaiting_onboarding`, `account.requested`, `account.rejected`, and `account.inactive` fire on the new transitions. During the migration, account creation emits both `account.awaiting_onboarding` and the legacy `account.provisioning`, so existing integrations keep working. > [!NOTE] > > Subscribe to the new `account.*` events on the dashboard's webhook settings to receive the new lifecycle notifications. **August 18, 2026** v2.18.0 ### Limit increase requests via API 📈 **New feature:** 1. **Limit increase requests** Request higher transaction limits for a customer directly through the API: * Upload a supporting document with `POST /customers/{id}/documents` using one of the new document types: `LIMIT_REQUEST_BANK_STATEMENT`, `LIMIT_REQUEST_TAX_RETURN`, or `LIMIT_REQUEST_FINANCIAL_STATEMENTS`. * Create the request with `POST /customers/{id}/limit-requests`. * Track it with the read endpoints or subscribe to the `customer.limit_request.*` [webhook events](/developer/webhooks#available-events). The `status` moves from `IN_REVIEW` to `APPROVED`, `PARTIALLY_APPROVED`, or `REJECTED`. See the [Limit Increase Requests guide](/guides/limit-increase-requests). ```json theme={null} { "requested": { "single": "15000.00", "daily": "60000.00", "monthly": "120000.00" }, "supportingDocumentId": "9d1e2f3a-4b5c-6d7e-8f90-1a2b3c4d5e6f" } ``` **August 13, 2026** v2.17.0 ### USD autoconversion rules 💵 **New feature:** 1. **USD autoconversion rules** Create autoconversion rules for USD accounts: every USD deposit is automatically converted to USDC or USDT and delivered on-chain. `sourceDepositInfo` carries the bank details for the ACH, FEDWIRE, and SWIFT rails. Each customer can have one active USD rule at a time. See [Autoconversion Rules](/guides/autoconversion-rules). ```json theme={null} { "accountId": "a3f8d21c-6e94-4b7a-8c15-9d0e2f4b6a83", "targetCurrency": "USDC", "purpose": "INVESTMENT", "name": "USD → USDC investment" } ``` **Enhancement:** 2. **Optional simulate-deposit reference** `reference` is now optional on `POST /autoconversion-rules/simulate-deposit` and is not used for USD simulations. **August 10, 2026** v2.16.0 ### New error response contract 🚨 **Enhancement:** 1. **Standardized error responses** Every error now returns the same envelope with a machine-readable `code` field, a `requestId` for support requests, and field-level details in `validationErrors` on validation failures. Handle errors by `code` and not by `message`. See the full catalog in [Errors](/developer/errors). ```json theme={null} { "requestId": "9f6f9741-4c65-4e0b-a48f-16d5b34d9e2f", "timestamp": "2026-08-10T10:30:45.123Z", "path": "/transactions/on-ramp", "status": 403, "code": "KYC_NOT_APPROVED", "message": "KYC/B is not approved for this customer" } ``` **August 10, 2026** v2.15.0 ### IP allowlisting for API keys 🔒 **Enhancement:** 1. **IP allowlisting** Add allowed IP addresses for each API key on the **Developers → API Keys** page in the Dashboard. The allowlist applies to production only (`https://api.lumx.io`), sandbox stays open to requests from any IP. During the current grace period nothing is blocked, even for keys that already have an allowlist. Enforcement starts on **September 21, 2026**, when the production API begins rejecting requests from IPs outside the allowlist, so register your server IPs before then. **July 28, 2026** v2.14.0 ### Stellar network support ⭐ **New feature:** 1. **Stellar network** Pass `"blockchain": "STELLAR"` on on-ramps and exchange rate requests to settle USDC on the Stellar network. On-ramp only for now. See [Coverage](/get-started/coverage) and [Stablecoin Wallets](/concepts/wallets). ```json theme={null} { "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "rail": "PIX", "sourceCurrency": "BRL", "sourceAmount": "10000.00", "targetCurrency": "USDC", "blockchain": "STELLAR", "purpose": "PERSONAL_ACCOUNT" } ``` **July 14, 2026** v2.13.0 ### Autoconversion rules 🔁 **New feature:** 1. **Autoconversion rules** Create a standing rule with `POST /autoconversion-rules` and every fiat deposit that hits the account is automatically converted to USDC or USDT and delivered on-chain — no per-transaction API call. Available for BRL and MXN accounts for now. See [Autoconversion Rules](/guides/autoconversion-rules) for the full guide. ```json theme={null} { "accountId": "44dd9734-176a-4ced-924f-103f8d50ea5e", "targetCurrency": "USDC", "blockchain": "POLYGON", "purpose": "INVESTMENT", "name": "BRL → USDC investment" } ``` **July 13, 2026** v2.12.0 ### KYC reuse with Sumsub share tokens 🔁 **New feature:** 1. **Reuse an existing Sumsub verification** If your customers already completed KYC on your own Sumsub account, pass a Sumsub share token in the new optional `shareToken` field when creating a customer to import the existing verification instead of collecting documents again. Only available for individual customers, and requires a data-sharing agreement between you and Lumx — contact [compliance@lumx.io](mailto:compliance@lumx.io) to set it up. See the [KYC Reuse guide](/guides/kyc-reuse). ```json theme={null} { "shareToken": "_act-sbx-jwt-token-from-sumsub", "type": "INDIVIDUAL", "name": "William Default", "taxId": "123.456.789-00", "birthDate": "1990-01-01", "country": "BRA", "email": "william.default@example.com", "accounts": ["BRL"] } ``` **May 25, 2026** v2.11.0 ### Account provisioning at customer creation 🏦 **Enhancement:** 1. **Provision accounts at customer creation** You can now pass an `accounts` array of currency codes when creating a customer. Each currency provisions an account that enters verification and becomes `ACTIVE` once approved. The customer response now also returns the linked accounts with their `id` and `currency`. > [!WARNING] > > Transition period: Until June 19, 2026, the `accounts` field is optional. > During this period, if no account is specified, the BRL account will be > created. After this date, it will be required when creating a customer. ```json theme={null} { "type": "BUSINESS", "legalName": "Lumx Tecnologia LTDA", "taxId": "00.000.000/0001-91", "incorporationDate": "2020-01-01", "country": "BRA", "email": "hello@lumx.io", "accounts": [ { "id": "8412f484-32fe-418f-80d1-99eb1b3ba7f3", "currency": "BRL" }, { "id": "af04e979-360a-428a-84e6-cbd8ffc4942b", "currency": "USD" }, { "id": "d06d342c-629a-4d99-a26f-34e8ea528403", "currency": "EUR" }, { "id": "16cb802f-8521-41ad-ab6e-dc40f43c0ad3", "currency": "MXN" } ] } ``` 2. **Read accounts** List a customer's accounts via `GET /accounts`. Each account is linked to a customer and runs through a verification workflow (`PROVISIONING` → `ACTIVE`). See [Accounts](/concepts/accounts) for details. ```json theme={null} { "data": [ { "id": "433bbe8b-feba-49e3-80f8-7bb528d3744e", "customerId": "3b55a2fa-57dc-48f1-ac0f-ac1d5ba4674a", "currency": "BRL", "status": "ACTIVE", "createdAt": "2026-05-13T20:49:13.236Z", "updatedAt": "2026-05-14T13:55:15.418Z" } ] } ``` 3. **Account webhook events** Subscribe to `account.provisioning`, `account.rfi`, `account.active`, and `account.closed` to receive real-time updates as an account moves through verification. See [Available events](/developer/webhooks#available-events). **May 25, 2026** v2.11.0 ### Bank accounts renamed to destinations 📦 **Deprecated:** > [!WARNING] > > Transition period: The `/bank-accounts` route will continue to work until June > 19, 2026. After this date, only `/destinations` will be available. Migrate > your integration before then. 1. **Resource renamed across the API** The `/bank-accounts` resource is now `/destinations`. The `bankAccountId` field is now `destinationId`. See [Destinations](/concepts/destinations) for the updated concept. `POST /transactions/off-ramp` will accept either `destinationId` or `bankAccountId` until June 19, 2026. After this date, only `destinationId` will be accepted. 2. **`bank_account.*` webhook events renamed to `destinations.*`** The events previously emitted as `bank_account.under_verification`, `bank_account.approved`, and `bank_account.final_rejection` are now `destinations.under_verification`, `destinations.approved`, and `destinations.final_rejection`. 3. **`type` field removed from destination requests** The `type` field is no longer documented for `POST /destinations`. The `/destinations` route will continue to accept `type` as an optional field until June 19, 2026 so existing integrations migrating from `/bank-accounts` keep working. After this date, the field will be rejected. Continue sending `type` on `/bank-accounts`; stop sending it on `/destinations`. > [!NOTE] > > Update your subscriptions on the [dashboard](https://dashboard.lumx.io) to > start receiving the new `destinations.*` events — the old event names will no > longer fire. **May 25, 2026** v2.11.0 ### `TEMPORARY_REJECTION` renamed to `RFI` ✏️ **Deprecated:** > [!WARNING] > > Breaking change. The status value, the value returned for each associated > party, and the webhook event name have all changed. Update your integration > before deploying to production. 1. **Customer and associated party status renamed** The verification status `TEMPORARY_REJECTION` is now `RFI` (Request for Information). The rename applies to the `status` field on both the customer and each entry in the `associatedParties` array, in API responses and webhook payloads. 2. **New `customer.rfi` webhook event** A new `customer.rfi` webhook event has been introduced. The `customer.temporary_rejection` event will continue to exist, but it will no longer fire — only the new `customer.rfi` event will be emitted. The `customer.temporary_rejection` event will be removed in a future release. > [!NOTE] > > Update your subscriptions on the [dashboard](https://dashboard.lumx.io) to > start receiving the new `customer.rfi` event. **May 25, 2026** v2.11.0 ### Wallets only returned for APPROVED customers 👛 **Deprecated:** 1. **`wallets` gated by verification status** The `wallets` array is now only returned on customer responses once the customer's `verification.status` is `APPROVED`. This also means [Create a customer](/api-reference/customers/create-a-customer) no longer returns `wallets`. Call [Read a customer](/api-reference/customers/read-a-customer) or register the `customer.approved` [webhook](/developer/webhooks) to get notified once verification is approved and the wallets become available. **May 25, 2026** v2.11.0 ### Isolated retry for associated party verification 🔁 **Enhancement:** 1. **Retry verification for specific associated parties** The [Start a verification](/api-reference/customers/start-a-verification) endpoint now accepts an optional `associatedPartyIds` array in the request body. When provided, only the listed associated parties are sent for verification again, and any associated party not included keeps its current verification status. See [Retrying verification for specific associated parties](/guides/business-verification#retrying-verification-for-specific-associated-parties) for details. > [!NOTE] > > Applies only to BUSINESS customers. Resubmit any corrected > documents/information for the affected associated parties before starting the > retry. ```json theme={null} { "associatedPartyIds": [ "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "b2c3d4e5-f6a7-8901-bcde-f12345678901" ] } ``` **April 8, 2026** v2.10.0 ### Improving customer onboarding 🛩️ **Enhancement:** 1. **Auto-assign UBO role to sole shareholders** If an individual associated party is the only shareholder, the system will automatically add `UBO` to their `roles` array. No action required from your side. 2. **`PROOF_OF_ADDRESS` document now required** A `PROOF_OF_ADDRESS` document is now required when [uploading documents](/api-reference/customers/upload-a-document) for individual customers, business customers, UBOs, and representatives. You can't start a verification without sending this document. > [!NOTE] > > The proof of address document must have been issued within the last **90 > days**. ```json theme={null} { "type": "PROOF_OF_ADDRESS" } ``` 3. **Terms of service acceptance required before verification** > [!WARNING] > > Transition period: Until April 16, 2026, acceptance of terms of service will > be optional to start a verification. After this date, acceptance will be > mandatory. Customers must now accept terms of service before verification can start. Send a `POST` request to `/customers/{id}/tos` with an optional `redirectUrl` to get the acceptance URL. Share the returned URL with your customer. The `requirements` array includes `TERMS_OF_SERVICE` until the customer has accepted. See also [Read a customer](/api-reference/customers/read-a-customer). ```json Request body (optional) theme={null} { "redirectUrl": "https://yourapp.com/onboarding/next-step" } ``` ```json Response theme={null} { "url": "https://dashboard.lumx.io/tos/sandbox/eyJhbGciOiJSUzI1...", "expiresAt": "2026-04-09T16:00:00Z" } ``` The acceptance URL expires after **24 hours**. Generate a new one if it expires before the customer accepts. **March 31, 2026** v2.9.0 ### Webhooks v2 🔔 **Enhancement:** 1. **New webhook delivery system** Webhooks now use a new delivery infrastructure with improved reliability, automatic retries with exponential backoff, and signature verification. All events follow a consistent structure with `eventId`, `eventType`, and `data` fields. See [Webhooks](/developer/webhooks) for details. **Deprecated:** > [!WARNING] > > Transition period: Until April 30, 2026, both legacy and new webhook formats > will be delivered. After this date, only the new format will be sent. 2. **Legacy webhook format deprecated:** Migrate to the new webhook format and update your signature verification to use the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers. See [Verifying webhook signatures](/developer/webhooks#verifying-webhook-signatures) for implementation examples. **March 12, 2026** v2.8.0 ### MXN support is now live 🇲🇽 **New feature:** 1. **MXN transactions now available** You can now execute transactions using MXN (Mexican Peso) as source or target currency, expanding our coverage to support payments in Mexico. ```json theme={null} { "sourceCurrency": "MXN", "sourceAmount": "10000.00", "targetCurrency": "USDC", "purpose": "PERSONAL_ACCOUNT" } ``` 2. **MXN bank accounts now supported** You can now create and manage bank accounts with MXN currency for off-ramp payments to Mexican bank accounts using SPEI. ```json theme={null} { "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "Mexico Supplier Account", "type": "EXTERNAL", "rail": "SPEI", "currency": "MXN", "identifier": { "clabe": "012180001234567897" }, "holder": { "type": "INDIVIDUAL", "relationship": "SUPPLIER", "name": "Juan Pérez", "taxId": "PEPJ850101ABC", "address": { "line1": "Avenida Insurgentes Sur 1458", "city": "Mexico City", "state": "CDMX", "postalCode": "03900", "country": "MEX" } } } ``` For complete coverage details, see [Coverage](/get-started/coverage). **March 10, 2026** v2.7.0 ### Sandbox magic numbers for testing 🪄 **New feature:** 1. **Magic numbers for customer verification testing** Use sentinel values in the `taxId` field when creating customers in sandbox to simulate different verification statuses. No API changes required, magic numbers use existing fields. | `taxId` ending | Simulated status | | - | - | | `1` | `NOT_STARTED` - verification never starts automatically | | `2` | `RFI` - requests additional documents | | `3` | `FINAL_REJECTION` - permanent rejection | | Any other | `APPROVED` - default behavior | ```json theme={null} { "type": "INDIVIDUAL", "name": "William Default", "taxId": "123.456.789-01", "birthDate": "1990-01-01", "country": "BRA", "email": "william.default@example.com" } ``` Webhooks are triggered almost instantly with the simulated status, so you can test your full end-to-end flow exactly as it works in production. **March 6, 2026** v2.6.0 ### Multiple roles for associated parties 🎭 **Deprecated:** > [!WARNING] > > Breaking change. Requests using the old `role` field will be rejected. Update > your integration before deploying to production. 1. **`role` changed to `roles` array in associated parties:** The `POST /customers/{id}/associated-parties` endpoint now requires a `roles` array instead of the single `role` string field. This allows associated parties to hold multiple roles simultaneously (e.g., both UBO and REPRESENTATIVE). Valid values: `UBO`, `REPRESENTATIVE`, `SHAREHOLDER`. Before: ```json theme={null} { "type": "INDIVIDUAL", "role": "UBO", "name": "Maria Santos", "birthDate": "1985-07-10", "email": "maria.santos@example.com", "taxId": "987.654.321-00", "ownershipPercentage": 40 } ``` After: ```json theme={null} { "type": "INDIVIDUAL", "roles": ["UBO"], "name": "Maria Santos", "birthDate": "1985-07-10", "email": "maria.santos@example.com", "taxId": "987.654.321-00", "ownershipPercentage": 40 } ``` For multiple roles: ```json theme={null} { "type": "INDIVIDUAL", "roles": ["UBO", "REPRESENTATIVE"], "name": "Maria Santos" } ``` **February 13, 2026** v2.5.0 ### KYC/KYB API and idempotency support 🤯 **New feature:** 1. **KYC/KYB verification endpoints** New endpoints for the full identity verification flow: send additional information, upload documents, manage associated parties, and start verifications — all via API. * `PATCH /customers/{id}/additional-information` — Submit KYC/KYB data * `POST /customers/{id}/documents` — Upload verification documents * `POST /customers/{id}/associated-parties` — Add UBOs, shareholders, and representatives * `GET /customers/{id}/associated-parties` — List associated parties * `GET /customers/{id}/associated-parties/{associatedPartyId}` — Read an associated party * `POST /customers/{id}/verifications` — Start a verification * `GET /customers/{id}/verifications/{verificationId}` — Read verification status 2. **`additionalInformation` in customer response** After sending additional information, the customer response now includes an `additionalInformation` object with all submitted KYC/KYB data. 3. **Multi-level corporate structures** Associated parties now support a `parentId` field, allowing you to nest shareholders across multiple levels of ownership hierarchy. ```json theme={null} { "type": "INDIVIDUAL", "role": "SHAREHOLDER", "parentId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "name": "John Smith", "ownershipPercentage": 50 } ``` 4. **Idempotency support** All mutation endpoints (`POST`, `PUT`, `PATCH`) now accept an `Idempotency-Key` header to prevent duplicate operations. Keys expire after 24 hours. See [Idempotency](/developer/idempotency) for details. **January 8, 2026** v2.4.0 ### Individual customers and bank account holders 🥳 **New feature:** 1. **Individual customer type** Customers can now be created with `type: "INDIVIDUAL"` for natural persons. Individual customers require `name`, `taxId`, and `birthDate` fields. ```json theme={null} { "type": "INDIVIDUAL", "name": "William Default", "taxId": "100.100.100-01", "birthDate": "1990-01-01" } ``` 2. **Individual bank account holders** Bank accounts now support individual holders with the same structure. For counterparty bank accounts, `birthDate` is optional. 3. **Individual bank account identifier key type as CPF** Bank account now support `CPF` as a valid `keyType` identifier for PIX. **Enhancement:** 4. **New relationship types for bank account holders** Added `FRIEND`, `RELATIVE`, and `EMPLOYEE` to the available relationship types for bank account holders. **January 6, 2026** v2.3.0 ### Multi-blockchain support 🔥 **New feature:** 1. **Multiple wallets per customer** Customers now support multiple wallets, one per blockchain. The new `wallets` array includes blockchain, address, block explorer link, stablecoin balances, and default blockchain indication. ```json theme={null} { "wallets": [ { "blockchain": "POLYGON", "address": "0x...", "blockExplorerUrl": "https://polygonscan.com/address/0x...", "isDefault": true, "balances": [{ "stablecoin": "USDC", "balance": "1000.00" }] } ] } ``` 2. **Blockchain field in transactions** All transactions (on-ramp, off-ramp, transfer) now accept an optional `blockchain` field. If not specified, the project's default blockchain is used. ```json theme={null} { "blockchain": "BASE", "sourceCurrency": "BRL", "sourceAmount": "1000.00", "targetCurrency": "USDC" } ``` 3. **Blockchain field in exchange rates** Exchange rate requests now accept an optional `blockchain` field. The response indicates which blockchain was used for rate calculation. **Enhancement:** 4. **Improved balance tracking** Balances are now tracked per blockchain and per stablecoin, providing better clarity for multi-network operations. **Deprecated:** > [!WARNING] > > Transition period: Until January 26, 2026, both old and new formats will be > returned. After this date, only the new format will be available. 5. **`wallet` and `balances` fields deprecated on customers:** Migrate to the new `wallets` array. Affected endpoints: `POST /customers`, `GET /customers`. 6. **Default partner fee removed:** If no `partnerFeeId` is provided, partner fees will be zero. Remove any logic that depends on a default fee. Affected endpoints: `GET /partner-fees`, `POST /partner-fees`. **December 15, 2025** v2.2.0 ### Transaction limits response improved ⚡ **Enhancement:** 1. **Transaction limits now include usage tracking** When using `includeTransactionLimits=true`, the `transactionLimits` object now returns detailed usage information with `used` and `remaining` fields for `daily` and `monthly` limits, giving you real-time visibility into your customer's limit consumption. ```json theme={null} { "transactionLimits": { "single": { "max": "10000.00" }, "daily": { "max": "100000.00", "used": "0.00", "remaining": "100000.00" }, "monthly": { "max": "1000000.00", "used": "0.00", "remaining": "1000000.00" } } } ``` **Deprecated:** > [!WARNING] > > This is a breaking change. Update your integration before deployment. 2. **`transactionLimits` no longer returned by default:** The `transactionLimits` field is no longer included in `GET /customers/{id}` responses by default. Use the new query parameter `includeTransactionLimits=true` to retrieve transaction limits. 3. **`transactionLimits` removed from customer listing:** The `GET /customers` endpoint no longer returns the `transactionLimits` field in the response array. 4. **10-second timelock removed from exchange rates:** The 10-second timelock option is no longer available. The 30-second timelock now has no additional fees, and fees for other timelock options have been optimized. **November 23, 2025** v2.1.0 ### SEPA and SWIFT are live 🚀 **New feature:** 1. **SEPA support now available** You can now create bank accounts and execute transactions using SEPA (Single Euro Payments Area) for EUR payments across Europe. SEPA provides instant settlement for transactions under \$100k and 1 business day settlement for larger amounts. ```json theme={null} { "rail": "SEPA", "sourceCurrency": "EUR", "sourceAmount": "10000.00", "targetCurrency": "USDC", "purpose": "PERSONAL_ACCOUNT" } ``` 2. **SWIFT support now available** You can now create bank accounts and execute transactions using SWIFT for USD international wire transfers. SWIFT enables global payments with 1-5 business day settlement times. ```json theme={null} { "rail": "SWIFT", "sourceCurrency": "USD", "sourceAmount": "10000.00", "targetCurrency": "USDC", "purpose": "PERSONAL_ACCOUNT" } ``` For complete coverage details, see [Coverage](/get-started/coverage). **Enhancement:** 3. **Purpose field now returned in transaction responses** The `purpose` field is now included in transaction responses under the `request` object, providing full visibility of the original transaction intent. 4. **Target amount visibility improved** Transaction responses now consistently return `targetAmount` in the `receipt` object when the conversion is complete, ensuring you always have access to the final conversion amounts. 5. **Bank account ordering updated** Bank accounts are now ordered by creation date (newest first) instead of by ID, making it easier to find recently added accounts. **October 27, 2024** v2.0.0 ### Bank Accounts are live 🎉 **New feature:** 1. **Supplier payments now available** You can now send off-ramp funds to bank accounts that don't belong to the registered customer. This enables direct supplier payments. The new `/bank-accounts` endpoint allows you to register and manage bank accounts for supplier payments. Refer to the [Create a Bank Account](/api-reference/bank-accounts/create-a-bank-account) for details. **Enhancement:** 2. `PROCESSING` **status renamed to** `TRANSFERRING_STABLECOIN` 3. `state.blockchain` **moved to** `state.receipt` The new `receipt` object includes `transactionHash` and `blockExplorerUrl` fields for better transaction tracking. 4. `purpose` **now required for on-ramp and off-ramp transactions** For compliance reasons, all transaction requests must include a `purpose` field. Requests without this field will be rejected. 5. **On-ramp:** `payment.rail` moved to request level `rail` 6. **Off-ramp:** `customerId` and `payment` object removed, use `bankAccountId` only 7. **Exchange Rate:** `rail` and `customerId` parameters now required # Identity Verification Source: https://docs.lumx.io/compliance/identity-verification Required data for KYC/B verification processes Every customer has to complete identity verification before transacting. What you need to submit depends on whether the customer is an individual or a business. ## Prerequisites The customer has to accept Lumx's Terms of Service before verification can start. Acceptance is collected via `POST /customers/{id}/tos`. Until they accept, the `TERMS_OF_SERVICE` entry stays in the customer's `requirements` array, verification can't proceed, and the customer's `verification.link` is omitted. See [Terms of Service](/compliance/terms-of-service) for the full flow. For step-by-step API instructions on submitting data and documents, see [Individual verification](/guides/individual-verification) or [Business verification](/guides/business-verification). ## Standard KYC/B | **Individual** | **Business** | | :- | :- | | First Name | Country | | Last Name | Company Name | | Tax ID | Tax ID | | Date of Birth | Incorporation Date | | Email | Email | | Country | Phone | | State | State | | City | City | | Street | Street | | Postcode | Postcode | | Phone | Website | | Identity Document | Company Formation Documents¹ | | Liveness | Documents from Representative + UBOs + shareholders with more than 25%⁵ | | Transactional information² | Transactional information² | | Financial documents\* | Regulated activity information³ | | Proof of Address⁴ | Proof of Address⁴ | | -- | Financial documents\* | \*When applicable. ¹ **Company Formation Documents.** In Brazil, we typically ask for: Articles of Incorporation/Bylaws, ownership chart, and (if applicable) minutes appointing/electing directors and the Share Register Book. In other jurisdictions: Articles of Association (or equivalent), Register of Members, and Register of Directors (or equivalents). ² **Transactional Information.** We'll ask about source of funds, monthly income (individuals) or monthly revenue (businesses), expected transaction volume, and the counterparties involved. For the accepted values, see [Onboarding fields](/additional-information/onboarding-fields). For the documentation Lumx accepts, see [Source of Funds and Wealth](/compliance/source-of-funds). ³ **Regulated Activity Information.** We'll ask about any regulated activities the company carries out and, when applicable, request the corresponding license. ⁴ **Proof of Address.** Required for every individual customer, business customer, and each associated party (UBOs, shareholders above 25%, representatives). Must be issued within the last 90 days. For accepted documents, see [Proof of Address](/compliance/proof-of-address). ⁵ **Associated Party Documents.** Each UBO, shareholder above 25%, and representative submits a government-issued ID and a proof of address. IDs must be within their official validity period and issued no more than 10 years ago. > [!NOTE] > > The `taxId` value must be the local tax identifier for the customer's country > of residence. See [Tax IDs by > country](/additional-information/tax-ids-by-country) for the accepted > identifier per country. ## Enhanced KYC/B Enhanced verification kicks in when a customer wants higher transaction limits than the standard thresholds. It's run directly with the Compliance team and involves a deeper review of additional documents. For how to request higher limits and what to send, see [Requesting higher limits](/compliance/transaction-limits#requesting-higher-limits). ## Common rejections A few patterns Lumx compliance flags during review. Avoiding these reduces back-and-forth: * All pages of multi-page documents must be submitted, not just the first. * IDs must be within their official validity period. * Names must match across the ID, ownership documents, and onboarding submission. * Proof of address documents must be issued within the last 90 days, regardless of category. ## Verification SLAs How long it takes a customer to move from `UNDER_VERIFICATION` to `APPROVED` once all required documents are submitted. Targets, not contractual guarantees. | **Verification type** | **Target** | **Notes** | | :- | :- | :- | | KYC Standard | 1 business day | Standard review for individual customers | | KYB Standard | 2 business days | Includes review of associated parties (UBOs, representatives) | | KYC Enhanced | 2 business days | Triggered by higher-limit requests, risk signals, or sanctioned-country exposure | | KYB Enhanced | 3 business days | Deeper review of additional documents | > [!NOTE] > > If compliance needs additional documents, the customer moves to `RFI` and the > SLA clock resets when you resubmit. See [Request for > Information](/compliance/rfi). ## Statuses * `NOT_STARTED`: customer was created, but no data has been submitted yet. * `UNDER_VERIFICATION`: ToS accepted, additional information and required documents submitted, review in progress. * `APPROVED`: verification complete, customer can transact. * `RFI`: submitted information is invalid or inconsistent (e.g. wrong tax ID, unrecognized documents). The customer can resubmit corrected documents. * `FINAL_REJECTION`: verification permanently denied. The customer can't transact. ### Sandbox magic numbers In sandbox, the last digit of `taxId` is a sentinel that drives the simulated verification outcome. Webhooks fire almost instantly with the matching status. | `taxId` ending | Simulated status | | :- | :- | | `1` | `NOT_STARTED` | | `2` | `RFI` | | `3` | `FINAL_REJECTION` | | Any other digit | `APPROVED` (default) | ## Status progression > [tab] Approved > > Successful verification flow: > > `NOT_STARTED` → `UNDER_VERIFICATION` → `APPROVED` > [tab] RFI > > When submitted information is invalid, the customer can resubmit corrected documents: > > `NOT_STARTED` → `UNDER_VERIFICATION` → `RFI` → `UNDER_VERIFICATION` → `APPROVED` > [tab] Final Rejection > > When a customer is permanently rejected: > > `NOT_STARTED` → `UNDER_VERIFICATION` → `FINAL_REJECTION` # Legal Documents Source: https://docs.lumx.io/compliance/legal-documents Terms, policies, and legal requirements for using Lumx services All entities using Lumx services must agree to our legal terms. Review the documents below before proceeding. | **Document** | **Description** | | :- | :- | | [Terms of Service](https://lumx.io/terms-of-use) | General terms of use for Lumx services | | [Privacy Policy](https://lumx.io/privacy-policy) | How we collect, use, and protect your data | | [Restricted Locations List](https://lumx.io/restricted-list) | Jurisdictions where Lumx services are not available | | [Restricted Activities List](https://lumx.io/activities-list) | Activities where Lumx services are not allowed | ## Sharing policies with your end users The Privacy Policy and the Restricted Lists are not collected through the API. The preferred approach is to incorporate them within your own terms, ensuring your end users review them before using Lumx services. If incorporating the policies directly isn't feasible, share our [Legal Documents page](https://lumx.io/legal) with your end users so they can review them before proceeding. For Terms of Service acceptance — which **is** collected through the API per customer — see [Terms of Service](/compliance/terms-of-service). ## Related resources > [card] [Terms of Service](/compliance/terms-of-service) > > How customers accept Lumx's ToS before verification. > [card] [Supported countries](/compliance/supported-countries) > > Jurisdiction tiers and where onboarding is restricted. > [card] [Prohibited business activities](/compliance/prohibited-activities) > > Industries and activities Lumx doesn't support. # Nested Payments Source: https://docs.lumx.io/compliance/nested-payments Visibility rules for processing payments through Lumx Lumx accounts can't be used to process, facilitate, or transmit payments on behalf of any party whose identity or transaction activity isn't visible to Lumx. Every transaction has to be tied to an onboarded customer acting on their own behalf with their own funds. ## What nesting looks like Nesting is when an onboarded customer uses their Lumx account as a pass-through for undisclosed third parties. Common signals: * Funds in the account economically belong to someone other than the onboarded customer. * Invoices, contracts, or payment instructions name an entity different from the account holder. * A single customer collects payments for, or pays expenses of, multiple underlying businesses. * The customer acts as an intermediary, routing money between two third parties, without written approval from Lumx. * Virtual accounts or sub-balances are used to track funds belonging to other parties. ## A simple test Ask one question before any transaction: whose money is it, and whose business does it pay for? If the answer to both is the onboarded customer, the transaction fits within Lumx's visibility model. If either side names an entity Lumx hasn't onboarded, it's nesting. ## Illustrative examples | **Scenario** | **Allowed** | **Not allowed** | | :- | :- | :- | | Receiving payments | Customer collects revenue for goods or services they actually provide. | Customer receives funds owed to a different, non-onboarded entity. | | Paying suppliers or expenses | Customer pays its own vendors, employees, or operating costs. | Customer pays vendors or employees of another entity not onboarded with Lumx. | | Treasury movements | Customer moves its own funds between its own accounts or approved external wallets. | Customer routes another party's treasury activity through their account. | | Virtual accounts | Used for the customer's own commercial activity. | Used to pool or commingle multiple third parties' funds. | | Marketplace flows | Each seller onboarded as a Lumx customer; platform takes a [partner fee](/concepts/partner-fees). | Buyer funds pooled in one account and distributed to non-onboarded sellers. | | Payroll | Each employer holds their own Lumx account and pays from it. | A payroll provider uses its own account to pay employees of multiple, non-onboarded employers. | ## Why it's prohibited Nested payments break the visibility model that compliance, sanctions screening, and transaction monitoring depend on: * KYC/KYB integrity. Lumx can't run due diligence on parties it can't see. * Sanctions screening. Hidden counterparties evade screening against restricted lists. * Transaction monitoring. Pooled or masked flows defeat pattern detection. * Regulatory exposure. Acting as an undisclosed intermediary can amount to operating as an unlicensed payment institution. * Operational risk. Commingled funds create reconciliation gaps and dispute exposure. ## Approved structures by use case Some business models legitimately involve money flowing between multiple parties. These are allowed only when every party in the chain is visible to Lumx. The tables below break down what each model looks like in practice. ### Marketplaces and platforms | **Area** | **Acceptable** | **Nested (avoid)** | | :- | :- | :- | | Onboarding | Each seller is onboarded as its own Lumx customer. | Sellers transact through the platform's account without their own Lumx onboarding. | | Funds flow | Buyer payments are split between seller and platform via Lumx; sellers receive funds in their own account. | All buyer funds land in the platform's account; the platform distributes to sellers off-platform. | | Platform fees | Collected via the [partner fee](/concepts/partner-fees) mechanism. | Skimmed from a commingled pool before forwarding to sellers. | ### Payroll providers | **Area** | **Acceptable** | **Nested (avoid)** | | :- | :- | :- | | Onboarding | The employer has its own Lumx account and is approved by Lumx. | A payroll provider uses its own Lumx account to pay employees of non-onboarded employers. | | Accounts | The payout account belongs to the approved employer. | The provider pools multiple employers' payroll funds in its own account. | | Payments | Each payout originates from the employer's account with documentation matching that employer. | The provider sends payouts in its own name on behalf of an employer Lumx hasn't seen. | ### Investment vehicles and SPVs | **Area** | **Acceptable** | **Nested (avoid)** | | :- | :- | :- | | Onboarding | The fund or SPV is onboarded as its own customer, separate from the manager. | Only the fund manager is onboarded; investor money for multiple vehicles flows through the manager's account. | | Investor flow | Subscriptions and redemptions go through the fund's own account. | Subscription funds for multiple vehicles are pooled in the manager's account. | | Documentation | Subscription agreements and capital calls name the fund as counterparty. | Documents name the fund but funds clear through an unrelated entity. | ### Payment processors and PSPs | **Area** | **Acceptable** | **Nested (avoid)** | | :- | :- | :- | | Underlying customer visibility | Each legal entity whose funds are moved is separately onboarded with Lumx, or expressly approved under a written structure. | The processor uses one Lumx relationship to move funds for many merchants Lumx hasn't onboarded. | | Use of the platform | The processor operates only within the bounds of an approved model and documented control framework. | The processor effectively provides downstream access to Lumx without prior written approval. | | Documentation | Supporting records identify the same entity that owns the account and is sending or receiving funds. | Documentation names one party while payments are initiated from another, unrelated account. | ## When written approval may be required Some business models involve layered payment activity, agency, merchant acquisition, payout programs, or other arrangements that require a separate written agreement, enhanced due diligence, and additional oversight. In those cases, a structure that would otherwise look nested may be permitted only if Lumx has expressly approved it in writing and the customer operates strictly within that approved model. Operational convenience, internal sub-ledgers, or contractual arrangements with your own customers aren't enough. Visibility to Lumx, direct onboarding of the relevant party, and written approval where applicable remain the requirements. For pre-approval, email [compliance@lumx.io](mailto:compliance@lumx.io). ## Compliance checklist Before initiating a transaction, confirm: 1. The funds belong to the onboarded customer. 2. Invoices, contracts, or payment references name that same customer. 3. The counterparty (sender or recipient) is either the customer itself or a disclosed, approved third party (e.g. a registered destination holder). 4. No undisclosed business is collecting or distributing funds through the account. ## Consequences of non-compliance Transactions that don't meet the visibility requirement may be delayed, rejected, or reversed. Repeated or serious violations can result in account suspension or termination, and Lumx may report the activity to the relevant authorities. ## Related resources > [card] [Prohibited business activities](/compliance/prohibited-activities) > > Industries and activities Lumx doesn't support, including unlicensed money services. > [card] [Partner Fees](/concepts/partner-fees) > > How marketplaces and platforms collect revenue without nesting. > [card] [Marketplaces guide](/guides/use-cases/marketplaces) > > Reference flow for onboarding every seller as a customer. > [card] [Payroll guide](/guides/use-cases/payroll) > > Reference flow for employer payouts without pooling client funds. # Prohibited business activities Source: https://docs.lumx.io/compliance/prohibited-activities High-risk and prohibited industries under Lumx's AML/CFT, sanctions, and partner-bank policies Every customer has to fully disclose their business activities to Lumx during onboarding and on an ongoing basis. We use that disclosure to run the right risk assessment, apply the right controls, and stay compliant with AML/CFT, sanctions, and regulatory requirements. Not disclosing relevant activity can lead to onboarding rejection, account suspension, or termination. ## High-risk business activities Allowed with disclosure, risk assessment, and Enhanced Due Diligence. Being in one of the categories below doesn't disqualify you — it triggers EDD and ongoing monitoring. You have to disclose these activities up front. This list isn't exhaustive: * Money services, payment processing, or funds transmission (MSBs, PSPs, P2P platforms, prepaid or gift cards, ATM operators) * Licensed lending, foreign exchange, and securities brokerage * Virtual asset service providers (VASPs): crypto exchanges, OTC desks, wallet providers, custody or escrow, stablecoin issuers * Decentralized finance (DeFi), DAOs, foundations, gaming, metaverse, or other virtual asset ecosystems * Issuing, holding, safeguarding, or managing client funds, digital assets, stablecoins, or wallets * Trading, brokering, or dealing in high-value or trade-exposed goods — precious metals and stones, jewelry, oil and gas, mining, agriculture, construction, and real estate * Cash-intensive businesses — convenience stores, liquor retail, restaurants operating on cash, car washes * Licensed gambling, betting, or gaming operators * Crowdfunding platforms * Licensed investment advisors and asset managers * Businesses with complex or multi-layered ownership structures * Customers where a principal owner or controlling person is a politically exposed person (PEP) > [!NOTE] > > High-risk customers may be asked for additional documents via [Request for Information](/compliance/rfi). Plan for verification windows beyond the [standard SLA](/compliance/identity-verification#verification-slas). ## Prohibited business activities Not supported under any circumstances. Lumx doesn't serve businesses engaged in the following activities, directly or indirectly, regardless of jurisdiction or licensing claims. | **Category** | **Description** | | :- | :- | | Adult content and sexually oriented services | Businesses offering or facilitating sexually explicit, obscene, or adult-oriented content or services. | | Drugs, controlled substances, and unlicensed pharmaceuticals | Sale, distribution, or facilitation of illegal or controlled substances, including cannabis and unlicensed pharmaceuticals. | | Tobacco and tobacco-related products | Manufacture, distribution, or sale of tobacco products and related services. | | Weapons, ammunition, and explosives | Trading or dealing in weapons, firearms, ammunition, or weapon-related products. | | Unlicensed gambling, betting, and games of chance | Any business involving unlicensed or prohibited games of chance, betting, or casinos. Licensed operators fall under [high-risk](#high-risk-business-activities). | | Fraudulent and predatory financial models | Ponzi schemes, pyramid schemes, deceptive practices, and abusive or predatory lending. | | Unlicensed money services | Money transmitters, payment processors, virtual asset providers, and remittance operators without the required licenses in their jurisdiction. | | Shell banks and unsupervised banking structures | Shell banks, offshore banks without physical presence, correspondent accounts for shell banks, payable-through accounts, and concentration accounts. See [Nested Payments](/compliance/nested-payments). | | Unlicensed investment products and funds | Non-deposit investment products, trust and asset-management services, and trade finance operations without the required licenses. | | Client trust and pass-through accounts | IOLTA-style lawyer trust accounts, escrow accounts on behalf of unidentified beneficiaries, and any structure where Lumx-held funds belong to a non-onboarded party. See [Nested Payments](/compliance/nested-payments). | | Hate, violence, terrorism, and discriminatory activity | Activities promoting or enabling harm, hate, exploitation, or terrorism, including known terrorist organizations. | | Sanctioned and illicit entities | Entities or individuals on OFAC, EU, UN, UK, or other sanctions lists, or operating from jurisdictions under comprehensive embargoes. See [Supported countries](/compliance/supported-countries). | | Identity fraud, anonymity misuse, and shell structures | Anonymous or fictitious accounts, bearer-share companies, or entities designed to obscure beneficial ownership. | | Intellectual property infringement and counterfeit goods | Entities selling, distributing, or enabling IP-infringing or counterfeit products. | | Data misuse and consumer privacy violations | Businesses that compromise customer data or privacy. | | Governmental, diplomatic, and political entities | Government bodies, embassies, consulates, supranational organizations, and diplomatic entities. | | Charities and NGOs | Non-governmental organizations, charitable foundations, and non-profit entities. | ## Consequences of violation If prohibited activity is found on a Lumx account: * The account is reviewed and may be suspended or terminated. * Funds may be frozen pending investigation. * Partners and authorities may be notified, in line with legal and contractual obligations. ## Reporting violations If you spot prohibited activity on the platform, email [compliance@lumx.io](mailto:compliance@lumx.io). ## Related resources > [card] [Supported countries](/compliance/supported-countries) > > Jurisdiction tiers and where Enhanced Due Diligence applies. > [card] [Identity Verification](/compliance/identity-verification) > > KYC and KYB requirements for individuals and businesses. > [card] [Nested Payments](/compliance/nested-payments) > > Visibility rules and why pass-through structures are restricted. > [card] [Request for Information](/compliance/rfi) > > Resolve RFIs raised during enhanced due diligence. # Proof of Address Source: https://docs.lumx.io/compliance/proof-of-address Documents Lumx accepts to verify business and individual addresses Lumx verifies where a business operates and where the individuals tied to an account live. This page lists what counts as proof of address, the standards every document has to meet, and how to submit. ## When proof of address is required * **Business verification.** Confirming the registered or operating address of the company. * **Individual verification.** Confirming the residential address of UBOs, representatives, and individual customers. * **Ongoing reviews.** Periodic compliance checks may request updated documents. ## Business proof of address The document must: * Confirm the current registered or operating address of the company. * Be issued in the legal name of the business, exactly as registered. * Be issued within the last 90 days, unless noted otherwise below. * Show a physical address. PO boxes and virtual addresses are not accepted. ### Accepted documents * Bank statement in the business name. * Utility bill for the business premises (electricity, water, gas, internet). * Government-issued correspondence addressed to the business. * Office or commercial lease agreement. Must be current; may be older than 90 days. * Business license showing the company address. * Company registry extract or registration certificate. * Tax authority correspondence showing the business address. ## Individual proof of address The document must: * Confirm the individual's current residential address. * Be issued in the individual's full legal name, matching the name submitted during onboarding. * Be issued within the last 90 days, unless noted otherwise. * Show a physical residential address. PO boxes and virtual addresses are not accepted. ### Accepted documents * Utility bill (electricity, water, gas, internet, or landline). * Bank or credit card statement. * Government-issued letter (tax notices, social benefit correspondence). * Residence registration or certificate of domicile. * Residential lease or tenancy agreement. Must be current. * Local authority or municipal tax bill. * Insurance policy showing a residential address (home, health, or vehicle). * Driver's license or national ID card, when it includes a full residential address and is still valid. ## Document standards Every document must clearly show: * The full legal name of the business or individual. * The complete address. * The date of issue. * The issuer's name or logo (bank, utility company, government body). ### Submission format * Clear color scan or photo of the original document. * PDF, JPG, or PNG. * Maximum file size: 3 MB. * The entire document visible in frame, with no cropped edges. ### What to avoid * Photos of a screen, monitor, or another device. * Screenshots or photos of printed or photocopied documents. * Blurry, dark, cropped, or otherwise unreadable images. > [!WARNING] > > Screenshots are not accepted unless explicitly pre-approved by Lumx compliance. ## Related resources > [card] [Identity Verification](/compliance/identity-verification) > > Data requirements and verification statuses. > [card] [Source of Funds and Wealth](/compliance/source-of-funds) > > Documentation accepted to verify source of funds and wealth. > [card] [Request for Information](/compliance/rfi) > > How Lumx notifies you when documents are needed. > [card] [Document Types](/additional-information/document-types) > > Enum reference for the `type` field on document uploads. # Request for Information Source: https://docs.lumx.io/compliance/rfi Respond to additional information requests during verification A Request for Information (RFI) is how Lumx's compliance team asks for missing or corrected details during a KYC/KYB review, account provisioning, or transaction monitoring. Instead of rejecting outright, the affected object pauses and waits for you to submit what's needed. RFIs can be raised at three levels: customer, account, and transaction. ## Customer RFI When compliance needs additional or corrected information on a customer's KYC/KYB review, the customer moves to `RFI`. The `statusReason` payload spells out which documents or fields are at issue. ### Trigger Subscribe to the `customer.rfi` [webhook](/developer/webhooks#available-events) to be notified the moment the customer enters this state. ```json customer.rfi event (excerpt) theme={null} { "eventType": "customer.rfi", "data": { "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "status": "rfi", "statusReason": { "rejectLabels": ["DOCUMENT_QUALITY"], "rejectSubLabels": ["DOCUMENT_BLURRY"], "documents": [ { "type": "PASSPORT", "status": "DECLINED", "comment": "Document is blurry and illegible", "rejectLabels": ["DOCUMENT_QUALITY"] } ] } } } ``` ### Inspecting what's needed `statusReason` has top-level `rejectLabels` and `rejectSubLabels` describing what went wrong, plus a `documents` array with per-document issues. If the rejection involves an associated party (UBO, shareholder, representative), that party shows up under `associatedParties` with its own `statusReason`. You can also read the customer at any time to see the current state and labels: ```bash Request theme={null} curl https://api-sandbox.lumx.io/customers/3c90c3cc-0d44-4b50-8888-8dd25736052a \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Resolving the RFI To resolve a customer RFI, re-submit the corrected information through the same endpoints used during the original onboarding and then start a new verification: 1. If additional information needs to be corrected, send `PATCH /customers/{id}/additional-information`. 2. If a document needs to be replaced, send `POST /customers/{id}/documents`. 3. If an associated party's documents are flagged, re-upload via the associated-party endpoints. 4. Start a new verification with `POST /customers/{id}/verifications`. The customer moves back into `UNDER_VERIFICATION`. On approval you'll get `customer.approved`. If more issues are found, you'll get another `customer.rfi`. See [Business verification](/guides/business-verification#handling-rejections) for the end-to-end resolution example. > [!WARNING] > > A `FINAL_REJECTION` is permanent. The customer can't resubmit and can't be re-verified. ## Account RFI Virtual accounts stay in `AWAITING_ONBOARDING` until the customer is approved, then move to `REQUESTED` and start provisioning. When compliance needs more information to provision an account, its status moves to `RFI`. ### Trigger Subscribe to the `account.rfi` [webhook](/developer/webhooks#available-events). ```json account.rfi event theme={null} { "eventType": "account.rfi", "data": { "id": "ce4c89f3-60f2-45a3-9de7-415a462dd95c", "customerId": "96fec1e2-78d9-4978-813d-674bba64020d", "status": "RFI", "currency": "USD" } } ``` ### Resolving the RFI When an account enters `RFI`, Lumx's compliance team reaches out on your usual channel (email or Slack) with the specific information needed. You respond on the same channel. Once compliance reviews it, the account moves back through `PROVISIONING` and emits `account.active` when it's ready to receive funds. An RFI that can't be resolved ends in `REJECTED` (`account.rejected` webhook). > [!NOTE] > > An API-driven flow for account RFIs is coming. Until then, resolution stays on email/Slack. ## Transaction RFI On-ramps and off-ramps flagged by Lumx's transaction monitoring are put on hold pending review. Holds prevent fraud and money laundering and protect Lumx's relationship with its banking partners. ### Resolving the RFI Compliance manually reviews the flagged transaction. If it isn't a false positive, they reach out on your usual channel (email or Slack) with the transaction details and an RFI. They usually ask for: * The relationship between the sender and the receiver. * The purpose of the transaction. * The expected outcome. * Any other context that helps clarify what's going on. Once you provide the information, compliance reviews it and decides whether to release the transaction. > [!WARNING] > > If you don't respond within 24 hours, the transaction may be refunded to the sender. > [!NOTE] > > An API-driven flow for transaction RFIs is coming. Until then, resolution stays on email/Slack. ## Related resources > [card] [Identity Verification](/compliance/identity-verification) > > Data requirements and verification statuses. > [card] [Source of Funds and Wealth](/compliance/source-of-funds) > > Documentation accepted to verify source of funds and wealth. > [card] [Webhooks](/developer/webhooks) > > Full list of customer and account events. > [card] [Business verification](/guides/business-verification) > > End-to-end onboarding flow for business customers. # Source of Funds and Wealth Source: https://docs.lumx.io/compliance/source-of-funds Documentation Lumx accepts to verify where a customer's funds and wealth come from When a customer is routed into enhanced due diligence (EDD) at onboarding, or flagged during ongoing transaction monitoring, Lumx may request documentation showing where their funds come from and how their wealth was built. This page lists what to provide. ## Source of funds vs. source of wealth * **Source of funds.** How the specific money moving through Lumx was generated. For example: operating revenue, customer payments, or investment capital. * **Source of wealth.** How the customer or its owners accumulated their overall wealth. For example: business earnings, prior ventures, or long-term investments. Both may be requested during onboarding or as part of an ongoing review. ## When this is requested Lumx asks for source of funds or wealth documentation in a few scenarios: * The customer falls into an EDD tier based on country, industry, or risk score. See [Supported countries](/compliance/supported-countries) for the country-level tiers. * High transaction volumes or complex funding structures need additional validation. * A material change in expected activity, ownership, or business model. * A regulatory or compliance obligation requires it. When documentation is needed, you'll be notified through an [RFI](/compliance/rfi) listing the specific documents to submit. ## Acceptable documentation Documents should clearly show where funds come from, how they were generated, and how they'll move to Lumx. Everything should align with the customer's declared business activity. ### Fiat funding sources | **Category** | **What it shows** | | :- | :- | | Bank statements or account summaries | The account funds will move from, issued by a regulated financial institution. | | Investment or capital contribution records | Subscription agreements, capital injections, or founder contribution confirmations. | | Revenue documentation | Invoices, customer contracts, settlement statements, or receipts demonstrating operating income. | | Loan or financing agreements | Executed loan agreements or credit facilities showing lawful borrowing. | | Asset sale records | Proceeds from the sale of business assets, securities, or property. | ### Crypto and digital asset funding sources | **Category** | **What it shows** | | :- | :- | | Wallet ownership evidence | Cryptographic proof that the customer controls the wallet(s) funds are moving from. | | On-chain transaction history | Origin and movement of funds, including transaction hashes and wallet addresses. | | Exchange account statements | Balances, deposits, withdrawals, or trading activity from regulated exchanges. | | Stablecoin issuance or redemption records | How stablecoins were minted, acquired, or redeemed. | | Market-making or trading records | Trade history, P\&L summaries, or liquidity provision records. | | Token sale or fundraising documentation | Allocation summaries, private placement records, and use-of-funds explanations. | ## Document standards To keep review moving, make sure each document: * Is issued in the name of the business or the relevant funding entity. * Clearly shows the source and flow of funds. * Is legible and complete, with no material redactions. * Is submitted as PDF, JPG, or PNG. > [!NOTE] > > Lumx may request additional supporting documentation if more clarification is needed during review. ## Related resources > [card] [Request for Information](/compliance/rfi) > > How Lumx notifies you when documents are needed. > [card] [Identity Verification](/compliance/identity-verification) > > KYC and KYB requirements per customer type. > [card] [Document Types](/additional-information/document-types) > > Enum reference for the `type` field on document uploads. > [card] [Onboarding Fields](/additional-information/onboarding-fields) > > Self-disclosure enums including `sourceOfFunds`. # Supported countries Source: https://docs.lumx.io/compliance/supported-countries Where Lumx operates, where Enhanced Due Diligence applies, and where onboarding is restricted Every jurisdiction falls into one of three tiers. The tier determines whether you can onboard a customer based there and how much compliance review they go through. Tiers reflect Lumx's internal risk appetite, partner bank requirements, and external references (FATF guidance, UN, U.S. OFAC, EU, and UK sanctions lists), and are reassessed as those inputs change. ## Tiers | **Tier** | **What it means** | | :- | :- | | Supported | Lumx onboards customers under standard KYC/KYB. | | High-Risk (EDD Required) | Onboarding allowed, but every customer goes through Enhanced Due Diligence: deeper risk assessment, beneficial-owner analysis, ongoing monitoring. | | Restricted | No transactions, partnerships, or business relationships with parties based in, incorporated in, or operating from these locations. | ## Supported Standard compliance procedures apply. | **Country** | **Code** | | :- | :- | | Albania | AL | | Andorra | AD | | Antigua and Barbuda | AG | | Argentina | AR | | Armenia | AM | | Australia | AU | | Austria | AT | | Azerbaijan | AZ | | Bahamas | BS | | Bahrain | BH | | Barbados | BB | | Belgium | BE | | Belize | BZ | | Benin | BJ | | Bhutan | BT | | Bosnia and Herzegovina | BA | | Botswana | BW | | Brazil | BR | | Brunei | BN | | Burundi | BI | | Cabo Verde | CV | | Cambodia | KH | | Canada | CA | | Chad | TD | | Chile | CL | | Comoros | KM | | Congo (Republic of) | CG | | Costa Rica | CR | | Cyprus | CY | | Czech Republic | CZ | | Denmark | DK | | Djibouti | DJ | | Dominica | DM | | Dominican Republic | DO | | El Salvador | SV | | Equatorial Guinea | GQ | | Eritrea | ER | | Estonia | EE | | Eswatini | SZ | | Ethiopia | ET | | Fiji | FJ | | Finland | FI | | France | FR | | Gabon | GA | | Gambia | GM | | Georgia | GE | | Germany | DE | | Ghana | GH | | Greece | GR | | Grenada | GD | | Guatemala | GT | | Guinea | GN | | Guinea-Bissau | GW | | Guyana | GY | | Honduras | HN | | Hungary | HU | | Iceland | IS | | India | IN | | Indonesia | ID | | Ireland | IE | | Israel | IL | | Italy | IT | | Jamaica | JM | | Japan | JP | | Jordan | JO | | Kazakhstan | KZ | | Kiribati | KI | | Kuwait | KW | | Kyrgyzstan | KG | | Latvia | LV | | Lesotho | LS | | Liberia | LR | | Liechtenstein | LI | | Lithuania | LT | | Luxembourg | LU | | Madagascar | MG | | Malawi | MW | | Malaysia | MY | | Maldives | MV | | Malta | MT | | Marshall Islands | MH | | Mauritania | MR | | Mauritius | MU | | Mexico | MX | | Micronesia | FM | | Moldova | MD | | Mongolia | MN | | Montenegro | ME | | Nauru | NR | | Netherlands | NL | | New Zealand | NZ | | Nicaragua | NI | | Niger | NE | | Norway | NO | | Oman | OM | | Pakistan | PK | | Palau | PW | | Papua New Guinea | PG | | Paraguay | PY | | Peru | PE | | Philippines | PH | | Poland | PL | | Portugal | PT | | Qatar | QA | | Romania | RO | | Rwanda | RW | | Saint Kitts and Nevis | KN | | Saint Lucia | LC | | Saint Vincent and the Grenadines | VC | | Samoa | WS | | San Marino | SM | | São Tomé and Príncipe | ST | | Saudi Arabia | SA | | Senegal | SN | | Serbia | RS | | Seychelles | SC | | Sierra Leone | SL | | Singapore | SG | | Slovakia | SK | | Slovenia | SI | | Solomon Islands | SB | | South Africa | ZA | | South Korea | KR | | South Sudan | SS | | Spain | ES | | Sri Lanka | LK | | Suriname | SR | | Sweden | SE | | Switzerland | CH | | Tajikistan | TJ | | Thailand | TH | | Timor-Leste | TL | | Togo | TG | | Tonga | TO | | Trinidad and Tobago | TT | | Tunisia | TN | | Turkey | TR | | Turkmenistan | TM | | Tuvalu | TV | | Uganda | UG | | Ukraine\* | UA | | United Arab Emirates | AE | | United Kingdom | GB | | United States | US | | Uruguay | UY | | Uzbekistan | UZ | | Vanuatu | VU | | Zambia | ZM | > [!NOTE] > > \*Crimea, Donetsk, Luhansk, and any areas not under Ukrainian government control are restricted regardless of the country-level classification. ## High-Risk (EDD Required) Onboarding is allowed, but every customer goes through Enhanced Due Diligence. Expect additional documents, beneficial-owner analysis, and longer verification windows. | **Country** | **Code** | | :- | :- | | Angola | AO | | Bolivia | BO | | Bulgaria | BG | | Burkina Faso | BF | | Cameroon | CM | | Central African Republic | CF | | China | CN | | Colombia | CO | | Côte d'Ivoire | CI | | Croatia | HR | | Democratic Republic of the Congo | CD | | Haiti | HT | | Iraq | IQ | | Kenya | KE | | Laos | LA | | Libya | LY | | Mali | ML | | Monaco | MC | | Mozambique | MZ | | Namibia | NA | | Nigeria | NG | | Panama | PA | | Somalia | SO | | Tanzania | TZ | | Vietnam | VN | | Yemen | YE | | Zimbabwe | ZW | > [!NOTE] > > EDD customers may be asked for additional documents via [Request for Information](/compliance/rfi). Plan for verification windows beyond the [standard SLA](/compliance/identity-verification#verification-slas). ## Restricted No transactions, partnerships, or business relationships allowed. Onboarding a customer with any restricted nexus (residence, incorporation, principal place of business, or beneficial ownership) will be rejected. | **Country / Region** | **Code** | | :- | :- | | Afghanistan | AF | | Algeria | DZ | | Bangladesh | BD | | Belarus | BY | | Crimea, Donetsk, Luhansk (Ukraine) | — | | Cuba | CU | | Ecuador | EC | | Egypt | EG | | Iran | IR | | Lebanon | LB | | Macau | MO | | Morocco | MA | | Myanmar (Burma) | MM | | Nepal | NP | | North Korea | KP | | North Macedonia | MK | | Russia | RU | | Sudan | SD | | Syria | SY | | Venezuela | VE | > [!WARNING] > > Hidden exposure detected after onboarding can result in account suspension, freezing, or termination. ## Updates and exceptions * Classifications get reassessed as sanctions regimes, FATF guidance, and partner bank policies change. * Lumx may approve or refuse onboarding outside the standard tier in limited cases, based on a customer's specific activity or counterparty exposure. * A country being Supported means Lumx will onboard the customer. It doesn't guarantee a specific local payment rail. See [Coverage](/get-started/coverage) for rails and currencies by country. For grey-area cases or pre-approval requests, email [compliance@lumx.io](mailto:compliance@lumx.io). ## Related resources > [card] [Coverage](/get-started/coverage) > > Payment rails, currencies, and stablecoin pairs by country. > [card] [Identity Verification](/compliance/identity-verification) > > KYC and KYB requirements per customer type. > [card] [Transaction Limits](/compliance/transaction-limits) > > Default thresholds and how EDD affects limits. > [card] [Request for Information](/compliance/rfi) > > Resolve RFIs raised during enhanced due diligence. # Terms of Service Source: https://docs.lumx.io/compliance/terms-of-service How customers accept Lumx's terms of service before verification Every customer has to accept Lumx's [Terms of Service](https://lumx.io/terms-of-use) before they can complete identity verification and move money. Acceptance is collected programmatically through the API. Until the customer accepts, the `TERMS_OF_SERVICE` entry stays in their `requirements` array and verification can't proceed. ## Generating the acceptance link Send a `POST` request to `/customers/{id}/tos` to generate an acceptance URL for a specific customer. Optionally include a `redirectUrl` to send the customer back to your app after they accept. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers/3c90c3cc-0d44-4b50-8888-8dd25736052a/tos \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "redirectUrl": "https://yourapp.com/onboarding/next-step" }' ``` ```json Response theme={null} { "url": "https://dashboard.lumx.io/tos/eyJhbGciOiJSUzI1...", "expiresAt": "2026-04-09T16:00:00Z" } ``` The returned `url` expires after 24 hours. If it expires before the customer accepts, generate a new one. `redirectUrl` takes an `http` or `https` address. Custom schemes such as `myapp://` don't pass validation, and neither does `localhost` — point it at `127.0.0.1` when you're testing locally. ## Opening the acceptance page The acceptance flow lives on a Lumx-hosted page. Open the returned `url` somewhere the address bar is visible, so the customer can see they're accepting on Lumx's domain: either a full redirect or a separate window, depending on whether you can afford to leave the current screen. Sending the link over email or SMS works too. > [!WARNING] > > The page can't be embedded in an iframe. The response sets `X-Frame-Options: SAMEORIGIN`, so the browser refuses to render it on your domain and the customer gets an empty frame. ### Full redirect Send the customer straight to the URL. They accept and come back to your `redirectUrl` as a normal top-level navigation. Simplest option, but it unloads the current screen along with any unsaved form state. ```js Web theme={null} window.location.assign(tosUrl); ``` ### Separate window Open the URL in its own window and your screen stays loaded underneath, form state intact. Browsers only allow this from inside a user gesture, so open a blank window in the click handler and set its location once your backend returns the URL. ```js Web theme={null} button.addEventListener("click", async () => { const popup = window.open("", "lumx-tos", "width=520,height=760"); const { url } = await generateTosUrl(customerId); popup.location = url; }); ``` Point `redirectUrl` at a route on your own origin. It loads inside the same window, so it can message the opener and close. Run it before the page paints and the customer sees no extra screen: they accept, the window closes, your screen carries on. ```js Web theme={null} window.opener?.postMessage({ type: "tos-accepted" }, window.location.origin); window.close(); ``` ### In-app browser On mobile, open the URL in the platform's in-app browser. These are browser contexts too, not frames, so the page renders normally. Present it as a sheet rather than full screen and it reads as part of your app. ```swift iOS theme={null} import SafariServices let controller = SFSafariViewController(url: tosUrl) controller.modalPresentationStyle = .pageSheet present(controller, animated: true) ``` ```kotlin Android theme={null} CustomTabsIntent.Builder() .setInitialActivityHeightPx(900) .build() .launchUrl(context, Uri.parse(tosUrl)) ``` There's no opener to message here, and `redirectUrl` won't take a custom scheme, so point it at a universal link on iOS or an app link on Android. The operating system hands the customer back to your app and dismisses the browser. Skip that setup and you can detect the dismissal instead, then read the customer. ## Tracking acceptance status Terms of Service appears as a `TERMS_OF_SERVICE` entry inside the customer's `requirements` array, alongside other onboarding requirements. It moves to `APPROVED` once the customer accepts. The customer's `verification.link`, and the links of their associated parties, are omitted until `TERMS_OF_SERVICE` is `APPROVED`. Read the customer after acceptance to get the link for the hosted verification flow. Read the customer to confirm acceptance rather than trusting the redirect, since the customer can close the window before it completes. No webhook event is specific to Terms of Service, so subscribe to [customer webhooks](/developer/webhooks#available-events) to follow the rest of verification. ```bash Request theme={null} curl https://api-sandbox.lumx.io/customers/3c90c3cc-0d44-4b50-8888-8dd25736052a \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response (excerpt) theme={null} { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "requirements": [ { "name": "TERMS_OF_SERVICE", "status": "NOT_SENT" } ] } ``` ## Related resources > [card] [Legal Documents](/compliance/legal-documents) > > Full list of Lumx's legal documents and policies. > [card] [Identity Verification](/compliance/identity-verification) > > Data requirements for KYC/KYB after acceptance. > [card] [Individual verification](/guides/individual-verification) > > End-to-end onboarding flow for individual customers. > [card] [Business verification](/guides/business-verification) > > End-to-end onboarding flow for business customers. # Transaction Limits Source: https://docs.lumx.io/compliance/transaction-limits Understand transaction thresholds per transaction type and how to request higher limits Transaction limits cap how much a customer can move per transaction, per day, and per month. The level of KYC/B verification a customer goes through determines which limits apply. ## Limits per transaction type Each transaction type has its own set of limits: `onramp`, `offramp`, and `transfer`. A transaction only counts against the limits of its own type, so an on-ramp never uses up off-ramp or transfer headroom. | **Transaction** | **Limits it uses** | | :- | :- | | On-ramp | `onramp` | | Off-ramp | `offramp` | | Transfer | `transfer` | Each type has a `single` (per-transaction), `daily`, and `monthly` limit. ## Default limits | **Limit** | **KYC Standard** | **KYC Enhanced\*** | **KYB Standard** | **KYB Enhanced\*** | | :- | :- | :- | :- | :- | | Per Transaction | \$7,500 | \$25,000 + | \$50,000 | \$50,000 + | | Daily | \$15,000 | \$50,000 + | \$100,000 | \$100,000 + | | Monthly | \$15,000 | \$50,000 + | \$100,000 | \$100,000 + | \*Once a customer finishes Enhanced KYC/KYB, Lumx sets a limit that fits the financial capacity shown in the documents they submitted. ## Tracking usage To track a customer's limit consumption in real time, pass `includeTransactionLimits=true` when reading the customer. `transactionLimits` returns one entry per transaction type. Each entry has the `max` per-transaction limit and the `max`, `used`, and `remaining` amounts for its `daily` and `monthly` limits. Amounts are strings in USD with 2 decimal places. ```bash Request theme={null} curl https://api-sandbox.lumx.io/customers/3c90c3cc-0d44-4b50-8888-8dd25736052a?includeTransactionLimits=true \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response (excerpt) theme={null} { "transactionLimits": { "onramp": { "single": { "max": "7500.00" }, "daily": { "max": "15000.00", "used": "2300.00", "remaining": "12700.00" }, "monthly": { "max": "15000.00", "used": "2300.00", "remaining": "12700.00" } }, "offramp": { "single": { "max": "7500.00" }, "daily": { "max": "15000.00", "used": "0.00", "remaining": "15000.00" }, "monthly": { "max": "15000.00", "used": "4000.00", "remaining": "11000.00" } }, "transfer": { "single": { "max": "7500.00" }, "daily": { "max": "15000.00", "used": "0.00", "remaining": "15000.00" }, "monthly": { "max": "15000.00", "used": "0.00", "remaining": "15000.00" } } } } ``` Before you create a transaction, check the entry that matches its type. ## When a transaction exceeds a limit A transaction that would pass its type's `single`, `daily`, or `monthly` limit is rejected with `422` and the `TRANSACTION_LIMIT_EXCEEDED` code. The message names the transaction type and the limit that was hit: ```json Response theme={null} { "requestId": "9f6f9741-4c65-4e0b-a48f-16d5b34d9e2f", "timestamp": "2026-10-28T10:30:45.123Z", "path": "/transactions/off-ramp", "status": 422, "code": "TRANSACTION_LIMIT_EXCEEDED", "message": "This transaction would pass the offramp daily limit of $2000 for this customer. Current daily total: $1500." } ``` Match on `code`, not on `message`. To create the transaction, wait until the daily or monthly window has room, lower the amount, or [request higher limits](#requesting-higher-limits) for that transaction type. See [Errors](/developer/errors) for the full error format. ## Requesting higher limits To unlock higher transaction limits, the customer needs to go through Enhanced KYC/B verification. That runs directly with the Compliance team. ### How to request Limit increase requests are submitted from the [Lumx Dashboard](https://dashboard.lumx.io), in the customer's overview page. 1. Open the customer and click **Limit Increase Request**. 2. Choose the transaction type and enter the new per-transaction, daily, and monthly limits being requested. 3. Upload a supporting document that justifies the new limits. PDF, JPG, JPEG, or PNG, up to 10 MB. 4. Submit the request. Partners integrating directly can submit the same request through the API. Follow the [Limit Increase Requests guide](/guides/limit-increase-requests). Each request targets one transaction type (`ONRAMP`, `OFFRAMP`, or `TRANSFER`), and a customer can have one pending request per type. An approval changes only the limits of that type. ### Accepted supporting documents The document has to help Lumx compliance validate the requested limits. Accepted types depend on the customer profile: | **Individual customers** | **Business customers** | | :- | :- | | Bank statement | Bank statement | | Tax return | Financial statements | | Proof of income | Tax return | ### Review outcomes Lumx compliance reviews the document and decides the outcome. The request page shows the current status and a timeline of events. Possible outcomes: * `Approved`: the new limits are granted in full and become active immediately. * `Partially approved`: compliance grants limits below what was requested (e.g. 70,000 USD on a 100,000 USD request) based on what the supporting document justifies. The approved limits become active immediately. * `Rejected`: the new limits aren't granted. The customer stays on its current limits. * `RFI`: compliance needs additional or corrected documentation before deciding. Resubmit through the same flow. ### Review timeline | **Service** | **Timeline** | | :- | :- | | Limit increase | 1 business day | More complex cases (large step-ups, unusual document types, or requests that touch enhanced due diligence) can take longer. Target, not a contractual guarantee. # Accounts Source: https://docs.lumx.io/concepts/accounts Send and receive fiat with virtual accounts tied to a customer A virtual account is a fiat account issued by a Lumx banking partner in your customer's name to send and receive payments in a local currency. Each account is tied to one currency and supports every rail the partner bank offers for it. A USD account, for example, supports ACH, Fedwire, and SWIFT on the same account. Lumx orchestrates account provisioning and verification with banking partners that hold the funds and execute the underlying rails. Accounts carry their own verification status, independent from the customer's. A customer can hold multiple accounts across different currencies. See [Coverage](/get-started/coverage) for supported currencies and rails. > [!TIP] > > To convert every deposit that lands in an account to stablecoin automatically, set up an [autoconversion rule](/guides/autoconversion-rules) instead of running a conversion per deposit. ## Lifecycle An account is created together with its customer and waits for the customer's verification before provisioning starts. The lifecycle has these statuses: | **Status** | **Description** | | :- | :- | | `AWAITING_ONBOARDING` | Account exists but the customer has not completed verification yet. | | `REQUESTED` | Customer approved. Provisioning has been requested. | | `PROVISIONING` | Account is being provisioned and verified. | | `RFI` | Lumx requested additional details to proceed. | | `ACTIVE` | Verification complete. Account can move funds. | | `REJECTED` | The account application was rejected. | | `INACTIVE` | The account was deactivated. | The happy path is `AWAITING_ONBOARDING` → `REQUESTED` → `PROVISIONING` → `ACTIVE`. Provisioning can pause in `RFI` when more information is needed, and can end in `REJECTED`. An account moves to `INACTIVE` when it's closed or when its customer becomes inactive. > [!NOTE] > > If an account moves to `RFI`, you'll be notified of the specific information needed. Provide the requested details to continue verification. > [!NOTE] > > Subscribe to [webhooks](/developer/webhooks) for real-time notifications when an account's status changes. ## Activation timelines The time an account takes to move from `REQUESTED` to `ACTIVE` once the customer is approved varies by currency, because Lumx has direct reliance with local banking partners in some markets but not others. | **Currency** | **Target** | **Why** | | :- | :- | :- | | BRL | Immediate | Direct reliance with local banking partner. Provisioning completes right after the customer is `APPROVED`. | | MXN | Immediate | Direct reliance with local banking partner. Provisioning completes right after the customer is `APPROVED`. | | EUR | Immediate | Direct reliance with local banking partner. Provisioning completes right after the customer is `APPROVED`. | | GBP | Immediate | Direct reliance with local banking partner. Provisioning completes right after the customer is `APPROVED`. | | USD | Up to 1 business day | Banking partner reviews before the account moves to `ACTIVE`. | Subscribe to the `account.active` [webhook](/developer/webhooks) to know the moment an account is ready to receive funds. ## Related resources > [card] [Customers](/concepts/customers) > > The legal entity that owns the account. > [card] [Transactions](/concepts/transactions) > > Move funds in and out of accounts. > [card] [Destinations](/concepts/destinations) > > External payout targets for outbound fiat. > [card] [Coverage](/get-started/coverage) > > Supported rails and currencies by country. # Customers Source: https://docs.lumx.io/concepts/customers Understand customer entities and verification workflows A customer is the legal owner of wallets, balances, and transaction history. Customers can be one of the following: * Your own business entity, when moving funds for your own operations. * The end users (individuals or businesses) onboarded through your platform. Each customer carries its own compliance status, wallets, and transaction history. ## Verification Before a customer can transact, they complete identity verification (KYC). The flow has five statuses: | **Status** | **Description** | | :- | :- | | `NOT_STARTED` | Customer created, verification pending. | | `UNDER_VERIFICATION` | Documents submitted, under review. | | `APPROVED` | Verification complete, customer can transact. | | `RFI` | Submitted information is invalid or inconsistent. The customer can resubmit corrected documents. | | `FINAL_REJECTION` | Customer permanently rejected. | > [!NOTE] > > If a customer moves to `RFI`, you'll be notified of the specific documents needed. Resubmit them to continue verification. For supported documents and compliance requirements by jurisdiction, see [Identity Verification](/compliance/identity-verification). ## Stablecoin wallet Creating a customer provisions a wallet on every supported blockchain. Wallets are enabled for use once the customer reaches `APPROVED`. Once active, they hold stablecoin balances, send and receive crypto payments, and power instant payouts via [prefunded balances](/concepts/wallets#prefunded-wallets). See [Stablecoin Wallets](/concepts/wallets) for the full schema and lifecycle. > [!NOTE] > > The `wallets` array is only returned on customer responses once verification reaches `APPROVED`. ## Virtual accounts A customer can also hold virtual fiat accounts to send and receive local fiat. Each account is tied to one payment rail and currency, with its own verification status independent from the customer's. See [Accounts](/concepts/accounts) for the full schema and lifecycle. # Destinations Source: https://docs.lumx.io/concepts/destinations Configure destinations and holder relationships for payouts A destination defines where fiat funds land when an off-ramp completes. Each destination is tied to a customer and holds: * Account holder details and banking information for the receiving bank. * The relationship between the customer and the account holder (see below). Supported rails depend on the destination's country and currency — see [Coverage](/get-started/coverage). ## Holder relationships When you add a destination to a customer, you have to specify the relationship between the customer and the account holder. ### Self payments If the customer is sending funds to their own destination, use: | **Relationship** | **Use case** | | :- | :- | | `SELF` | Withdrawals and transfers to the customer's own destination for personal or business use | ### Third-party payments If the customer is paying a third party (suppliers, employees, family members, etc.), use one of these relationships: | **Relationship** | **Use case** | | :- | :- | | `HOLDING_COMPANY` | Parent or controlling company that owns the customer entity | | `SUBSIDIARY_COMPANY` | Subsidiary or controlled entity owned by the customer | | `BRANCH_OFFICE` | Branch, regional office, or division of the customer's organization | | `BUSINESS_PARTNER` | Strategic partners, joint venture collaborators, or business associates | | `SUPPLIER` | Vendors, suppliers, or service providers for goods and services purchased | | `CUSTOMER` | Customer's clients for refunds, rebates, or other customer obligations | | `CREDITOR` | Lenders, financial institutions, or parties to whom the customer owes money | | `DEBTOR` | Parties who owe money to the customer (collections or debt settlements) | | `FRANCHISEE` | Franchisees operating under the customer's brand or business model | | `EMPLOYEE` | Employees or contractors paid by the customer (payroll, salaries, reimbursements) | | `RELATIVE` | Family members of the customer (remittances, personal transfers) | | `FRIEND` | Personal acquaintances of the customer (P2P transfers, gifts) | Example: a Brazilian company paying a supplier in the United States would create a destination with `SUPPLIER` as the holder relationship and provide the supplier's US bank details. ## Related resources > [card] [Transactions](/concepts/transactions) > > Route payouts to a destination. > [card] [Customers](/concepts/customers) > > The legal entity that owns the destination. > [card] [Nested Payments](/compliance/nested-payments) > > Why holder relationships have to be disclosed and accurate. > [card] [Coverage](/get-started/coverage) > > Supported rails by country and currency. # Exchange Rates Source: https://docs.lumx.io/concepts/exchange-rates Understand floating and locked exchange rates for currency conversions An exchange rate sets the conversion value between currencies when you run a transaction. Two conversion directions are supported: * Fiat to stablecoin (e.g. USD to USDC). * Stablecoin to fiat (e.g. USDT to BRL). Rates move with market conditions, liquidity, and the pair you're converting. ## Exchange rate types ### Floating rate A floating rate is a real-time reference rate without a price lock. Use it to show estimated conversion amounts in your UI. The actual rate applied at execution time may vary slightly with market conditions. No fee. Example: display an estimated conversion amount before a user confirms a transaction. ### Locked rate A locked rate guarantees the exact conversion rate for a specific time window. When you request one, you get an `exchangeRateId` that locks in the quoted rate. Pass that ID on the transaction request to settle at the quoted rate. Available durations: 30s, 1m, and 5m. The 30-second lock has no fee. Longer durations include a fee to cover volatility risk. If the lock expires before the transaction is submitted, the `exchangeRateId` is no longer valid — request a new rate to continue. Example: quote a guaranteed price to a customer so they have time to review and confirm before the rate expires. Request a locked rate: ```bash POST /exchange-rates theme={null} curl https://api-sandbox.lumx.io/exchange-rates \ -X POST \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "type": "LOCKED", "rail": "PIX", "sourceCurrency": "BRL", "targetCurrency": "USDC", "sourceAmount": "59255.50", "timelock": "1m" }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174001", "type": "LOCKED", "rail": "PIX", "sourceCurrency": "BRL", "sourceAmount": "59255.50", "targetCurrency": "USDC", "baseTargetAmount": "10000.000000", "baseExchangeRate": "5.9255", "finalTargetAmount": "9978.000000", "finalExchangeRate": "5.9386", "expiresAt": "2026-05-05T00:00:00Z" } ``` Pass the returned `id` as `exchangeRateId` on the transaction request to settle at the quoted rate: ```json POST /transactions (excerpt) theme={null} { "exchangeRateId": "123e4567-e89b-12d3-a456-426614174001" } ``` ## Rate comparison | **Feature** | **Floating rate** | **Locked rate** | | :- | :- | :- | | Rate guarantee | No | Yes | | Lock duration | N/A | 30s, 1m, 5m | | Amount inputs | Source amount only | Source or target amount | | Expiration | No | Yes | | Additional fees | No | No for 30s, Yes for 1m+ | | Best for | Immediate execution, low friction | Guaranteed pricing, exact quotes | ## Related resources > [card] [Transactions](/concepts/transactions) > > Apply exchange rates when moving funds between currencies. > [card] [Coverage](/get-started/coverage) > > Supported currency pairs and conversion routes. # Partner Fees Source: https://docs.lumx.io/concepts/partner-fees Earn revenue by adding fees to transactions processed through Lumx A partner fee is an extra charge you add on top of a transaction to earn revenue. You define the fee structure, Lumx collects it from your customers and shares it with you. Each configuration has: * A name to identify it. * Fee rates for on-ramp and off-ramp transactions. * A wallet address where collected fees are sent. You can create multiple partner fees for different use cases and customers. ## Fee types Two fee types, configured independently for on-ramp and off-ramp: | **Type** | **Description** | **Example** | | :- | :- | :- | | Rate | Percentage of the transaction amount, in basis points (bps). | 75 bps = 0.75% | | Flat | Fixed amount per transaction, in dollars. | \$1.00 per transaction | You can combine both. For example, charge 50 bps plus a \$0.50 flat fee per transaction. ## Default fee You can mark one partner fee as default (`isDefault: true`). When a transaction or exchange rate request doesn't include a `partnerFeeId`, Lumx applies the default fee. Useful when you want consistent pricing across transactions without specifying the fee each time. ## Configuring partner fees Create and manage partner fees in the [Dashboard](https://dashboard.lumx.io/?to=%2Fsettings%2Fpartner-fees) or through the API. > [!NOTE] > > You need to be a project admin or owner to create partner fees. ## When fees are applied How you apply a partner fee depends on which exchange rate type you use: | **Rate type** | **When to include `partnerFeeId`** | | :- | :- | | Locked rate | When requesting the exchange rate. The fee becomes part of the locked quote. | | Floating rate | When creating the transaction, or omit it to use your default fee. | ## Fee destination Collected fees are sent to the wallet address you specify. Supported networks: * EVM-compatible wallets (Ethereum, Polygon, Base, etc.) * TVM-compatible wallets (Tron) * Stellar wallets You can use a Lumx-generated wallet by creating a customer from your entity, or use your own treasury wallet. > [!TIP] > > Test fee collection in sandbox before going live. ## Related resources > [card] [Transactions](/concepts/transactions) > > Where partner fees are applied and collected. > [card] [Exchange Rates](/concepts/exchange-rates) > > Locked rates can include the fee in the quote. # Transactions Source: https://docs.lumx.io/concepts/transactions Understand transaction types and lifecycle for moving funds A transaction moves value on the platform — converting between fiat and stablecoin, or transferring stablecoin between wallets. Each one has: * A type defining the direction of the conversion. * Source and target currencies with amounts. * A lifecycle status tracking progress. * Payment details and receipts on completion. Transactions require an approved [customer](/concepts/customers) and support every payment rail available for the currency pair. See [Coverage](/get-started/coverage) for the full matrix. ## Transaction types | **Type** | **Description** | **Example** | | :- | :- | :- | | On-ramp | Fiat to stablecoin. The customer sends fiat and receives stablecoins in their wallet. | Deposit BRL via PIX, receive USDC. | | Off-ramp | Stablecoin to fiat. The customer's stablecoins are sent to a [destination](/concepts/destinations). | Convert USDC to USD via wire transfer. | | Transfer | Moves stablecoins between wallets without conversion. To another customer or external address. | Send USDC to another customer's wallet. | On-ramps run on demand or automatically: an [autoconversion rule](/guides/autoconversion-rules) converts every fiat deposit that lands in an account to stablecoin, with no per-transaction call. ## Transaction lifecycle Each transaction type has its own status progression: > [tab] On-ramp > > `AWAITING_FUNDS` → `TRANSFERRING_FIAT` → `TRADING` → `TRANSFERRING_STABLECOIN` → `SUCCESS` > [tab] Off-ramp > > `TRANSFERRING_STABLECOIN` → `TRADING` → `TRANSFERRING_FIAT` → `SUCCESS` > [tab] Transfer > > `TRANSFERRING_STABLECOIN` → `SUCCESS` > [!NOTE] > > Subscribe to [webhooks](/developer/webhooks) to receive real-time > notifications when transaction statuses change. ## Transaction purpose Every on-ramp and off-ramp carries a `purpose` field — the end-use category of the funds. Banking partners and correspondent banks on the rail use it for AML screening, cross-border reporting, and how the receiving institution classifies the deposit. Pick the value that matches what the funds are actually being used for; mismatches can hold up the transaction or trigger an [RFI](/compliance/rfi). | **Purpose** | **Use case** | | :- | :- | | `PERSONAL_ACCOUNT` | Personal transfers to own accounts (requires `SELF` destination relationship) | | `INVESTMENT` | Investment-related transactions | | `REAL_ESTATE` | Real estate purchases or payments | | `TRADE_TRANSACTIONS` | Commercial trade and goods | | `TAX` | Tax payments | | `LOAN` | Loan disbursements or repayments | | `BILLS` | Bill payments | | `EXPENSES_REIMBURSEMENT` | Expense reimbursements | | `PROFESSIONAL_SERVICES` | Professional service payments | ## Payment expiration On-ramp transactions have a payment window. If the customer doesn't complete the fiat payment in time, the transaction expires. | **Exchange rate type** | **Expiration** | | :- | :- | | Locked | Matches the exchange rate quote expiration. | | Floating (PIX, SPEI) | 1 day. | | Floating (other rails) | 7 days. | ### Late deposits If a fiat deposit lands after the transaction has expired, Lumx doesn't process the on-ramp. The funds are refunded to the original sender on the same rail used for the deposit. Start a new on-ramp if the customer still wants to convert. ## Related resources > [card] [Customers](/concepts/customers) > > Approved customers are required to run transactions. > [card] [Exchange Rates](/concepts/exchange-rates) > > Floating and locked rates that drive conversion pricing. > [card] [Destinations](/concepts/destinations) > > Where off-ramp funds land. > [card] [Payment rail cut-off times](/additional-information/sla-and-cutoffs) > > Settlement windows and rail-by-rail deadlines. # Stablecoin Wallets Source: https://docs.lumx.io/concepts/wallets Hold stablecoin balances and trigger payouts in a single API call Every Lumx customer gets a wallet on each supported blockchain, provisioned at the same time as the customer. Wallets are only enabled for use once the customer reaches `APPROVED` — see [Identity Verification](/compliance/identity-verification). Once active, the wallet holds stablecoin balances between on-ramps, transfers, and off-ramps, so you can fund a customer once and pay out from the same balance as many times as you need. ## Wallet schema Each wallet has: * `blockchain`: the network the address belongs to. One of `ETHEREUM`, `POLYGON`, `BASE`, `TRON`, or `STELLAR`. * `address`: the on-chain address. Holds balances and can receive direct transfers. * `blockExplorerUrl`: deep link to the address on the relevant block explorer. * `isDefault`: the wallet Lumx uses when an on-ramp, off-ramp, or exchange rate request doesn't specify a `blockchain`. * `balances`: current stablecoin holdings on that wallet. Each entry has `currency` (`USDC` or `USDT`), `amount`, and `updatedAt`. ```json Wallets on a customer response theme={null} { "wallets": [ { "blockchain": "POLYGON", "address": "0x1234567890123456789012345678901234567890", "blockExplorerUrl": "https://polygonscan.com/address/0x1234567890123456789012345678901234567890", "isDefault": true, "balances": [ { "currency": "USDC", "amount": "12500.00", "updatedAt": "2026-05-25T14:32:00Z" }, { "currency": "USDT", "amount": "0.00", "updatedAt": "2026-05-25T14:32:00Z" } ] } ] } ``` > [!NOTE] > > The `wallets` array is omitted on the [Create a customer](/api-reference/customers/create-a-customer) response. Fetch it later via [Read a customer](/api-reference/customers/read-a-customer) or subscribe to the `customer.approved` [webhook](/developer/webhooks). ## Supported networks and stablecoins Wallets support every blockchain and stablecoin available on the platform. For the full conversion matrix by currency, see [Coverage](/get-started/coverage). | **Blockchain** | **Stablecoins** | | :- | :- | | Ethereum | USDC, USDT | | Polygon | USDC, USDT | | Base | USDC | | Tron | USDT | | Stellar | USDC | The default blockchain is set at the project level. Pass `blockchain` on on-ramp, off-ramp, transfer, or exchange rate requests to target a specific network; omit it to fall back to the default. ## How balances change A wallet's balance moves in three ways: * On-ramps credit the wallet with stablecoin once fiat lands on the matching [account](/concepts/accounts). * Transfers move stablecoin in or out, either between two Lumx customers or to and from any external wallet address. * Off-ramps debit the wallet and convert stablecoin to fiat sent to a registered [destination](/concepts/destinations). Subscribe to `onramp.success`, `offramp.success`, and `transfer.success` [webhooks](/developer/webhooks) to keep balances in sync inside your product. ## Prefunded wallets A wallet keeps its balance between operations. That means you can on-ramp once and trigger many payouts against the same wallet, without waiting for incoming fiat to clear before each one. A few common patterns: * Payroll. Fund the employer's wallet at the start of the cycle, then trigger one off-ramp per employee against registered destinations. See [Payroll](/guides/use-cases/payroll). * Supplier batches. Hold working capital in stablecoin and settle invoices across rails on demand. See [Treasury management](/guides/use-cases/treasury-management). * Card acquiring. Keep a balance to credit users at checkout, before card networks settle. * Marketplace payouts. Pay sellers from your platform's balance the moment an order ships. See [Marketplaces](/guides/use-cases/marketplaces). Each payout has to debit the customer's own wallet to a destination registered under that customer, or transfer to another onboarded customer's wallet. Lumx needs to see every payer and payee, so pooling funds for unboarded third parties isn't allowed. See [Nested Payments](/compliance/nested-payments). ## External transfers A wallet address is a real on-chain address. Anyone can send supported stablecoins to it, and you can transfer from it to any external wallet via the [Transfer](/api-reference/transactions/transfer) endpoint. Use this to: * Fund a customer's wallet directly with stablecoin held elsewhere, with no on-ramp needed. * Move stablecoin to a self-custody wallet, exchange, or counterparty outside Lumx. ## Related resources > [card] [Customers](/concepts/customers) > > Customer entities and verification lifecycle. > [card] [Accounts](/concepts/accounts) > > Virtual fiat accounts for incoming payments. > [card] [Destinations](/concepts/destinations) > > Payout destinations and holder relationships. > [card] [Transactions](/concepts/transactions) > > On-ramp, off-ramp, and transfer lifecycle. # Errors Source: https://docs.lumx.io/developer/errors Error response format and the full catalog of API error codes Every error the API returns uses the same response shape, so you can handle failures with a single code path. Match on the `code` field to decide what to do next, and keep the `requestId` for [support](mailto:support@lumx.io) requests. The catalog below covers the most common codes and is not exhaustive, so handle unknown codes gracefully. ## Error response ```typescript ErrorResponse theme={null} interface ErrorResponse { requestId: string timestamp: string path: string status: number code: string message: string validationErrors?: { param: string; message: string }[] } ``` ```json Example theme={null} { "requestId": "9f6f9741-4c65-4e0b-a48f-16d5b34d9e2f", "timestamp": "2026-07-31T10:30:45.123Z", "path": "/transactions/on-ramp", "status": 403, "code": "KYC_NOT_APPROVED", "message": "KYC/B is not approved for this customer" } ``` | Field | Description | | :- | :- | | `requestId` | Unique request ID. Include it when contacting support. | | `timestamp` | ISO 8601 time the error was generated. | | `path` | Path of the request that failed. | | `status` | HTTP status code. | | `code` | Machine-readable error code. Match on this field. | | `message` | Human-readable description. | | `validationErrors` | Field-level details. Only present on `VALIDATION_ERROR`. | > [!WARNING] > > Handle errors by `code` and not by `message`. The `message` is subject to changes. On any `5xx` response the `message` is always `"An unexpected error occurred."`, while the `code` stays specific. ## Validation errors When the request body fails validation, the response is a `400` with `code` `VALIDATION_ERROR` and one entry per invalid field in `validationErrors`. ```json Response theme={null} { "requestId": "9f6f9741-4c65-4e0b-a48f-16d5b34d9e2f", "timestamp": "2026-07-31T10:30:45.123Z", "path": "/customers", "status": 400, "code": "VALIDATION_ERROR", "message": "The request body contains invalid parameters.", "validationErrors": [ { "param": "taxId", "message": "taxId must be a string" } ] } ``` ## Error codes ### General | Code | Status | When it happens | | :- | :- | :- | | `VALIDATION_ERROR` | 400 | Request body failed validation. Details in `validationErrors`. | | `BAD_REQUEST` | 400 | Request is invalid without a more specific code. | | `UNAUTHORIZED` | 401 | API key is missing, invalid, or inactive. | | `FORBIDDEN` | 403 | API key lacks the scope required by the route. | | `RESOURCE_PROJECT_MISMATCH` | 403 | Resource exists but belongs to another project. | | `NOT_FOUND` | 404 | Resource does not exist. | | `CONFLICT` | 409 | Request conflicts with the current resource state. | | `RESOURCE_ALREADY_EXISTS` | 409 | A resource with the same unique value already exists. | | `UNPROCESSABLE_ENTITY` | 422 | Request violates a business rule. | | `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded. | | `INTERNAL_SERVER_ERROR` | 500 | Unexpected server error. | | `NOT_IMPLEMENTED` | 501 | Operation not supported by the active provider. | ### Customers | Code | Status | When it happens | | :- | :- | :- | | `CUSTOMER_NOT_FOUND` | 404 | Customer does not exist. | | `CUSTOMER_LIMIT_REACHED` | 403 | Project reached its maximum number of customers. | | `CUSTOMER_UPDATE_FAILED` | 500 | Customer update failed. | | `WALLET_CREATION_FAILED` | 500 | Wallet creation failed. | | `WALLET_NOT_FOUND` | 404 | Customer has no wallet on the requested blockchain. | | `KYC_NOT_APPROVED` | 403 | Customer identity verification (KYC/KYB) is not approved. | | `INVALID_OR_EXPIRED_TOKEN` | 401 | Terms of service token is invalid or expired. | | `LIMIT_REQUEST_ALREADY_PENDING` | 409 | Customer already has a pending limit increase request for that `type`. | ### Accounts | Code | Status | When it happens | | :- | :- | :- | | `ACCOUNT_NOT_FOUND` | 404 | Account does not exist. | | `ACCOUNT_NOT_ACTIVE` | 409 | Account is not in a usable state. | | `ACCOUNT_ALREADY_EXISTS` | 409 | Customer already has an account in that currency. | | `ACCOUNT_CREATION_FAILED` | 500 | Account creation failed. | | `ACCOUNT_CUSTOMER_MISMATCH` | 400 | Account does not belong to the given customer. | | `NO_SOURCE_ACCOUNT_TO_PROVISION` | 404 | No existing account to use as the provisioning source. | ### Destinations | Code | Status | When it happens | | :- | :- | :- | | `DESTINATION_NOT_FOUND` | 404 | Destination does not exist. | | `RAIL_NOT_ENABLED` | 403 | Rail is not enabled for this project. | | `HOLDER_TYPE_MISMATCH` | 403 | `holderType` differs from the customer type on `SELF` destinations. | | `HOLDER_TAX_ID_MISMATCH` | 403 | `holderTaxId` differs from the customer tax ID on `SELF` destinations. | ### Transactions | Code | Status | When it happens | | :- | :- | :- | | `TRANSACTION_NOT_FOUND` | 404 | Transaction does not exist. | | `INSUFFICIENT_BALANCE` | 422 | Balance is too low for the operation. | | `TRANSACTION_LIMIT_EXCEEDED` | 422 | Exceeds the customer's single, daily, or monthly limit for that transaction type. | | `PERSONAL_ACCOUNT_RELATIONSHIP_REQUIRED` | 400 | Personal transactions require a `SELF` bank account. | | `TRANSACTION_CREATION_FAILED` | 500 | Transaction creation failed. | | `INSOLVENT_TRADE` | 422 | Trade cannot be settled at the moment. | | `INVALID_PAYMENT_DESTINATION` | 400 | Payment destination is invalid. | | `INVALID_TAX_ID` | 400 | Tax ID was rejected by bank validation. | | `INVALID_AMOUNT` | 400 | Amount was rejected by bank validation. | ### Exchange rates | Code | Status | When it happens | | :- | :- | :- | | `EXCHANGE_RATE_NOT_FOUND` | 404 | Exchange rate does not exist. | | `EXCHANGE_RATE_EXPIRED` | 422 | Locked rate has expired. | | `EXCHANGE_RATE_ALREADY_USED` | 422 | Rate was already consumed by another transaction. | | `EXCHANGE_RATE_CURRENCY_MISMATCH` | 422 | Rate currency does not match the transaction type. | | `MINIMUM_AMOUNT_NOT_MET` | 422 | Amount is below the supported minimum for the currency. | | `EXCHANGE_RATE_FETCH_FAILED` | 500 | Fetching or locking the rate failed. | | `NO_LIQUIDITY_PROVIDER` | 422 | No liquidity available for this trade. | ### Autoconversion | Code | Status | When it happens | | :- | :- | :- | | `AUTOCONVERSION_RULE_NOT_FOUND` | 404 | Autoconversion rule does not exist. | | `INVALID_AUTOCONVERSION_RULE` | 400 | Autoconversion rule is invalid. | | `AUTOCONVERSION_RULE_ALREADY_EXISTS` | 409 | Customer already has an active USD, EUR, or GBP autoconversion rule. | | `NO_DEPOSIT_HANDLER` | 400 | No deposit handler matches the rule. | ### Partner fees | Code | Status | When it happens | | :- | :- | :- | | `PARTNER_FEE_NOT_FOUND` | 404 | Partner fee does not exist. | | `INVALID_WALLET_ADDRESS` | 400 | Partner wallet address is invalid for the blockchain. | ### Idempotency | Code | Status | When it happens | | :- | :- | :- | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | Same `Idempotency-Key` reused with a different body. | | `IDEMPOTENCY_KEY_IN_FLIGHT` | 409 | A request with that key is still processing. | ### Service availability | Code | Status | When it happens | | :- | :- | :- | | `UPSTREAM_SERVICE_ERROR` | 500 or 502 | An internal dependency failed. | | `TIMEOUT_ERROR` | 503 | Provider call timed out. Safe to retry. | | `NETWORK_ERROR` | 503 | Network failure reaching the provider. Safe to retry. | | `SERVICE_UNAVAILABLE` | 503 | Provider is unavailable. Safe to retry. | | `INVALID_REQUEST` | 400 | Provider rejected the request data. Not retryable. | Didn't find what you need? [Let us know](mailto:support@lumx.io). # Idempotency Source: https://docs.lumx.io/developer/idempotency Prevent duplicate operations with idempotency keys Idempotency ensures that repeating the same API request produces the same result without creating duplicate resources or triggering duplicate operations. This is especially important for financial transactions, where network issues or timeouts could cause your application to retry a request that already succeeded. ## How it works Include an `Idempotency-Key` header with a UUID v4 value on any `POST`, `PUT`, or `PATCH` request. The API stores the response for that key and returns the cached result if the same key is sent again. ```bash Request with idempotency key theme={null} curl -X POST https://api.lumx.io/transactions/on-ramp \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \ -H "Content-Type: application/json" \ -d '{ "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "rail": "PIX", "sourceCurrency": "BRL", "sourceAmount": "10000.00", "targetCurrency": "USDC", "purpose": "PERSONAL_ACCOUNT" }' ``` ## Behavior | Scenario | Result | | :- | :- | | First request with a key | Processes normally and caches the response | | Same key, same body | Returns the cached response with `X-Idempotency-Cached: true` header | | Same key, different body | Returns a `409 Conflict` error | > [!NOTE] > > Both successful and failed responses are cached. If the original request failed, retrying with the same key returns the same error. ## Key expiration Idempotency keys expire after 24 hours. After expiration, the same key can be reused for a new request. ## Best practices * **Generate a new UUID v4 for each unique operation.** Do not reuse keys across different operations. * **Store the key before sending the request.** If your application crashes mid-request, you can safely retry with the same key. * **Use idempotency keys on all mutation requests** (`POST`, `PUT`, `PATCH`), especially for transactions and customer creation. # MCP Server Source: https://docs.lumx.io/developer/mcp-server Connect AI tools to the Lumx documentation using the Model Context Protocol The Lumx documentation provides a [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that lets AI tools search and retrieve content directly from these docs. This means you can ask questions about the Lumx API from tools like Claude, Cursor, or VS Code and get answers grounded in the official documentation. ## Server URL ``` https://docs.lumx.io/mcp ``` ## Setup > [tab] Claude Code > > Run the following command in your terminal: > > ```bash theme={null} > claude mcp add --transport http lumx-docs https://docs.lumx.io/mcp > ``` > [tab] Cursor > > Add the following to your `.cursor/mcp.json` file: > > ```json theme={null} > { > "mcpServers": { > "lumx-docs": { > "url": "https://docs.lumx.io/mcp" > } > } > } > ``` > [tab] VS Code > > Add the following to your `.vscode/mcp.json` file: > > ```json theme={null} > { > "servers": { > "lumx-docs": { > "type": "http", > "url": "https://docs.lumx.io/mcp" > } > } > } > ``` > [tab] Codex > > Add the following to your `~/.codex/config.toml` file: > > ```toml theme={null} > [mcp_servers.lumx-docs] > url = "https://docs.lumx.io/mcp" > ``` > > Restart the CLI to pick up the new server. Codex Cloud doesn't currently expose MCP configuration — use the CLI for live docs lookup. > [tab] Claude Web > > 1. Go to **Settings** > **Connectors** in [Claude](https://claude.ai) > 2. Select **Add custom connector** > 3. Enter `Lumx Docs` as the name > 4. Paste `https://docs.lumx.io/mcp` as the URL ## Available tools The server exposes two tools to connected AI clients. Your client picks the right one based on the question, so you don't need to call them directly. * `search_lumx`: semantic search across the docs. Returns matching pages with titles, snippets, and direct links. Best for "where do I find X?" questions. * `query_docs_filesystem_lumx`: shell-style access to the raw `.mdx` and OpenAPI files using `rg`, `head`, `cat`, and `tree`. Best for fetching full request schemas, listing endpoints, or grepping for specific patterns. # OpenAPI Specification Source: https://docs.lumx.io/developer/openapi Download the Lumx API specification for use in your tools and workflows The Lumx API specification is available in OpenAPI 3.0 format. Use it to generate client SDKs, import into API tools, or integrate with your development workflow. ## Specification URL Download the [OpenAPI Specification](https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml) file. ## Importing into tools | Tool | How to import | | :- | :- | | Postman | Import → Link → Paste the URL | | Insomnia | Application → Import → From URL | | OpenAPI Generator | Use the URL as input to generate client SDKs | | Swagger Editor | File → Import URL | # Webhooks Source: https://docs.lumx.io/developer/webhooks Receive real-time notifications when resource statuses change Webhooks notify your application whenever a resource changes status. Instead of polling the API, you receive an HTTP POST request to your endpoint with the updated data. ## Setting up webhooks To configure webhooks, go to **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io) and add your endpoint URL. You can subscribe to specific event types or receive all events. > [!NOTE] > > You must be a project admin or owner to create webhooks. ## Event structure Every webhook event follows a consistent structure with three top-level fields: ```json theme={null} { "eventId": "550e8400-e29b-41d4-a716-446655440000", "eventType": "onramp.success", "data": { // Event-specific payload } } ``` | Field | Type | Description | | :- | :- | :- | | `eventId` | `string` | Unique identifier for the event (UUID) | | `eventType` | `string` | The event type (e.g., `onramp.success`, `customer.approved`) | | `data` | `object` | The event payload, which varies by event type | ## Available events > [accordion] On-ramp events > > | Event type | Triggered when | > | :- | :- | > | `onramp.awaiting_funds` | On-ramp is waiting for the customer to send funds | > | `onramp.transferring_fiat` | Fiat transfer is in progress | > | `onramp.trading` | Trade is being executed | > | `onramp.transferring_stablecoin` | Stablecoin transfer to the customer is in progress | > | `onramp.success` | On-ramp completed successfully | > | `onramp.failed` | On-ramp failed | > | `onramp.expired` | On-ramp expired before payment was received | > [accordion] Off-ramp events > > | Event type | Triggered when | > | :- | :- | > | `offramp.transferring_stablecoin` | Stablecoin transfer from the customer is in progress | > | `offramp.trading` | Trade is being executed | > | `offramp.transferring_fiat` | Fiat transfer to the destination is in progress | > | `offramp.success` | Off-ramp completed successfully | > | `offramp.failed` | Off-ramp failed | > [accordion] Transfer events > > | Event type | Triggered when | > | :- | :- | > | `transfer.transferring_stablecoin` | Stablecoin transfer is in progress | > | `transfer.success` | Transfer completed successfully | > | `transfer.failed` | Transfer failed | > [accordion] Customer events > > | Event type | Triggered when | > | :- | :- | > | `customer.created` | Customer was created | > | `customer.under_verification` | Customer verification is in progress | > | `customer.approved` | Customer was approved | > | `customer.rfi` | Customer requires additional information (can resubmit) | > | `customer.final_rejection` | Customer was permanently rejected | > [accordion] Limit request events > > | Event type | Triggered when | > | :- | :- | > | `customer.limit_request.created` | A [limit increase request](/guides/limit-increase-requests) was created | > | `customer.limit_request.approved` | The request was approved in full | > | `customer.limit_request.partially_approved` | The request was approved below what was requested | > | `customer.limit_request.rejected` | The request was rejected | > > The payload's `type` is the transaction type the request targets (`ONRAMP`, `OFFRAMP`, or `TRANSFER`). It's `null` for requests created before October 28, 2026. > [accordion] Account events > > | Event type | Triggered when | > | :- | :- | > | `account.awaiting_onboarding` | Account was created and waits for the customer's verification | > | `account.requested` | Customer approved and account provisioning was requested | > | `account.provisioning` | Account is being provisioned and verified | > | `account.rfi` | Additional information is required to continue verification | > | `account.active` | Account is verified and can receive funds | > | `account.rejected` | Account application was rejected | > | `account.inactive` | Account was deactivated | > [accordion] Destination events > > | Event type | Triggered when | > | :- | :- | > | `destinations.under_verification` | Destination verification is in progress | > | `destinations.approved` | Destination was approved | > | `destinations.final_rejection` | Destination was permanently rejected | ## Payload examples > [tab] On-ramp > > > [tab] awaiting\_funds > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "onramp.awaiting_funds", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "ON_RAMP", > > "request": { > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "rail": "PIX", > > "sourceCurrency": "BRL", > > "sourceAmount": "10000.00", > > "targetCurrency": "USDC", > > "purpose": "PERSONAL_ACCOUNT" > > }, > > "state": { > > "status": "AWAITING_FUNDS", > > "payment": { > > "rail": "PIX", > > "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD" > > } > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] transferring\_fiat > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "onramp.transferring_fiat", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "ON_RAMP", > > "request": { > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "rail": "PIX", > > "sourceCurrency": "BRL", > > "sourceAmount": "10000.00", > > "targetCurrency": "USDC", > > "purpose": "PERSONAL_ACCOUNT" > > }, > > "state": { > > "status": "TRANSFERRING_FIAT", > > "payment": { > > "rail": "PIX", > > "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD" > > } > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] trading > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "onramp.trading", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "ON_RAMP", > > "request": { > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "rail": "PIX", > > "sourceCurrency": "BRL", > > "sourceAmount": "10000.00", > > "targetCurrency": "USDC", > > "purpose": "PERSONAL_ACCOUNT" > > }, > > "state": { > > "status": "TRADING", > > "payment": { > > "rail": "PIX", > > "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD" > > } > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] transferring\_stablecoin > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "onramp.transferring_stablecoin", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "ON_RAMP", > > "request": { > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "rail": "PIX", > > "sourceCurrency": "BRL", > > "sourceAmount": "10000.00", > > "targetCurrency": "USDC", > > "purpose": "PERSONAL_ACCOUNT" > > }, > > "state": { > > "status": "TRANSFERRING_STABLECOIN", > > "payment": { > > "rail": "PIX", > > "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD" > > } > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] success > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "onramp.success", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "ON_RAMP", > > "request": { > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "rail": "PIX", > > "sourceCurrency": "BRL", > > "sourceAmount": "10000.00", > > "targetCurrency": "USDC", > > "purpose": "PERSONAL_ACCOUNT" > > }, > > "state": { > > "status": "SUCCESS", > > "payment": { > > "rail": "PIX", > > "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD" > > }, > > "receipt": { > > "sourceCurrency": "BRL", > > "sourceAmount": "10000.00", > > "targetCurrency": "USDC", > > "baseExchangeRate": "5.25", > > "baseTargetAmount": "1904.76", > > "fees": { > > "lumx": { > > "rate": "0.005", > > "flatAmount": "0.00", > > "totalAmount": "9.52", > > "currency": "USDC" > > }, > > "partner": { > > "rate": "0.002", > > "flatAmount": "0.00", > > "totalAmount": "3.81", > > "currency": "USDC" > > } > > }, > > "finalTargetAmount": "1891.43", > > "finalExchangeRate": "5.29", > > "transactionHash": "0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1", > > "blockExplorerUrl": "https://polygonscan.com/tx/0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1" > > } > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] failed > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "onramp.failed", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "ON_RAMP", > > "request": { > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "rail": "PIX", > > "sourceCurrency": "BRL", > > "sourceAmount": "10000.00", > > "targetCurrency": "USDC", > > "purpose": "PERSONAL_ACCOUNT" > > }, > > "state": { > > "status": "FAILED", > > "payment": { > > "rail": "PIX", > > "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD" > > }, > > "receipt": { > > "error": { > > "message": "Your transaction has failed. Please contact support." > > } > > } > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] expired > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "onramp.expired", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "ON_RAMP", > > "request": { > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "rail": "PIX", > > "sourceCurrency": "BRL", > > "sourceAmount": "10000.00", > > "targetCurrency": "USDC", > > "purpose": "PERSONAL_ACCOUNT" > > }, > > "state": { > > "status": "EXPIRED", > > "payment": { > > "rail": "PIX", > > "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD" > > } > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > [tab] Off-ramp > > > [tab] transferring\_stablecoin > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "offramp.transferring_stablecoin", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "destinationId": "ba123456-e89b-12d3-a456-426614174000", > > "type": "OFF_RAMP", > > "request": { > > "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284", > > "sourceCurrency": "USDC", > > "sourceAmount": "1000.00", > > "purpose": "PERSONAL_ACCOUNT" > > }, > > "state": { > > "status": "TRANSFERRING_STABLECOIN" > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] trading > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "offramp.trading", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "destinationId": "ba123456-e89b-12d3-a456-426614174000", > > "type": "OFF_RAMP", > > "request": { > > "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284", > > "sourceCurrency": "USDC", > > "sourceAmount": "1000.00", > > "purpose": "PERSONAL_ACCOUNT" > > }, > > "state": { > > "status": "TRADING" > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] transferring\_fiat > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "offramp.transferring_fiat", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "destinationId": "ba123456-e89b-12d3-a456-426614174000", > > "type": "OFF_RAMP", > > "request": { > > "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284", > > "sourceCurrency": "USDC", > > "sourceAmount": "1000.00", > > "purpose": "PERSONAL_ACCOUNT" > > }, > > "state": { > > "status": "TRANSFERRING_FIAT" > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] success > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "offramp.success", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "destinationId": "ba123456-e89b-12d3-a456-426614174000", > > "type": "OFF_RAMP", > > "request": { > > "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284", > > "sourceCurrency": "USDC", > > "sourceAmount": "1000.00", > > "purpose": "PERSONAL_ACCOUNT" > > }, > > "state": { > > "status": "SUCCESS", > > "receipt": { > > "sourceCurrency": "USDC", > > "sourceAmount": "1904.76", > > "targetCurrency": "BRL", > > "baseExchangeRate": "5.25", > > "baseTargetAmount": "10000.00", > > "fees": { > > "lumx": { > > "rate": "0.005", > > "flatAmount": "0.00", > > "totalAmount": "9.52", > > "currency": "USDC" > > }, > > "partner": { > > "rate": "0.002", > > "flatAmount": "0.00", > > "totalAmount": "3.81", > > "currency": "USDC" > > } > > }, > > "finalTargetAmount": "9900.00", > > "finalExchangeRate": "5.20", > > "transactionHash": "0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1", > > "blockExplorerUrl": "https://polygonscan.com/tx/0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1" > > } > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] failed > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "offramp.failed", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "destinationId": "ba123456-e89b-12d3-a456-426614174000", > > "type": "OFF_RAMP", > > "request": { > > "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284", > > "sourceCurrency": "USDC", > > "sourceAmount": "1000.00", > > "purpose": "PERSONAL_ACCOUNT" > > }, > > "state": { > > "status": "FAILED", > > "receipt": { > > "error": { > > "message": "Your transaction has failed. Please contact support." > > } > > } > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > [tab] Transfer > > > [tab] transferring\_stablecoin > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "transfer.transferring_stablecoin", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "TRANSFER", > > "request": { > > "currency": "USDC", > > "from": "123e4567-e89b-12d3-a456-426614174001", > > "to": "123e4567-e89b-12d3-a456-426614174002", > > "amount": "1000.000000" > > }, > > "state": { > > "status": "TRANSFERRING_STABLECOIN" > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] success > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "transfer.success", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "TRANSFER", > > "request": { > > "currency": "USDC", > > "from": "123e4567-e89b-12d3-a456-426614174001", > > "to": "123e4567-e89b-12d3-a456-426614174002", > > "amount": "1000.000000" > > }, > > "state": { > > "status": "SUCCESS", > > "receipt": { > > "transactionHash": "0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1", > > "blockExplorerUrl": "https://polygonscan.com/tx/0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1" > > } > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] failed > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "transfer.failed", > > "data": { > > "id": "123e4567-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "TRANSFER", > > "request": { > > "currency": "USDC", > > "from": "123e4567-e89b-12d3-a456-426614174001", > > "to": "123e4567-e89b-12d3-a456-426614174002", > > "amount": "1000.000000" > > }, > > "state": { > > "status": "FAILED", > > "receipt": { > > "error": { > > "message": "Your transaction has failed. Please contact support." > > } > > } > > }, > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > [tab] Customer > > > [tab] created > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "customer.created", > > "data": { > > "id": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "status": "created", > > "level": "BASIC", > > "associatedParties": [ > > { > > "id": "f0e9d8c7-b6a5-4f3e-2d1c-0b9a8f7e6d5c", > > "associatedPartyId": "c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6", > > "roles": ["SHAREHOLDER"], > > "status": "approved", > > "level": "BASIC" > > } > > ], > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] under\_verification > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "customer.under_verification", > > "data": { > > "id": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "status": "under_verification", > > "level": "BASIC", > > "associatedParties": [ > > { > > "id": "f0e9d8c7-b6a5-4f3e-2d1c-0b9a8f7e6d5c", > > "associatedPartyId": "c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6", > > "roles": ["SHAREHOLDER"], > > "status": "approved", > > "level": "BASIC" > > } > > ], > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] approved > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "customer.approved", > > "data": { > > "id": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "status": "approved", > > "level": "BASIC", > > "associatedParties": [ > > { > > "id": "f0e9d8c7-b6a5-4f3e-2d1c-0b9a8f7e6d5c", > > "associatedPartyId": "c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6", > > "roles": ["SHAREHOLDER"], > > "status": "approved", > > "level": "BASIC" > > } > > ], > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] rfi > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "customer.rfi", > > "data": { > > "id": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "status": "rfi", > > "level": "BASIC", > > "statusReason": { > > "rejectLabels": ["DOCUMENT_QUALITY"], > > "rejectSubLabels": ["DOCUMENT_BLURRY"], > > "documents": [ > > { > > "type": "PASSPORT", > > "status": "DECLINED", > > "comment": "Document is blurry and illegible", > > "rejectLabels": ["DOCUMENT_QUALITY"] > > } > > ] > > }, > > "associatedParties": [ > > { > > "id": "f0e9d8c7-b6a5-4f3e-2d1c-0b9a8f7e6d5c", > > "associatedPartyId": "c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6", > > "roles": ["SHAREHOLDER"], > > "status": "rfi", > > "level": "BASIC", > > "statusReason": { > > "rejectLabels": ["DOCUMENT_QUALITY"], > > "rejectSubLabels": ["DOCUMENT_BLURRY"] > > } > > } > > ], > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] final\_rejection > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "customer.final_rejection", > > "data": { > > "id": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "status": "final_rejection", > > "level": "BASIC", > > "statusReason": { > > "rejectLabels": ["FRAUDULENT_PATTERNS"], > > "rejectSubLabels": ["MULTIPLE_SUBMISSIONS"] > > }, > > "associatedParties": [ > > { > > "id": "f0e9d8c7-b6a5-4f3e-2d1c-0b9a8f7e6d5c", > > "associatedPartyId": "c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6", > > "roles": ["SHAREHOLDER"], > > "status": "rfi", > > "level": "BASIC", > > "statusReason": { > > "rejectLabels": ["DOCUMENT_QUALITY"], > > "rejectSubLabels": ["DOCUMENT_BLURRY"] > > } > > } > > ], > > "metadata": {}, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > [tab] Limit request > > > [tab] created > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "customer.limit_request.created", > > "data": { > > "id": "b2e1a2d4-1234-4a3b-9c4d-5e6f7a8b9c0d", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "OFFRAMP", > > "requested": { > > "single": "15000.00", > > "daily": "60000.00", > > "monthly": "120000.00" > > }, > > "approved": { > > "single": null, > > "daily": null, > > "monthly": null > > }, > > "supportingDocumentType": "LIMIT_REQUEST_BANK_STATEMENT", > > "supportingDocumentId": "9d1e2f3a-4b5c-6d7e-8f90-1a2b3c4d5e6f", > > "status": "IN_REVIEW", > > "reviewComment": null, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:00Z" > > } > > } > > ``` > > > [tab] approved > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "customer.limit_request.approved", > > "data": { > > "id": "b2e1a2d4-1234-4a3b-9c4d-5e6f7a8b9c0d", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "OFFRAMP", > > "requested": { > > "single": "15000.00", > > "daily": "60000.00", > > "monthly": "120000.00" > > }, > > "approved": { > > "single": "15000.00", > > "daily": "60000.00", > > "monthly": "120000.00" > > }, > > "supportingDocumentType": "LIMIT_REQUEST_BANK_STATEMENT", > > "supportingDocumentId": "9d1e2f3a-4b5c-6d7e-8f90-1a2b3c4d5e6f", > > "status": "APPROVED", > > "reviewComment": null, > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] partially\_approved > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "customer.limit_request.partially_approved", > > "data": { > > "id": "b2e1a2d4-1234-4a3b-9c4d-5e6f7a8b9c0d", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "OFFRAMP", > > "requested": { > > "single": "15000.00", > > "daily": "60000.00", > > "monthly": "120000.00" > > }, > > "approved": { > > "single": "7500.00", > > "daily": "30000.00", > > "monthly": "60000.00" > > }, > > "supportingDocumentType": "LIMIT_REQUEST_BANK_STATEMENT", > > "supportingDocumentId": "9d1e2f3a-4b5c-6d7e-8f90-1a2b3c4d5e6f", > > "status": "PARTIALLY_APPROVED", > > "reviewComment": "Approved below the requested amount based on the supporting document.", > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] rejected > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "customer.limit_request.rejected", > > "data": { > > "id": "b2e1a2d4-1234-4a3b-9c4d-5e6f7a8b9c0d", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "type": "OFFRAMP", > > "requested": { > > "single": "15000.00", > > "daily": "60000.00", > > "monthly": "120000.00" > > }, > > "approved": { > > "single": null, > > "daily": null, > > "monthly": null > > }, > > "supportingDocumentType": "LIMIT_REQUEST_BANK_STATEMENT", > > "supportingDocumentId": "9d1e2f3a-4b5c-6d7e-8f90-1a2b3c4d5e6f", > > "status": "REJECTED", > > "reviewComment": "Supporting document doesn't justify the requested limits.", > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > [tab] Account > > > [tab] awaiting\_onboarding > > > > ```json theme={null} > > { > > "eventId": "3f0a5b62-96cf-4f11-8a1e-2a6a9be116b0", > > "eventType": "account.awaiting_onboarding", > > "data": { > > "id": "ce4c89f3-60f2-45a3-9de7-415a462dd95c", > > "customerId": "96fec1e2-78d9-4978-813d-674bba64020d", > > "status": "AWAITING_ONBOARDING", > > "currency": "USD", > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] requested > > > > ```json theme={null} > > { > > "eventId": "9a2f2d7c-8f0f-46a4-b7de-51f18a6bdc1d", > > "eventType": "account.requested", > > "data": { > > "id": "ce4c89f3-60f2-45a3-9de7-415a462dd95c", > > "customerId": "96fec1e2-78d9-4978-813d-674bba64020d", > > "status": "REQUESTED", > > "currency": "USD", > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] provisioning > > > > ```json theme={null} > > { > > "eventId": "d26c479f-f786-4be8-a694-c4da0e8393e7", > > "eventType": "account.provisioning", > > "data": { > > "id": "ce4c89f3-60f2-45a3-9de7-415a462dd95c", > > "customerId": "96fec1e2-78d9-4978-813d-674bba64020d", > > "status": "PROVISIONING", > > "currency": "USD", > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] rfi > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "account.rfi", > > "data": { > > "id": "ce4c89f3-60f2-45a3-9de7-415a462dd95c", > > "customerId": "96fec1e2-78d9-4978-813d-674bba64020d", > > "status": "RFI", > > "currency": "USD", > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] active > > > > ```json theme={null} > > { > > "eventId": "81d783f1-3675-4f3a-92d1-e4f5af66cca3", > > "eventType": "account.active", > > "data": { > > "id": "d06d342c-629a-4d99-a26f-34e8ea528403", > > "customerId": "768d6aac-627e-44e8-aabd-d51d10f59c02", > > "status": "ACTIVE", > > "currency": "EUR", > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] rejected > > > > ```json theme={null} > > { > > "eventId": "7c4f0e2a-1d64-4c33-9b3f-6f2f8f0f5f10", > > "eventType": "account.rejected", > > "data": { > > "id": "d06d342c-629a-4d99-a26f-34e8ea528403", > > "customerId": "768d6aac-627e-44e8-aabd-d51d10f59c02", > > "status": "REJECTED", > > "currency": "EUR", > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] inactive > > > > ```json theme={null} > > { > > "eventId": "b8e17f60-2f43-49f2-9a51-0c8f37d51a22", > > "eventType": "account.inactive", > > "data": { > > "id": "d06d342c-629a-4d99-a26f-34e8ea528403", > > "customerId": "768d6aac-627e-44e8-aabd-d51d10f59c02", > > "status": "INACTIVE", > > "currency": "EUR", > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > [tab] Destination > > > [tab] under\_verification > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "destinations.under_verification", > > "data": { > > "id": "ba123456-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "status": "UNDER_VERIFICATION", > > "level": "BASIC", > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] approved > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "destinations.approved", > > "data": { > > "id": "ba123456-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "status": "APPROVED", > > "level": "BASIC", > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` > > > [tab] final\_rejection > > > > ```json theme={null} > > { > > "eventId": "550e8400-e29b-41d4-a716-446655440000", > > "eventType": "destinations.final_rejection", > > "data": { > > "id": "ba123456-e89b-12d3-a456-426614174000", > > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > > "status": "FINAL_REJECTION", > > "level": "BASIC", > > "createdAt": "2024-03-20T15:30:00Z", > > "updatedAt": "2024-03-20T15:30:05Z" > > } > > } > > ``` ## Verifying webhook signatures Each webhook request includes three headers for signature verification: | Header | Description | | :- | :- | | `webhook-id` | Unique message identifier | | `webhook-timestamp` | Unix timestamp (seconds) of when the message was sent | | `webhook-signature` | Base64-encoded signature(s), space-delimited and prefixed with version | To verify a webhook signature, you construct the signed content, compute the expected signature, and compare it with the header value. The signed content is created by concatenating the `webhook-id`, `webhook-timestamp`, and request body, separated by dots: ``` signed_content = "${webhook_id}.${webhook_timestamp}.${body}" ``` The signature is computed as a HMAC-SHA256 hash of the signed content using your webhook secret (base64-decoded) as the key, then base64-encoded. > [!NOTE] > > Your webhook signing secret starts with `whsec_`. You must strip this prefix and base64-decode the remainder before using it as the HMAC key. > [!TIP] > > When you rotate your signing secret, Lumx continues signing messages with both the old and new secrets for 24 hours. This means the `webhook-signature` header may contain multiple signatures (e.g., `v1, v1,`). Your verification code should accept any valid signature from the list, which the examples below already handle. > [tab] TypeScript > > ```typescript theme={null} > import { createHmac, timingSafeEqual } from "crypto"; > > function verifyWebhook( > body: string, > headers: Record, > secret: string > ): boolean { > const msgId = headers["webhook-id"]; > const timestamp = headers["webhook-timestamp"]; > const signature = headers["webhook-signature"]; > > if (!msgId || !timestamp || !signature) { > return false; > } > > // Reject messages older than 5 minutes to prevent replay attacks > const now = Math.floor(Date.now() / 1000); > if (Math.abs(now - parseInt(timestamp)) > 300) { > return false; > } > > // Strip the whsec_ prefix and decode the secret > const secretBytes = Buffer.from(secret.replace("whsec_", ""), "base64"); > > // Compute the expected signature > const signedContent = `${msgId}.${timestamp}.${body}`; > const expectedSignature = createHmac("sha256", secretBytes) > .update(signedContent) > .digest("base64"); > > // Compare against all provided signatures (there may be multiple) > const signaturesV1 = signature > .split(" ") > .filter((s) => s.startsWith("v1,")) > .map((s) => s.substring(3)); > > return signaturesV1.some((sig) => > timingSafeEqual(Buffer.from(sig), Buffer.from(expectedSignature)) > ); > } > ``` > [tab] JavaScript > > ```javascript theme={null} > const { createHmac, timingSafeEqual } = require("crypto"); > > function verifyWebhook(body, headers, secret) { > const msgId = headers["webhook-id"]; > const timestamp = headers["webhook-timestamp"]; > const signature = headers["webhook-signature"]; > > if (!msgId || !timestamp || !signature) { > return false; > } > > // Reject messages older than 5 minutes to prevent replay attacks > const now = Math.floor(Date.now() / 1000); > if (Math.abs(now - parseInt(timestamp)) > 300) { > return false; > } > > // Strip the whsec_ prefix and decode the secret > const secretBytes = Buffer.from(secret.replace("whsec_", ""), "base64"); > > // Compute the expected signature > const signedContent = `${msgId}.${timestamp}.${body}`; > const expectedSignature = createHmac("sha256", secretBytes) > .update(signedContent) > .digest("base64"); > > // Compare against all provided signatures (there may be multiple) > const signaturesV1 = signature > .split(" ") > .filter((s) => s.startsWith("v1,")) > .map((s) => s.substring(3)); > > return signaturesV1.some((sig) => > timingSafeEqual(Buffer.from(sig), Buffer.from(expectedSignature)) > ); > } > ``` > [tab] Python > > ```python theme={null} > import base64 > import hashlib > import hmac > import time > > > def verify_webhook( > body: str, headers: dict[str, str], secret: str > ) -> bool: > msg_id = headers.get("webhook-id") > timestamp = headers.get("webhook-timestamp") > signature = headers.get("webhook-signature") > > if not msg_id or not timestamp or not signature: > return False > > # Reject messages older than 5 minutes to prevent replay attacks > now = int(time.time()) > if abs(now - int(timestamp)) > 300: > return False > > # Strip the whsec_ prefix and decode the secret > secret_bytes = base64.b64decode(secret.removeprefix("whsec_")) > > # Compute the expected signature > signed_content = f"{msg_id}.{timestamp}.{body}" > expected_signature = base64.b64encode( > hmac.new( > secret_bytes, signed_content.encode(), hashlib.sha256 > ).digest() > ).decode() > > # Compare against all provided signatures (there may be multiple) > signatures_v1 = [ > s.removeprefix("v1,") > for s in signature.split(" ") > if s.startswith("v1,") > ] > > return any( > hmac.compare_digest(sig, expected_signature) > for sig in signatures_v1 > ) > ``` > [tab] Go > > ```go theme={null} > package webhook > > import ( > "crypto/hmac" > "crypto/sha256" > "encoding/base64" > "fmt" > "math" > "net/http" > "strconv" > "strings" > "time" > ) > > func VerifyWebhook(body string, headers http.Header, secret string) bool { > msgID := headers.Get("webhook-id") > timestamp := headers.Get("webhook-timestamp") > signature := headers.Get("webhook-signature") > > if msgID == "" || timestamp == "" || signature == "" { > return false > } > > // Reject messages older than 5 minutes to prevent replay attacks > ts, err := strconv.ParseInt(timestamp, 10, 64) > if err != nil { > return false > } > now := time.Now().Unix() > if math.Abs(float64(now-ts)) > 300 { > return false > } > > // Strip the whsec_ prefix and decode the secret > secretBytes, err := base64.StdEncoding.DecodeString( > strings.TrimPrefix(secret, "whsec_"), > ) > if err != nil { > return false > } > > // Compute the expected signature > signedContent := fmt.Sprintf("%s.%s.%s", msgID, timestamp, body) > mac := hmac.New(sha256.New, secretBytes) > mac.Write([]byte(signedContent)) > expectedSignature := base64.StdEncoding.EncodeToString(mac.Sum(nil)) > > // Compare against all provided signatures (there may be multiple) > for _, sig := range strings.Split(signature, " ") { > if strings.HasPrefix(sig, "v1,") { > if hmac.Equal( > []byte(strings.TrimPrefix(sig, "v1,")), > []byte(expectedSignature), > ) { > return true > } > } > } > > return false > } > ``` ## Retries If your endpoint does not return a `2xx` response, Lumx retries the delivery with exponential backoff: | Attempt | Delay | | :- | :- | | 1 | Immediately | | 2 | 5 seconds | | 3 | 5 minutes | | 4 | 30 minutes | | 5 | 2 hours | | 6 | 5 hours | | 7 | 10 hours | | 8 | 10 hours | After all retry attempts are exhausted, Lumx marks the message as failed. You can manually retry failed messages from the [Dashboard](https://dashboard.lumx.io). > [!TIP] > > Use the `webhook-id` header to deduplicate events in case your endpoint receives the same event more than once. ## IP allowlisting If your infrastructure requires allowlisting specific IPs, add the following addresses. These IPs are shared across both sandbox and production environments: ``` 44.214.29.156/32 3.82.0.0/32 100.56.2.161/32 ``` ## Basic authentication If your endpoint requires HTTP Basic authentication, include the credentials directly in the endpoint URL: ``` https://username:password@your-domain.com/webhook ``` Lumx extracts the credentials and sends them in the `Authorization` header on every webhook request: ``` Authorization: Basic ``` # Authentication Source: https://docs.lumx.io/get-started/authentication Get your authentication credentials to start sending requests. **1. Create a Lumx account** Sign up at the [Dashboard](https://dashboard.lumx.io). From there you can manage projects, generate API keys, and monitor transaction activity. > [!NOTE] > > New accounts start with a Sandbox environment for testing. To access Production, [schedule a call with our team](https://cal.com/caiobrbs/discovery). **2. Generate your API key** Navigate to **Developers → API Keys** and create a new key for your project. Copy your API key immediately. It's only shown once. If compromised, revoke and rotate from the Dashboard. > [!CAUTION] > > Store your API keys securely. Never commit them to version control or expose them in client-side code. **3. Allowlist your server IPs** Add the IP addresses your servers call the API from on the **Developers → API Keys** page. The allowlist applies to production only (`https://api.lumx.io`), sandbox stays open to requests from any IP. During the current grace period nothing is blocked, even for keys that already have an allowlist. Enforcement starts on **September 21, 2026**, when the production API begins rejecting requests from IPs outside the allowlist, so register your server IPs before then. # Coverage Source: https://docs.lumx.io/get-started/coverage Explore payment rails and supported pairs ## Payment rails | **Type** | **Country/Region** | **Settlement Time** | | :- | :- | :- | | International SWIFT | Global | 1-5 business days | | PIX | 🇧🇷 Brazil | Instant | | ACH | 🇺🇸 United States | 1-2 business days | | FEDWIRE | 🇺🇸 United States | Same day | | SEPA | 🇪🇺 Europe | Instant; ≥ €100k between 4:30 PM and 2:30 AM UK time: same or next business day | | SPEI | 🇲🇽 Mexico | Instant | | FPS (Faster Payments Service) | 🇬🇧 United Kingdom | Instant | | SWIFT (EUR) Q4/2026 | 🇪🇺 Europe | 1-5 business days | | ACH Q4/2026 | 🇨🇴 Colombia | 1 business day | | Bre-B Q4/2026 | 🇨🇴 Colombia | Instant | | Transfers 3.0 Q4/2026 | 🇦🇷 Argentina | Instant | To enable a rail for a customer, request the corresponding currency in the `accounts` array when you [create the customer](/guides/create-a-customer). For per-rail cut-off times, see [Payment rail cut-off times](/additional-information/sla-and-cutoffs). For the list of jurisdictions where Lumx onboards customers, see [Supported countries](/compliance/supported-countries). ## Supported pairs All supported fiat-to-stablecoin and stablecoin-to-fiat conversion routes: | **Blockchain** | **Currency** | **BRL** | **USD** | **EUR** | **MXN** | **GBP** | | :- | :- | :- | :- | :- | :- | :- | | Ethereum | USDC | Supported | Supported | Supported | Supported | Supported | | Ethereum | USDT | Supported | Supported | Supported | Supported | Supported | | Polygon | USDC | Supported | Supported | Supported | Supported | Supported | | Polygon | USDT | Supported | Supported | Supported | Supported | Supported | | Base | USDC | Supported | Supported | Supported | Supported | Supported | | Tron | USDT | Supported | Supported | Supported | Supported | Supported | | Stellar On-ramp only | USDC | Supported | - | - | - | - | # Environments Source: https://docs.lumx.io/get-started/environments Sandbox and production base URLs Lumx exposes two environments. Same API surface, separate data. ## Sandbox Sandbox runs against test stablecoins on testnet and integrates with mock banking partners. No real money moves. Use it to build, test KYC/KYB outcomes against the [sandbox magic numbers](/compliance/identity-verification#sandbox-magic-numbers) in the `taxId` field, and verify your webhook handlers before going live. ``` https://api-sandbox.lumx.io/ ``` ## Production Production moves real money on mainnet blockchains and live banking rails. Every call settles. Treat keys accordingly. ``` https://api.lumx.io/ ``` # Welcome Source: https://docs.lumx.io/get-started/welcome Stablecoin infrastructure for global payments, treasury, and payouts ![](https://mintcdn.com/lumx-9fc2c172/1m7pRn2WUCJazJgg/images/welcome-light.svg?fit=max\&auto=format\&n=1m7pRn2WUCJazJgg\&q=85\&s=2dac325b9c94ea436278f2d9d1102ee0) Lumx is payment infrastructure that connects traditional banking rails to stablecoins. Use the API to collect, hold, convert, and pay out money in either form, with instant settlement and compliance built in. Move money across borders, run corporate treasury, or scale global payouts without standing up local entities in every country. ## What you can build * [**Global accounts**](/guides/use-cases/global-accounts) — provision local-currency accounts so each customer can be paid worldwide. * [**Treasury management**](/guides/use-cases/treasury-management) — hold working capital in stablecoin and settle to local fiat on demand. * [**Payroll**](/guides/use-cases/payroll) — fund employers in stablecoin and pay employees in their local currency. * [**Remittances**](/guides/use-cases/remittances) — fund senders in local currency and pay recipients on local rails. * [**Marketplaces**](/guides/use-cases/marketplaces) — take a platform fee on every sale and settle sellers in their local currency. ## Platform The building blocks behind every Lumx integration. > [card] [Customers](/concepts/customers) > > Individual and business entities, with KYC/KYB built in. > [card] [Stablecoin wallets](/concepts/wallets) > > Multi-chain custody for stablecoin balances and payouts. > [card] [Accounts](/concepts/accounts) > > Local-currency virtual accounts for fiat on and off ramps. > [card] [Destinations](/concepts/destinations) > > Payout endpoints for fiat and stablecoin transfers. > [card] [Exchange rates](/concepts/exchange-rates) > > Floating and locked rates for fiat to stablecoin conversions. > [card] [Partner fees](/concepts/partner-fees) > > Add fees to transactions to monetize your integration. > [card] [Transactions](/concepts/transactions) > > On-ramp, off-ramp, and transfer flows with full lifecycle. ## Get started > [card] [Authentication](/get-started/authentication) > > Generate an API key and start sending requests. > [card] [Guides](/guides/create-a-customer) > > Create your first customer, accept payments, and send money globally. > [card] [Compliance](/compliance/identity-verification) > > KYC/KYB requirements, transaction limits, and supported countries. > [card] [API Reference](/api-reference) > > Endpoint schemas, request and response examples. # Autoconversion Rules Source: https://docs.lumx.io/guides/autoconversion-rules Automatically convert incoming fiat deposits to stablecoin with a standing rule An autoconversion rule runs a standing conversion for you: set it up once and every matching transaction is converted automatically, with no per-transaction API call. Rules convert **fiat deposits to stablecoin**. You share the deposit details with your customer, and each matching deposit (a PIX transfer using those details, for example) is converted to USDC or USDT and delivered on-chain. Rules are tied to an [account](/concepts/accounts) issued by a Lumx banking partner. The account's currency defines the source currency of the conversion; the rule defines the target asset and network. Autoconversion is available for BRL, MXN, USD, EUR, and GBP accounts. > [!NOTE] > > Stablecoin-to-fiat rules, converting a stablecoin deposit in a wallet to fiat, are coming. This guide covers the fiat-to-stablecoin direction available today. ## How it works 1. You create a rule for an account, choosing the target asset, network, purpose, and a name. 2. The response includes deposit details (`sourceDepositInfo`): for a BRL account, a PIX `brCode`. Share it with whoever is sending the funds. 3. Every deposit paid through it is matched to the rule and converted to the target asset. 4. You receive webhooks as each conversion moves through its lifecycle. You can create and view rules in the [Dashboard](https://dashboard.lumx.io) (open the customer's page and go to the **Autoconversion Rules** tab) or through the API. The sections below cover the API flow. ## Create a rule Send a `POST` request to `/autoconversion-rules`. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/autoconversion-rules \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "accountId": "44dd9734-176a-4ced-924f-103f8d50ea5e", "targetCurrency": "USDC", "blockchain": "POLYGON", "purpose": "INVESTMENT", "name": "BRL → USDC investment" }' ``` ```json Response theme={null} { "id": "c7f64332-5d53-4685-8aa9-48a5348c18c2", "accountId": "44dd9734-176a-4ced-924f-103f8d50ea5e", "partnerFeeId": "123e4567-e89b-12d3-a456-426614174004", "status": "ACTIVE", "provider": "STARKBANK", "sourceCurrency": "BRL", "targetCurrency": "USDC", "blockchain": "POLYGON", "purpose": "INVESTMENT", "sourceDepositInfo": [ { "rail": "PIX", "brCode": "00020126...5204", "depositIdentifier": "IIDCDYjYUTbKouEUOyvxZ3xnU" } ], "createdAt": "2026-07-08T23:18:58.574Z" } ``` ### Request fields | **Field** | **Required** | **Description** | | :- | :- | :- | | `accountId` | Yes | Account that receives the deposits. Must belong to your project | | `targetCurrency` | Yes | Asset to convert into: `USDC` or `USDT` | | `purpose` | Yes | End-use category of the funds (see [Transaction purpose](/concepts/transactions#transaction-purpose)) | | `name` | Yes | A label for the rule, visible only to you | | `blockchain` | No | Network for delivery: `ETHEREUM`, `POLYGON`, `BASE`, `TRON`, or `STELLAR`. Falls back to your project's default blockchain | | `partnerFeeId` | No | [Partner fee](/concepts/partner-fees) applied to each conversion. Must belong to your project | ### Share the deposit details `sourceDepositInfo` carries everything the sender needs, per rail. What you share depends on the account's currency: | **Currency** | **Rail** | **Deposit details** | **How the sender pays** | | :- | :- | :- | :- | | BRL | PIX | Static PIX QR code (`brCode`) | Scans the QR or pastes the code, which embeds the reference | | MXN | SPEI | CLABE (`clabe`) plus memo (`reference`) | Transfers to the CLABE with the memo as the payment reference | | USD | ACH, FEDWIRE, SWIFT | Bank details (`accountNumber`, `routingNumber` or `bic`, `bankName`) | Wires to the account | | EUR | SEPA | Bank details (`iban`, `bic`) | Transfers to the IBAN | | GBP | FPS | Bank details (`sortCode`, `accountNumber`) | Transfers to the sort code and account number | For BRL and MXN, the `depositIdentifier` (PIX) or `reference` (SPEI) is what matches a deposit to the rule. It's embedded in the PIX `brCode` and passed as the SPEI memo. Any deposit paid with it is converted. > [!NOTE] > > A customer can have only **one active rule per USD, EUR, or GBP account** at a time. Deposits in these currencies are matched to the customer's account rather than a per-rule reference, so a second rule would be ambiguous. Creating one while another is active returns `409 AUTOCONVERSION_RULE_ALREADY_EXISTS`. Delete the existing rule to create a new one. BRL and MXN rules have no such limit. ### Errors | **Status** | **Cause** | | :- | :- | | `400` | `name` empty or missing, invalid enum value, or no `blockchain` set and your project has no default | | `403` | Account or partner fee belongs to another project | | `404` | Account or partner fee not found | | `409` | `AUTOCONVERSION_RULE_ALREADY_EXISTS` (see the note above) | ## Retrieve a rule Fetch a single rule, including its current `sourceDepositInfo`, with `GET /autoconversion-rules/{id}`. Use it to re-render a deposit QR code or check a rule's status after creation. ```bash Request theme={null} curl https://api-sandbox.lumx.io/autoconversion-rules/c7f64332-5d53-4685-8aa9-48a5348c18c2 \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "c7f64332-5d53-4685-8aa9-48a5348c18c2", "accountId": "44dd9734-176a-4ced-924f-103f8d50ea5e", "partnerFeeId": "123e4567-e89b-12d3-a456-426614174004", "status": "ACTIVE", "provider": "STARKBANK", "sourceCurrency": "BRL", "targetCurrency": "USDC", "blockchain": "POLYGON", "purpose": "INVESTMENT", "sourceDepositInfo": [ { "rail": "PIX", "brCode": "00020126...5204", "depositIdentifier": "IIDCDYjYUTbKouEUOyvxZ3xnU" } ], "createdAt": "2026-07-08T23:18:58.574Z" } ``` ## List rules List a customer's rules, newest first, with `GET /autoconversion-rules?customerId={customerId}`. Results are paginated: pass `size` (1–50, default 25) and the previous page's `nextCursor` as `cursor`. List items include the rule's `name` but not `sourceDepositInfo`, so fetch a single rule to get the deposit details. ```bash Request theme={null} curl "https://api-sandbox.lumx.io/autoconversion-rules?customerId=3c90c3cc-0d44-4b50-8888-8dd25736052a&size=25" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "data": [ { "id": "c7f64332-5d53-4685-8aa9-48a5348c18c2", "accountId": "44dd9734-176a-4ced-924f-103f8d50ea5e", "partnerFeeId": "123e4567-e89b-12d3-a456-426614174004", "status": "ACTIVE", "provider": "STARKBANK", "sourceCurrency": "BRL", "targetCurrency": "USDC", "blockchain": "POLYGON", "purpose": "INVESTMENT", "name": "BRL → USDC investment", "createdAt": "2026-07-08T23:18:58.574Z" } ], "pagination": { "size": 25, "total": 1, "hasNextPage": false, "nextCursor": null } } ``` ## Minimum amount Each deposit must be worth at least 50 USD in the account's currency. Deposits below the minimum are not converted, so the conversion never starts. If a deposit comes in under the minimum, [contact support](mailto:support@lumx.io). ## Track conversions Each matched deposit runs as an on-ramp transaction, so subscribe to the `onramp.*` [webhook events](/developer/webhooks#available-events) to follow the lifecycle, from `onramp.transferring_fiat` through `onramp.trading` and `onramp.transferring_stablecoin` to `onramp.success`. The receipt (amounts, exchange rate, fees, transaction hash) is delivered on success. ## Test in sandbox Deposits can't be made against sandbox accounts, so use `POST /autoconversion-rules/simulate-deposit` to trigger the flow end-to-end. Pass the rule's `sourceDepositInfo[0].depositIdentifier` (PIX) or `sourceDepositInfo[0].reference` (SPEI) as the `reference`. For USD, EUR, and GBP rules, omit `reference`, since deposits are matched to the account. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/autoconversion-rules/simulate-deposit \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "accountId": "44dd9734-176a-4ced-924f-103f8d50ea5e", "reference": "IIDCDYjYUTbKouEUOyvxZ3xnU", "amount": "100.00" }' ``` ```json Response theme={null} { "matchedRule": true } ``` `matchedRule` tells you whether the simulated deposit matched an active rule. If it did, the conversion runs and your webhook endpoint receives the lifecycle events. > [!WARNING] > > This endpoint only exists in sandbox. Calling it in production returns `404`. ## Related resources > [card] [Accounts](/concepts/accounts) > > Virtual accounts that receive the deposits rules convert. > [card] [Webhooks](/developer/webhooks) > > Subscribe to conversion lifecycle events. > [card] [Partner Fees](/concepts/partner-fees) > > Earn revenue on every conversion a rule executes. > [card] [Exchange Rates](/concepts/exchange-rates) > > How conversion rates are quoted and locked. # Build with Bolt.new Source: https://docs.lumx.io/guides/build-with-ai/bolt Scaffold a Lumx integration in Bolt.new: customer onboarding, on-ramp deposits, and transactions via the Lumx API and a copy-paste prompt — with your key kept server-side. Bolt.new builds full-stack apps in your browser on StackBlitz. Paste the prompt below to scaffold a Lumx integration with a server runtime, server-side helpers, and a starter UI — so your API key stays out of the bundle that ships to users. ## Prerequisites * A [Lumx](https://dashboard.lumx.io) account with a Sandbox API key * A [Bolt.new](https://bolt.new) project * Familiarity with how StackBlitz exposes env vars to your runtime (Node, Next.js, Astro, etc.) ## Step 1 — Get your Lumx credentials From the [Lumx Dashboard](https://dashboard.lumx.io), open **Developers → API Keys** and copy a Sandbox key. See [Authentication](/get-started/authentication). Keys are only shown once. ## Step 2 — Store the key in Bolt Open the **`.env`** file in the Bolt file tree (create it if missing) and add: ```bash .env theme={null} LUMX_API_KEY=lumx_sk_sandbox_xxxxxxxxxxxxxxxx LUMX_ENV=sandbox ``` Bolt's runtime will pick these up on the next preview reload. If your stack needs a different prefix (for example `PRIVATE_LUMX_API_KEY` in some frameworks), use that — but never use a prefix that exposes the value to the client (no `VITE_`, `NEXT_PUBLIC_`, `PUBLIC_`, etc.). > [!WARNING] > > Don't paste your API key into the Bolt chat. Add it to `.env` directly and keep that file out of any export or share link. ## Step 3 — Paste the prompt In the Bolt chat, paste: > [!NOTE] > > Bolt doesn't support MCP servers directly. Once you pull the generated project into Cursor or Claude Code locally, connect them to Lumx's [docs MCP](/developer/mcp-server) so the agent can pull endpoint shapes and field definitions on demand. ```text Lumx integration prompt theme={null} Build a stablecoin payments app that uses the Lumx API. Lumx connects local banking rails to stablecoins so I can collect, hold, convert, and pay out money in either form. Rules - Read LUMX_API_KEY from the runtime's server env. It must stay server-side — do not bake it into the client bundle. Never use a framework prefix that exposes env vars to the browser (no VITE_, NEXT_PUBLIC_, PUBLIC_, etc.). - Pick the base URL from LUMX_ENV. Default to https://api-sandbox.lumx.io. Use https://api.lumx.io when LUMX_ENV === "production". - Send every request with the headers: Authorization: Bearer ${LUMX_API_KEY} Content-Type: application/json - On non-2xx responses, surface the Lumx error body and HTTP status. Do not retry 4xx. Server routes to build 1. POST /api/customers — calls Lumx POST /customers. Body: type ("INDIVIDUAL" or "BUSINESS"), name, taxId, country (ISO-3), email. Then calls Lumx POST /customers/{id}/tos and returns the customer record plus the terms of service url. verification.link is omitted until the customer accepts the terms (the TERMS_OF_SERVICE requirement is APPROVED). 2. POST /api/on-ramp — calls Lumx POST /transactions/on-ramp. Body: customerId, currency (ISO-3), amount (string). Returns the rail-specific deposit instructions. 3. GET /api/transactions/:id — calls Lumx GET /transactions/:id and returns the transaction's current status and timeline. 4. GET /api/customers/:id — calls Lumx GET /customers/:id and returns the customer record, with verification.link once the terms are accepted. UI to build - Customer onboarding screen: form that calls POST /api/customers, then renders the terms of service url as a button. Once the customer accepts, reads GET /api/customers/:id and renders verification.link as a button. - On-ramp deposit screen: form that picks a customer, sets a currency and amount, calls POST /api/on-ramp, then renders the returned deposit instructions. Reference: https://docs.lumx.io ``` ## Step 4 — Run the preview Bolt will scaffold the project, install dependencies, and open the preview. Walk the flow once: create a customer, accept the terms of service, open the verification link, and start an on-ramp. Watch the network panel — `LUMX_API_KEY` should never appear in any client request. ## Example: a server-side Lumx helper If Bolt's generated code drifts, ask it to align to this helper. ```ts server/lumx.ts theme={null} const LUMX_BASE = process.env.LUMX_ENV === "production" ? "https://api.lumx.io" : "https://api-sandbox.lumx.io"; async function lumx(path: string, init?: RequestInit) { const res = await fetch(`${LUMX_BASE}${path}`, { ...init, headers: { Authorization: `Bearer ${process.env.LUMX_API_KEY}`, "Content-Type": "application/json", ...init?.headers, }, }); if (!res.ok) { throw new Error(`Lumx ${res.status}: ${await res.text()}`); } return res.json(); } export const createCustomer = (input: { type: "INDIVIDUAL" | "BUSINESS"; name: string; taxId: string; country: string; email: string; }) => lumx("/customers", { method: "POST", body: JSON.stringify(input) }); export const createOnRamp = (input: { customerId: string; currency: string; amount: string; }) => lumx("/transactions/on-ramp", { method: "POST", body: JSON.stringify(input), }); export const getTransaction = (id: string) => lumx(`/transactions/${id}`); ``` ## FAQ > [accordion] How do I keep the key out of the bundle in Vite-based projects? > > Skip the `VITE_` prefix. Vite only exposes prefixed vars to the client. Read `LUMX_API_KEY` from a server file (an API route, a server action, a backend route) that never imports into the client tree. > [accordion] Can I share my Bolt project link with a teammate? > > Yes — but the share link includes everything in the file tree. Remove `.env` (or replace its values with placeholders) before sharing, then re-add the real key locally. Rotate the key in the Dashboard if it ever leaks. > [accordion] How do I switch from Sandbox to Production? > > Replace `LUMX_API_KEY` with a Production key and set `LUMX_ENV=production`. Production access requires a call with the Lumx team — see [Environments](/get-started/environments). > [accordion] Bolt is asking for a database. Do I need one? > > Not for this scaffold — every piece of state lives in Lumx. Skip the database prompt unless you want to persist your own customer IDs or transaction history alongside Lumx's. ## Next steps * [Create a customer](/guides/create-a-customer) — full reference for the customer payload * [Webhooks](/developer/webhooks) — subscribe to status changes instead of polling * [Use cases](/guides/use-cases/global-accounts) — patterns for global accounts, payroll, treasury, and more # Build with Claude Code Source: https://docs.lumx.io/guides/build-with-ai/claude-code Drop a Lumx integration into any codebase from the terminal: customer onboarding, on-ramp deposits, and transactions via the docs MCP server and a copy-paste prompt. Claude Code runs as a terminal agent that can read your repo, write files, and execute commands. Paste the prompt below to scaffold a Lumx integration into an existing project — or to bootstrap a new one — without ever putting your API key in client code. ## Prerequisites * A [Lumx](https://dashboard.lumx.io) account with a Sandbox API key * [Claude Code](https://claude.com/claude-code) installed and authenticated * A project (or empty directory) where the integration should land ## Step 1 — Get your Lumx credentials From the [Lumx Dashboard](https://dashboard.lumx.io), open **Developers → API Keys** and copy a Sandbox key. See [Authentication](/get-started/authentication). Keys are only shown once. ## Step 2 — Store the key locally Drop the secrets into the project's env file. Keep it out of git. ```bash .env.local theme={null} LUMX_API_KEY=lumx_sk_sandbox_xxxxxxxxxxxxxxxx LUMX_ENV=sandbox ``` ```bash .gitignore (append if missing) theme={null} .env .env.local .env*.local ``` If your project doesn't already have a CLAUDE.md, consider adding one rule: ```md CLAUDE.md theme={null} - Lumx API calls only run server-side. Never reference `process.env.LUMX_API_KEY` from a file that ends up in the browser bundle. ``` > [!WARNING] > > Claude Code can read every file in the working directory. Make sure `.env.local` is gitignored before you commit, and never paste your key into the chat. ## Step 3 — Hook up the Lumx docs MCP The Lumx documentation ships as an [MCP server](/developer/mcp-server) so Claude Code can pull endpoint shapes and field definitions on demand — no copying schemas into the prompt. Run: ```bash Add the MCP server theme={null} claude mcp add --transport http lumx-docs https://docs.lumx.io/mcp ``` Restart your session. Claude Code will now answer questions like "what fields does POST /transactions/on-ramp accept?" by querying the docs directly, and the agent will reach for the MCP automatically when it needs schema details mid-task. ## Step 4 — Paste the prompt In the Claude Code session, paste the prompt below. ```text Lumx integration prompt theme={null} Add a stablecoin payments backend that uses the Lumx API. Lumx connects local banking rails to stablecoins so I can collect, hold, convert, and pay out money in either form. Rules - Read LUMX_API_KEY from environment variables (process.env in Node, os.environ in Python, ENV in Ruby, etc.). Never expose it in client-side code. Always call Lumx from server routes. - Pick the base URL from LUMX_ENV. Default to https://api-sandbox.lumx.io. Use https://api.lumx.io when LUMX_ENV is "production". - Send every request with the headers: Authorization: Bearer ${LUMX_API_KEY} Content-Type: application/json - On non-2xx responses, surface the Lumx error body and HTTP status. Do not retry 4xx. Before writing code - Inspect the repo and write idiomatic code for the existing stack — reuse the existing router style, HTTP client, and error helper if one is already there. If the project is empty, ask me which stack before scaffolding. Server routes to build 1. POST /api/customers — calls Lumx POST /customers. Body: type ("INDIVIDUAL" or "BUSINESS"), name, taxId, country (ISO-3), email. Then calls Lumx POST /customers/{id}/tos and returns the customer record plus the terms of service url. verification.link is omitted until the customer accepts the terms (the TERMS_OF_SERVICE requirement is APPROVED). 2. POST /api/on-ramp — calls Lumx POST /transactions/on-ramp. Body: customerId, currency (ISO-3), amount (string). Returns the rail-specific deposit instructions. 3. GET /api/transactions/:id — calls Lumx GET /transactions/:id and returns the transaction's current status and timeline. 4. GET /api/customers/:id — calls Lumx GET /customers/:id and returns the customer record, with verification.link once the terms are accepted. UI to build - Customer onboarding screen: form that calls POST /api/customers, then renders the terms of service url as a button. Once the customer accepts, reads GET /api/customers/:id and renders verification.link as a button. - On-ramp deposit screen: form that picks a customer, sets a currency and amount, calls POST /api/on-ramp, then renders the returned deposit instructions. After writing - Run the app, hit each route once with curl in the terminal, and paste the responses back so I can confirm shape. Reference: https://docs.lumx.io ``` ## Step 5 — Verify end to end Claude Code will write the files, run the project, and (because the prompt asks for it) call each route with curl to confirm the shape. Walk the UI flow once: create a customer, accept the terms of service, open the verification link, start an on-ramp, and watch the transaction status update. ## Example: a server-side Lumx helper The prompt aims for this shape. Drop it in directly if you'd rather skip the agent for the helper itself. ```ts server/lumx.ts theme={null} const LUMX_BASE = process.env.LUMX_ENV === "production" ? "https://api.lumx.io" : "https://api-sandbox.lumx.io"; async function lumx(path: string, init?: RequestInit) { const res = await fetch(`${LUMX_BASE}${path}`, { ...init, headers: { Authorization: `Bearer ${process.env.LUMX_API_KEY}`, "Content-Type": "application/json", ...init?.headers, }, }); if (!res.ok) { throw new Error(`Lumx ${res.status}: ${await res.text()}`); } return res.json(); } export const createCustomer = (input: { type: "INDIVIDUAL" | "BUSINESS"; name: string; taxId: string; country: string; email: string; }) => lumx("/customers", { method: "POST", body: JSON.stringify(input) }); export const createOnRamp = (input: { customerId: string; currency: string; amount: string; }) => lumx("/transactions/on-ramp", { method: "POST", body: JSON.stringify(input), }); export const getTransaction = (id: string) => lumx(`/transactions/${id}`); ``` ## FAQ > [accordion] Can I connect Lumx's docs MCP to Claude Code? > > Yes — Lumx ships an MCP server that surfaces every page in this site as a tool. See [MCP server](/developer/mcp-server) for setup. Claude Code will pull endpoint shapes and field definitions on demand. > [accordion] Claude Code wants to commit my .env.local. What now? > > Reject the change and add `.env*.local` to `.gitignore` first. If the file was already committed, rotate the key in the Dashboard and remove the file with `git rm --cached .env.local`. > [accordion] How do I switch from Sandbox to Production? > > Replace `LUMX_API_KEY` with a Production key and set `LUMX_ENV=production`. Production access requires a call with the Lumx team — see [Environments](/get-started/environments). > [accordion] Can the same prompt work in a fresh, empty repo? > > Yes. The prompt asks Claude Code to confirm a stack before scaffolding — just answer with the framework you want (Next.js, FastAPI, Rails, etc.) and it will set up an idiomatic project. ## Next steps * [Create a customer](/guides/create-a-customer) — full reference for the customer payload * [Webhooks](/developer/webhooks) — subscribe to status changes instead of polling * [MCP server](/developer/mcp-server) — give Claude Code structured access to the Lumx docs # Build with Codex Source: https://docs.lumx.io/guides/build-with-ai/codex Add a Lumx integration to any codebase with OpenAI Codex: customer onboarding, on-ramp deposits, and transactions via the docs MCP server and a copy-paste prompt. OpenAI's [Codex](https://openai.com/codex) ships as both a terminal CLI and a cloud-hosted agent. Paste the prompt below in either to scaffold a Lumx integration — server routes, helper, and a starter UI — with your API key kept out of the client bundle. ## Prerequisites * A [Lumx](https://dashboard.lumx.io) account with a Sandbox API key * [Codex CLI](https://github.com/openai/codex) installed and signed in, or access to Codex Cloud at [chatgpt.com/codex](https://chatgpt.com/codex) * A project (or empty directory) where the integration should land ## Step 1 — Get your Lumx credentials From the [Lumx Dashboard](https://dashboard.lumx.io), open **Developers → API Keys** and copy a Sandbox key. See [Authentication](/get-started/authentication). Keys are only shown once. ## Step 2 — Store the key locally Drop the secrets into the project's env file and keep it out of git. ```bash .env.local theme={null} LUMX_API_KEY=lumx_sk_sandbox_xxxxxxxxxxxxxxxx LUMX_ENV=sandbox ``` ```bash .gitignore (append if missing) theme={null} .env .env.local .env*.local ``` > [!WARNING] > > Codex (especially the cloud variant) runs in a sandbox that can read every file in the working directory. Make sure `.env.local` is gitignored before you commit, and never paste your key into the chat. ## Step 3 — Hook up the Lumx docs MCP (CLI only) The Codex CLI supports the Model Context Protocol. Add Lumx's [docs MCP server](/developer/mcp-server) to `~/.codex/config.toml`: ```toml ~/.codex/config.toml theme={null} [mcp_servers.lumx-docs] url = "https://docs.lumx.io/mcp" ``` Restart the CLI. Codex will now pull endpoint shapes and field definitions from the Lumx docs on demand instead of guessing them. > [!NOTE] > > Codex Cloud (the hosted variant) doesn't expose MCP configuration today — it runs in an isolated container. Use the CLI when you want the agent to consult Lumx's docs live. ## Step 4 — Paste the prompt In the Codex session, paste: ```text Lumx integration prompt theme={null} Add a stablecoin payments backend that uses the Lumx API. Lumx connects local banking rails to stablecoins so I can collect, hold, convert, and pay out money in either form. Rules - Read LUMX_API_KEY from environment variables (process.env in Node, os.environ in Python, ENV in Ruby, etc.). Never expose it in client-side code. Always call Lumx from server routes. - Pick the base URL from LUMX_ENV. Default to https://api-sandbox.lumx.io. Use https://api.lumx.io when LUMX_ENV is "production". - Send every request with the headers: Authorization: Bearer ${LUMX_API_KEY} Content-Type: application/json - On non-2xx responses, surface the Lumx error body and HTTP status. Do not retry 4xx. Before writing code - Inspect the repo and write idiomatic code for the existing stack — reuse the existing router style, HTTP client, and error helper if one is already there. If the project is empty, ask me which stack before scaffolding. Server routes to build 1. POST /api/customers — calls Lumx POST /customers. Body: type ("INDIVIDUAL" or "BUSINESS"), name, taxId, country (ISO-3), email. Then calls Lumx POST /customers/{id}/tos and returns the customer record plus the terms of service url. verification.link is omitted until the customer accepts the terms (the TERMS_OF_SERVICE requirement is APPROVED). 2. POST /api/on-ramp — calls Lumx POST /transactions/on-ramp. Body: customerId, currency (ISO-3), amount (string). Returns the rail-specific deposit instructions. 3. GET /api/transactions/:id — calls Lumx GET /transactions/:id and returns the transaction's current status and timeline. 4. GET /api/customers/:id — calls Lumx GET /customers/:id and returns the customer record, with verification.link once the terms are accepted. UI to build - Customer onboarding screen: form that calls POST /api/customers, then renders the terms of service url as a button. Once the customer accepts, reads GET /api/customers/:id and renders verification.link as a button. - On-ramp deposit screen: form that picks a customer, sets a currency and amount, calls POST /api/on-ramp, then renders the returned deposit instructions. After writing - Run the app, hit each route once with curl in the terminal, and paste the responses back so I can confirm shape. Reference: https://docs.lumx.io ``` ## Step 5 — Verify end to end Codex will write the files, run the project, and (because the prompt asks for it) call each route with curl to confirm the shape. Walk the UI flow once: create a customer, accept the terms of service, open the verification link, start an on-ramp, and watch the transaction status update. ## Example: a server-side Lumx helper The prompt aims for this shape. Drop it in directly if you'd rather wire the helper by hand. ```ts server/lumx.ts theme={null} const LUMX_BASE = process.env.LUMX_ENV === "production" ? "https://api.lumx.io" : "https://api-sandbox.lumx.io"; async function lumx(path: string, init?: RequestInit) { const res = await fetch(`${LUMX_BASE}${path}`, { ...init, headers: { Authorization: `Bearer ${process.env.LUMX_API_KEY}`, "Content-Type": "application/json", ...init?.headers, }, }); if (!res.ok) { throw new Error(`Lumx ${res.status}: ${await res.text()}`); } return res.json(); } export const createCustomer = (input: { type: "INDIVIDUAL" | "BUSINESS"; name: string; taxId: string; country: string; email: string; }) => lumx("/customers", { method: "POST", body: JSON.stringify(input) }); export const createOnRamp = (input: { customerId: string; currency: string; amount: string; }) => lumx("/transactions/on-ramp", { method: "POST", body: JSON.stringify(input), }); export const getTransaction = (id: string) => lumx(`/transactions/${id}`); ``` ## FAQ > [accordion] Should I use the Codex CLI or Codex Cloud for this? > > Use the CLI when you want the agent to consult Lumx's docs MCP server live (Step 3) or when you need it to touch existing project files in place. Use Codex Cloud for fresh-repo scaffolds or longer autonomous runs in OpenAI's sandbox — no MCP, but no local setup either. > [accordion] Codex Cloud wrote code that hardcoded my key. What now? > > The cloud agent sometimes inlines env values when it can read them. Pull the generated branch locally, replace any literal key with `process.env.LUMX_API_KEY`, and rotate the key in the Dashboard if a literal made it into a commit. > [accordion] How do I switch from Sandbox to Production? > > Replace `LUMX_API_KEY` with a Production key and set `LUMX_ENV=production`. Production access requires a call with the Lumx team — see [Environments](/get-started/environments). > [accordion] Can the same prompt work in a fresh, empty repo? > > Yes. The prompt asks Codex to confirm a stack before scaffolding — answer with the framework you want (Next.js, FastAPI, Rails, etc.) and it sets up an idiomatic project. ## Next steps * [Create a customer](/guides/create-a-customer) — full reference for the customer payload * [Webhooks](/developer/webhooks) — subscribe to status changes instead of polling * [MCP server](/developer/mcp-server) — give the Codex CLI structured access to the Lumx docs # Build with Cursor Source: https://docs.lumx.io/guides/build-with-ai/cursor Wire Lumx into any project from Cursor: customer onboarding, on-ramp deposits, and transactions via the docs MCP server and a copy-paste prompt. Cursor is an AI-first editor that can read your repo, write new files, and run shell commands. Paste the prompt below in the composer to add a Lumx integration to any TypeScript or JavaScript project — server routes, helper, and a starter UI. ## Prerequisites * A [Lumx](https://dashboard.lumx.io) account with a Sandbox API key * [Cursor](https://cursor.com) installed and pointed at a project with a server-side runtime (Next.js, Remix, Express, SvelteKit, FastAPI, Django, Rails, etc.) * A local env file (`.env.local`, `.env`, or framework equivalent) the project already reads at runtime ## Step 1 — Get your Lumx credentials From the [Lumx Dashboard](https://dashboard.lumx.io), open **Developers → API Keys** and copy a Sandbox key. See [Authentication](/get-started/authentication). Keys are only shown once. ## Step 2 — Store the key locally Add the secrets to your project's env file. Keep the file out of git. ```bash .env.local theme={null} LUMX_API_KEY=lumx_sk_sandbox_xxxxxxxxxxxxxxxx LUMX_ENV=sandbox ``` ```bash .gitignore (append if missing) theme={null} .env .env.local .env*.local ``` > [!WARNING] > > Cursor can read any file in your project. Make sure `.env.local` is gitignored before you commit — and never paste your key into the composer. ## Step 3 — Hook up the Lumx docs MCP The Lumx documentation ships as an [MCP server](/developer/mcp-server) so Cursor can pull endpoint shapes and field definitions on demand — no copying schemas into the prompt. Add this to `.cursor/mcp.json`: ```json .cursor/mcp.json theme={null} { "mcpServers": { "lumx-docs": { "url": "https://docs.lumx.io/mcp" } } } ``` Restart Cursor. The composer will now answer questions like "what fields does POST /transactions/on-ramp accept?" by querying the docs directly. ## Step 4 — Paste the prompt Open the composer (`Cmd+I` / `Ctrl+I`) and paste: ```text Lumx integration prompt theme={null} Add a stablecoin payments backend that uses the Lumx API. Lumx connects local banking rails to stablecoins so I can collect, hold, convert, and pay out money in either form. Rules - Read LUMX_API_KEY from environment variables (process.env in Node, os.environ in Python, ENV in Ruby, etc.). Never expose it in client-side code. Always call Lumx from server routes. - Pick the base URL from LUMX_ENV. Default to https://api-sandbox.lumx.io. Use https://api.lumx.io when LUMX_ENV is "production". - Send every request with the headers: Authorization: Bearer ${LUMX_API_KEY} Content-Type: application/json - On non-2xx responses, surface the Lumx error body and HTTP status. Do not retry 4xx. Match the project's existing conventions - Detect the project's language and framework first, then write idiomatic code for that stack (Next.js app router, Remix loaders, Express routes, FastAPI routers, Django views, Rails controllers, etc.). - Reuse the project's existing HTTP client, error helper, or validation library if one already exists. Server routes to build 1. POST /api/customers — calls Lumx POST /customers. Body: type ("INDIVIDUAL" or "BUSINESS"), name, taxId, country (ISO-3), email. Then calls Lumx POST /customers/{id}/tos and returns the customer record plus the terms of service url. verification.link is omitted until the customer accepts the terms (the TERMS_OF_SERVICE requirement is APPROVED). 2. POST /api/on-ramp — calls Lumx POST /transactions/on-ramp. Body: customerId, currency (ISO-3), amount (string). Returns the rail-specific deposit instructions. 3. GET /api/transactions/:id — calls Lumx GET /transactions/:id and returns the transaction's current status and timeline. 4. GET /api/customers/:id — calls Lumx GET /customers/:id and returns the customer record, with verification.link once the terms are accepted. UI to build (only if the project already has a frontend) - Customer onboarding screen: form that calls POST /api/customers, then renders the terms of service url as a button. Once the customer accepts, reads GET /api/customers/:id and renders verification.link as a button. - On-ramp deposit screen: form that picks a customer, sets a currency and amount, calls POST /api/on-ramp, then renders the returned deposit instructions. Reference: https://docs.lumx.io ``` ## Step 5 — Review the diff Cursor will propose file changes. Reject any that put the API key in client-side code or that hardcode endpoints — both are common drifts when the agent rushes. Accept the rest, run the project, and walk the flow end-to-end. ## Example: a server-side Lumx helper If you'd rather drop this in by hand, here's the shape the prompt aims for. ```ts server/lumx.ts theme={null} const LUMX_BASE = process.env.LUMX_ENV === "production" ? "https://api.lumx.io" : "https://api-sandbox.lumx.io"; async function lumx(path: string, init?: RequestInit) { const res = await fetch(`${LUMX_BASE}${path}`, { ...init, headers: { Authorization: `Bearer ${process.env.LUMX_API_KEY}`, "Content-Type": "application/json", ...init?.headers, }, }); if (!res.ok) { throw new Error(`Lumx ${res.status}: ${await res.text()}`); } return res.json(); } export const createCustomer = (input: { type: "INDIVIDUAL" | "BUSINESS"; name: string; taxId: string; country: string; email: string; }) => lumx("/customers", { method: "POST", body: JSON.stringify(input) }); export const createOnRamp = (input: { customerId: string; currency: string; amount: string; }) => lumx("/transactions/on-ramp", { method: "POST", body: JSON.stringify(input), }); export const getTransaction = (id: string) => lumx(`/transactions/${id}`); ``` ## FAQ > [accordion] Should I use Cursor Rules to keep the agent honest? > > Yes. Add a rule that says "Lumx calls only run server-side. Never reference `process.env.LUMX_API_KEY` from a file that ends up in the browser bundle." Cursor will apply it to every follow-up edit. > [accordion] Cursor wants to commit my .env.local. What now? > > Reject the change and add `.env*.local` to `.gitignore` first. If the file was already committed, rotate the key in the Dashboard and remove the file with `git rm --cached .env.local`. > [accordion] How do I switch from Sandbox to Production? > > Replace `LUMX_API_KEY` with a Production key and set `LUMX_ENV=production`. Production access requires a call with the Lumx team — see [Environments](/get-started/environments). > [accordion] Can the same prompt extend an existing app? > > Yes. The prompt explicitly asks the agent to match your router style and reuse any fetch wrapper that's already in the repo, so it slots into existing codebases without inventing new patterns. ## Next steps * [Create a customer](/guides/create-a-customer) — full reference for the customer payload * [Webhooks](/developer/webhooks) — subscribe to status changes instead of polling * [MCP server](/developer/mcp-server) — connect Cursor to Lumx's docs MCP for inline answers # Build with Lovable Source: https://docs.lumx.io/guides/build-with-ai/lovable Add cross-border payments to your Lovable app: customer onboarding, on-ramp deposits, and transaction tracking via the Lumx API and a copy-paste prompt. Lovable generates full-stack apps from natural language. Paste the prompt below to scaffold a Lumx-powered backend that creates customers, opens on-ramp transactions, and tracks settlement — without leaking your API key to the browser. ## Prerequisites * A [Lumx](https://dashboard.lumx.io) account with a Sandbox API key * A [Lovable](https://lovable.dev) project * A Supabase project connected to that Lovable app (used to store your Lumx key as a secret) ## Step 1 — Get your Lumx credentials From the [Lumx Dashboard](https://dashboard.lumx.io), go to **Developers → API Keys** and copy a Sandbox key. See [Authentication](/get-started/authentication) for details. Keys are only shown once — store them somewhere safe before closing the modal. ## Step 2 — Store the key in Lovable Open your Lovable project's secrets pane (or the Supabase dashboard for the connected project) and add: | Name | Value | | - | - | | `LUMX_API_KEY` | Your Sandbox key from Step 1 | | `LUMX_ENV` | `sandbox` while building, `production` when you go live | > [!WARNING] > > Never paste your API key into the chat or into client-side code. Lovable will read it from secrets at runtime. ## Step 3 — Paste the prompt In the Lovable chat, paste the prompt below and let it scaffold the integration. > [!NOTE] > > Lovable doesn't support MCP servers directly, but if you open the same project in Cursor or Claude Code later, connect them to Lumx's [docs MCP](/developer/mcp-server) so the agent can pull endpoint shapes and field definitions without re-reading these guides. ```text Lumx integration prompt theme={null} Build a stablecoin payments backend using the Lumx API. Lumx connects local banking rails to stablecoins so you can collect, hold, convert, and pay out money in either form. Rules - Read LUMX_API_KEY from environment secrets. Never expose it in client-side code. Always call Lumx from server routes. - Pick the base URL from LUMX_ENV. Default to https://api-sandbox.lumx.io. Use https://api.lumx.io when LUMX_ENV === "production". - Send every request with the headers: Authorization: Bearer ${LUMX_API_KEY} Content-Type: application/json - On non-2xx responses, surface the Lumx error body and HTTP status. Do not retry 4xx. Server routes to build 1. POST /api/customers — calls Lumx POST /customers. Body: type ("INDIVIDUAL" or "BUSINESS"), name, taxId, country (ISO-3), email. Then calls Lumx POST /customers/{id}/tos and returns the customer record plus the terms of service url. verification.link is omitted until the customer accepts the terms (the TERMS_OF_SERVICE requirement is APPROVED). 2. POST /api/on-ramp — calls Lumx POST /transactions/on-ramp. Body: customerId, currency (ISO-3), amount (string). Returns the rail-specific deposit instructions. 3. GET /api/transactions/:id — calls Lumx GET /transactions/:id and returns the transaction's current status and timeline. 4. GET /api/customers/:id — calls Lumx GET /customers/:id and returns the customer record, with verification.link once the terms are accepted. UI to build - Customer onboarding screen: form that calls POST /api/customers, then renders the terms of service url as a button. Once the customer accepts, reads GET /api/customers/:id and renders verification.link as a button the customer can open to complete identity verification. - On-ramp deposit screen: form that picks a customer, sets a currency and amount, calls POST /api/on-ramp, then renders the returned deposit instructions. Reference: https://docs.lumx.io ``` ## Step 4 — Test the flow Lovable will generate routes and a UI. Open the preview, create a sandbox customer, accept the terms of service, open the verification link, and try an on-ramp deposit. Every transaction you create in Sandbox is fully simulated — no real money moves. ## Example: a server-side Lumx helper If Lovable's generated code drifts, drop this helper in (or ask Lovable to align to it) so calls stay consistent. ```ts server/lumx.ts theme={null} const LUMX_BASE = process.env.LUMX_ENV === "production" ? "https://api.lumx.io" : "https://api-sandbox.lumx.io"; async function lumx(path: string, init?: RequestInit) { const res = await fetch(`${LUMX_BASE}${path}`, { ...init, headers: { Authorization: `Bearer ${process.env.LUMX_API_KEY}`, "Content-Type": "application/json", ...init?.headers, }, }); if (!res.ok) { throw new Error(`Lumx ${res.status}: ${await res.text()}`); } return res.json(); } export const createCustomer = (input: { type: "INDIVIDUAL" | "BUSINESS"; name: string; taxId: string; country: string; email: string; }) => lumx("/customers", { method: "POST", body: JSON.stringify(input) }); export const createOnRamp = (input: { customerId: string; currency: string; amount: string; }) => lumx("/transactions/on-ramp", { method: "POST", body: JSON.stringify(input), }); export const getTransaction = (id: string) => lumx(`/transactions/${id}`); ``` ## FAQ > [accordion] Why does the prompt force a server route for every call? > > Lumx API keys carry full account access. A key in client-side code is a key on every visitor's machine. Lovable's server routes (or a Supabase Edge Function) keep it where it belongs. > [accordion] Can I use Lovable's Supabase integration to store the key? > > Yes. Add `LUMX_API_KEY` as a Supabase secret and reference it from a Supabase Edge Function. The prompt's server routes work the same way. > [accordion] How do I switch from Sandbox to Production? > > Replace `LUMX_API_KEY` with a Production key and set `LUMX_ENV=production`. Production access requires a call with the Lumx team — see [Environments](/get-started/environments). > [accordion] Which rails does the on-ramp support? > > The available rails depend on the currency. A BRL on-ramp returns PIX details; USD returns ACH and Fedwire. See [Coverage](/get-started/coverage) for the full list. ## Next steps * [Create a customer](/guides/create-a-customer) — full reference for the customer payload * [Webhooks](/developer/webhooks) — subscribe to status changes instead of polling * [Use cases](/guides/use-cases/global-accounts) — patterns for global accounts, payroll, treasury, and more # Ship with AI Source: https://docs.lumx.io/guides/build-with-ai/overview Ship stablecoin payments in apps built with AI coding tools and agents. Lumx drops into any builder through copy-paste prompts, a docs MCP server, and a REST API. Add cross-border payments to any app you build with AI — customer onboarding, on-ramp deposits, off-ramps, transfers, and webhooks — by pasting a prompt into the tool you already use. Lumx ships three surfaces that fit any AI workflow: **copy-paste prompts**, a **docs MCP server** for inline schema lookup, and the **REST API** itself. ## Pick your path ### App builders Tools that generate and host a full app from a prompt. Each guide shows where the builder keeps your API key and ships one prompt that scaffolds onboarding, deposits, and transaction lookup. * [**Lovable**](/guides/build-with-ai/lovable) — add Lumx to a Lovable app with Supabase-backed secrets. * [**v0**](/guides/build-with-ai/v0) — generate a Next.js Lumx flow with Vercel env vars and server actions. * [**Bolt.new**](/guides/build-with-ai/bolt) — wire Lumx into a Bolt.new StackBlitz project. * [**Replit**](/guides/build-with-ai/replit) — ship with Replit Agent and Replit Secrets. ### Coding assistants Agents that work inside a codebase you already have. Each guide connects the docs MCP server and ships a prompt that adds Lumx to your existing project. * [**Cursor**](/guides/build-with-ai/cursor) — pair Lumx's docs MCP server with Cursor's composer. * [**Claude Code**](/guides/build-with-ai/claude-code) — drop Lumx into any codebase from the terminal. * [**Codex**](/guides/build-with-ai/codex) — use OpenAI's CLI or cloud agent with the Lumx docs MCP. ### Prompt library Prompts for one task each, which work with any coding agent. Use them once the basics run, or when you need a single capability rather than a starter app. * [**Sandbox quickstart**](/prompts/sandbox-quickstart) — prove a deposit-to-stablecoin round trip before you write product code. * [**Integrate off-ramps**](/prompts/integrate-offramp) — pay out from a stablecoin balance to a local bank account. * [**Integrate webhooks**](/prompts/integrate-webhooks) — receive signed events that survive retries and secret rotation. * [**Migrate SWIFT wires**](/prompts/migrate-from-swift-wires) — move international wires onto local payout rails. See the [full library](/prompts/overview) for onboarding, virtual accounts, marketplaces, reconciliation, and go-live audits. ## The three integration surfaces **1. Copy-paste prompts** Builder and assistant guides ship a prompt that scaffolds the integration end-to-end: server routes for customer onboarding, on-ramp deposits, and transaction lookup, plus a starter UI. The [prompt library](/prompts/overview) adds one prompt per task, from payouts to migrations. Paste a prompt once and the agent does the rest. ```text Prompt fragment theme={null} Build a stablecoin payments backend that uses the Lumx API. Server routes to build: POST /api/customers, POST /api/on-ramp, GET /api/transactions/:id. Read LUMX_API_KEY from environment variables. Never expose it in client-side code. ``` **2. Docs MCP server** The Lumx documentation runs as a [Model Context Protocol](https://modelcontextprotocol.io/) server. Connect it to Claude Code, Cursor, VS Code, or Claude Web, and your agent pulls endpoint shapes, field definitions, and examples on demand — no copying schemas into the prompt. ```bash Claude Code theme={null} claude mcp add --transport http lumx-docs https://docs.lumx.io/mcp ``` See [MCP server](/developer/mcp-server) for setup in every supported host. **3. REST API** For everything else — CI scripts, edge functions, server jobs, backends an AI tool doesn't touch — call the Lumx REST API directly. Every prompt above eventually compiles down to these calls. ```bash cURL theme={null} curl https://api-sandbox.lumx.io/customers \ -H "Authorization: Bearer YOUR_API_KEY" ``` See [Authentication](/get-started/authentication) for credentials and the API reference for endpoint shapes. > [!NOTE] > > Get your API key from the [Lumx Dashboard](https://dashboard.lumx.io). Start in Sandbox — every transaction is simulated and no real money moves. Production access requires a call with our team — see [Environments](/get-started/environments). ## Next steps * [Create a customer](/guides/create-a-customer) — the first call in every Lumx integration * [Use cases](/guides/use-cases/global-accounts) — global accounts, payroll, treasury, marketplaces, remittances * [Webhooks](/developer/webhooks) — subscribe to transaction status changes instead of polling # Build with Replit Source: https://docs.lumx.io/guides/build-with-ai/replit Ship a Lumx-powered app from Replit Agent: customer onboarding, on-ramp deposits, and transactions with Replit Secrets and a copy-paste prompt. Replit Agent builds and runs full-stack apps in a hosted environment. Paste the prompt below to scaffold a Lumx integration — server routes, helper, and a starter UI — with your API key stored as a Replit Secret so it never touches the client. ## Prerequisites * A [Lumx](https://dashboard.lumx.io) account with a Sandbox API key * A [Replit](https://replit.com) account with Agent access * A new or existing Repl that runs a server (Node, Python, Bun, etc.) ## Step 1 — Get your Lumx credentials From the [Lumx Dashboard](https://dashboard.lumx.io), open **Developers → API Keys** and copy a Sandbox key. See [Authentication](/get-started/authentication). Keys are only shown once. ## Step 2 — Store the key in Replit Secrets In your Repl, open the **Secrets** tool (lock icon in the left rail) and add: | Key | Value | | - | - | | `LUMX_API_KEY` | Your Sandbox key from Step 1 | | `LUMX_ENV` | `sandbox` while building, `production` when you go live | Replit Secrets are injected into `process.env` (or `os.environ`) at runtime and never appear in the file tree or the public Repl URL. > [!WARNING] > > Don't paste your API key into the Agent chat. Add it as a Secret and reference it by name only. ## Step 3 — Paste the prompt In the Agent chat, paste: > [!NOTE] > > Replit Agent doesn't support MCP servers directly. Once you clone the Repl down locally and open it in Cursor or Claude Code, connect them to Lumx's [docs MCP](/developer/mcp-server) so the agent can pull endpoint shapes and field definitions on demand. ```text Lumx integration prompt theme={null} Build a stablecoin payments app that uses the Lumx API. Lumx connects local banking rails to stablecoins so I can collect, hold, convert, and pay out money in either form. Rules - Read LUMX_API_KEY from Replit Secrets (process.env on Node, os.environ on Python). Never expose it in client-side code — call Lumx from a server route only. - Pick the base URL from LUMX_ENV. Default to https://api-sandbox.lumx.io. Use https://api.lumx.io when LUMX_ENV === "production". - Send every request with the headers: Authorization: Bearer ${LUMX_API_KEY} Content-Type: application/json - On non-2xx responses, surface the Lumx error body and HTTP status. Do not retry 4xx. Server routes to build 1. POST /api/customers — calls Lumx POST /customers. Body: type ("INDIVIDUAL" or "BUSINESS"), name, taxId, country (ISO-3), email. Then calls Lumx POST /customers/{id}/tos and returns the customer record plus the terms of service url. verification.link is omitted until the customer accepts the terms (the TERMS_OF_SERVICE requirement is APPROVED). 2. POST /api/on-ramp — calls Lumx POST /transactions/on-ramp. Body: customerId, currency (ISO-3), amount (string). Returns the rail-specific deposit instructions. 3. GET /api/transactions/:id — calls Lumx GET /transactions/:id and returns the transaction's current status and timeline. 4. GET /api/customers/:id — calls Lumx GET /customers/:id and returns the customer record, with verification.link once the terms are accepted. UI to build - Customer onboarding screen: form that calls POST /api/customers, then renders the terms of service url as a button. Once the customer accepts, reads GET /api/customers/:id and renders verification.link as a button. - On-ramp deposit screen: form that picks a customer, sets a currency and amount, calls POST /api/on-ramp, then renders the returned deposit instructions. Reference: https://docs.lumx.io ``` ## Step 4 — Run the Repl Agent will write the files, install dependencies, and start the server. Open the webview, create a sandbox customer, accept the terms of service, open the verification link, and try an on-ramp. Every transaction in Sandbox is simulated — no real money moves. ## Example: a server-side Lumx helper The prompt aims for this shape. Drop it in directly if you'd rather wire the helper by hand. ```ts server/lumx.ts theme={null} const LUMX_BASE = process.env.LUMX_ENV === "production" ? "https://api.lumx.io" : "https://api-sandbox.lumx.io"; async function lumx(path: string, init?: RequestInit) { const res = await fetch(`${LUMX_BASE}${path}`, { ...init, headers: { Authorization: `Bearer ${process.env.LUMX_API_KEY}`, "Content-Type": "application/json", ...init?.headers, }, }); if (!res.ok) { throw new Error(`Lumx ${res.status}: ${await res.text()}`); } return res.json(); } export const createCustomer = (input: { type: "INDIVIDUAL" | "BUSINESS"; name: string; taxId: string; country: string; email: string; }) => lumx("/customers", { method: "POST", body: JSON.stringify(input) }); export const createOnRamp = (input: { customerId: string; currency: string; amount: string; }) => lumx("/transactions/on-ramp", { method: "POST", body: JSON.stringify(input), }); export const getTransaction = (id: string) => lumx(`/transactions/${id}`); ``` ## FAQ > [accordion] Are Replit Secrets visible to forks of my Repl? > > No. Secrets stay with the Repl owner and are not copied when someone forks. The forker has to add their own. That's also why the prompt asks Agent to read from `process.env`, not from a file in the tree. > [accordion] Can I deploy this as a Replit Deployment? > > Yes. Replit Deployments inherit the Repl's secrets. The same `LUMX_API_KEY` and `LUMX_ENV` apply — flip `LUMX_ENV` to `production` and swap the key when you're ready. > [accordion] How do I switch from Sandbox to Production? > > Update the two Secrets — set `LUMX_ENV=production` and replace `LUMX_API_KEY` with a Production key. Production access requires a call with the Lumx team — see [Environments](/get-started/environments). > [accordion] Agent wants to print the key in a log to debug. Should I let it? > > No. Logs in Replit can be shared with viewers of the Repl. Ask Agent to redact the key and only log the response status and request path. ## Next steps * [Create a customer](/guides/create-a-customer) — full reference for the customer payload * [Webhooks](/developer/webhooks) — subscribe to status changes instead of polling * [Use cases](/guides/use-cases/global-accounts) — patterns for global accounts, payroll, treasury, and more # Build with v0 Source: https://docs.lumx.io/guides/build-with-ai/v0 Generate a Lumx-powered Next.js app from v0: customer onboarding, on-ramp deposits, and transactions with server actions, Vercel env vars, and a copy-paste prompt. v0 by Vercel generates Next.js apps from chat. Paste the prompt below and v0 will scaffold a Lumx integration with server actions for customer onboarding, on-ramp deposits, and transaction status — ready to deploy to Vercel without leaking your API key to the browser. ## Prerequisites * A [Lumx](https://dashboard.lumx.io) account with a Sandbox API key * A [v0](https://v0.dev) chat * A [Vercel](https://vercel.com) project for the generated app (created automatically when you click **Deploy** from v0) ## Step 1 — Get your Lumx credentials From the [Lumx Dashboard](https://dashboard.lumx.io), open **Developers → API Keys** and copy a Sandbox key. See [Authentication](/get-started/authentication). Keys are only shown once. ## Step 2 — Store the key as Vercel env vars In your Vercel project, go to **Settings → Environment Variables** and add: | Name | Value | | - | - | | `LUMX_API_KEY` | Your Sandbox key from Step 1 | | `LUMX_ENV` | `sandbox` while building, `production` when you go live | Mark them **Server-side only**. v0's generated server actions and route handlers will pick them up automatically on the next deploy. > [!WARNING] > > Don't paste your API key into the v0 chat — v0 messages are not a secret store. Add the key in the Vercel dashboard instead. ## Step 3 — Paste the prompt In the v0 chat, paste: > [!NOTE] > > v0 doesn't support MCP servers directly. Once you pull the generated repo into Cursor or Claude Code for follow-up work, connect them to Lumx's [docs MCP](/developer/mcp-server) so the agent can pull endpoint shapes and field definitions on demand. ```text Lumx integration prompt theme={null} Build a Next.js (App Router) stablecoin payments app that uses the Lumx API. Lumx connects local banking rails to stablecoins so I can collect, hold, convert, and pay out money in either form. Rules - Read LUMX_API_KEY from process.env. It must stay server-side — use it from server actions and route handlers only, never in a "use client" file. - Pick the base URL from LUMX_ENV. Default to https://api-sandbox.lumx.io. Use https://api.lumx.io when LUMX_ENV === "production". - Send every request with the headers: Authorization: Bearer ${LUMX_API_KEY} Content-Type: application/json - On non-2xx responses, surface the Lumx error body and HTTP status. Do not retry 4xx. Server actions / route handlers to build 1. POST /api/customers — calls Lumx POST /customers. Body: type ("INDIVIDUAL" or "BUSINESS"), name, taxId, country (ISO-3), email. Then calls Lumx POST /customers/{id}/tos and returns the customer record plus the terms of service url. verification.link is omitted until the customer accepts the terms (the TERMS_OF_SERVICE requirement is APPROVED). 2. POST /api/on-ramp — calls Lumx POST /transactions/on-ramp. Body: customerId, currency (ISO-3), amount (string). Returns the rail-specific deposit instructions. 3. GET /api/transactions/[id] — calls Lumx GET /transactions/:id and returns the transaction's current status and timeline. 4. GET /api/customers/[id] — calls Lumx GET /customers/:id and returns the customer record, with verification.link once the terms are accepted. UI to build (App Router pages) - /onboarding: form that submits to the customer server action, then renders the terms of service url as a button. Once the customer accepts, reads GET /api/customers/[id] and renders verification.link as a button the customer can open. - /on-ramp: form that picks a customer, sets a currency and amount, calls the on-ramp action, then renders the returned deposit instructions in a clean card layout. Style: shadcn/ui components, Tailwind, light mode default. Reference: https://docs.lumx.io ``` ## Step 4 — Deploy and test Click **Deploy** in v0 (or push from the connected GitHub repo). Once the build picks up the env vars, walk the flow in the deployed preview: create a sandbox customer, accept the terms of service, open the verification link, and start an on-ramp. ## Example: a server-side Lumx helper The prompt aims for this shape. If v0's generated code diverges, ask it to align to the helper below. ```ts lib/lumx.ts theme={null} const LUMX_BASE = process.env.LUMX_ENV === "production" ? "https://api.lumx.io" : "https://api-sandbox.lumx.io"; async function lumx(path: string, init?: RequestInit) { const res = await fetch(`${LUMX_BASE}${path}`, { ...init, headers: { Authorization: `Bearer ${process.env.LUMX_API_KEY}`, "Content-Type": "application/json", ...init?.headers, }, }); if (!res.ok) { throw new Error(`Lumx ${res.status}: ${await res.text()}`); } return res.json(); } export const createCustomer = (input: { type: "INDIVIDUAL" | "BUSINESS"; name: string; taxId: string; country: string; email: string; }) => lumx("/customers", { method: "POST", body: JSON.stringify(input) }); export const createOnRamp = (input: { customerId: string; currency: string; amount: string; }) => lumx("/transactions/on-ramp", { method: "POST", body: JSON.stringify(input), }); export const getTransaction = (id: string) => lumx(`/transactions/${id}`); ``` ## FAQ > [accordion] Where do server actions read the env vars from? > > Locally, from `.env.local`. On Vercel, from the env vars you set in the project. Both reach `process.env` at runtime — the key never leaves the server. > [accordion] v0 put my key in a 'use client' file. What now? > > Move the call into a server action (`"use server"`) or a route handler. Client components should call those, not Lumx directly. Ask v0 to "convert the Lumx call into a server action" and it will reshuffle the boundary. > [accordion] How do I switch from Sandbox to Production? > > Replace `LUMX_API_KEY` with a Production key and set `LUMX_ENV=production` in Vercel. Production access requires a call with the Lumx team — see [Environments](/get-started/environments). > [accordion] Can I preview locally before deploying? > > Yes. Run `vercel env pull` to copy the project env into `.env.local`, then `pnpm dev` (or `npm run dev`). The server actions will read the same key. ## Next steps * [Create a customer](/guides/create-a-customer) — full reference for the customer payload * [Webhooks](/developer/webhooks) — subscribe to status changes instead of polling * [Use cases](/guides/use-cases/global-accounts) — patterns for global accounts, payroll, treasury, and more # Business Verification (KYB) Source: https://docs.lumx.io/guides/business-verification Complete identity verification for business customers After creating a business customer, you must complete identity verification (KYB) before they can transact. Business verification requires company information, documents, and details about associated parties (UBOs, shareholders, and representatives). > [!NOTE] > > If you don't want to build the verification flow via API, you can share the > `verification.link` returned in the customer response directly with your > customer. The link opens a guided flow where they can submit all required > information and documents without any additional API integration. It's only > returned once the customer has accepted the terms of service (step 1). > [!WARNING] > > If you start the verification process through the API, do not share the > `verification.link` with your customer. Mixing both approaches can cause > conflicts and lead to unexpected issues during the verification flow. ## Prerequisites * A business customer already created (see [Create a Customer](/guides/create-a-customer)) * An API key from the [Dashboard](https://dashboard.lumx.io) * Your environment base URL (see [Environments](/get-started/environments)) ## Verification flow **1. Accept terms of service** Before starting verification, the customer must accept the terms of service. Send a `POST` request to `/customers/{id}/tos` to get the acceptance URL. You can optionally include a `redirectUrl` to redirect the customer after they accept. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/tos \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "redirectUrl": "https://yourapp.com/onboarding/next-step" }' ``` ```json Response theme={null} { "url": "https://dashboard.lumx.io/tos/sandbox/eyJhbGciOiJSUzI1...", "expiresAt": "2026-04-09T16:00:00Z" } ``` Share the returned URL with your customer. The link expires after 24 hours. Generate a new one if needed. The `requirements` array will show `TERMS_OF_SERVICE` as `NOT_SENT` until they accept. You can check the status by [reading the customer](/api-reference/customers/read-a-customer). > [!NOTE] > > The request body is optional. If you don't need to redirect the customer after acceptance, you can send the request without a body. **2. Send additional information** Send a `PATCH` request to `/customers/{id}/additional-information` with the company's details and transactional information. ```bash Request theme={null} curl -X PATCH https://api-sandbox.lumx.io/customers/{id}/additional-information \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "BUSINESS", "phone": "+5511999999999", "address": { "country": "BRA", "line1": "Av. Paulista, 1000, Sala 101", "city": "São Paulo", "state": "SP", "postalCode": "01310-100" }, "website": "https://example.com", "businessActivity": "PAYMENT_PROCESSOR_GATEWAY", "servicesProvided": "We provide blockchain-based payment infrastructure for B2B cross-border transactions.", "monthlyRevenueInUSD": "250000", "monthlyTransactionVolumeInUSD": "100K_TO_1M", "sourceOfFunds": "OPERATING_SERVICE_REVENUE", "transactionCounterparties": ["CUSTOMERS", "SUPPLIERS_VENDORS"], "isRegulatedActivity": false }' ``` All fields are required unless noted otherwise. Here's a summary of the business-specific fields: | Field | Description | | :- | :- | | `website` | Company website URL | | `businessActivity` | What the company does (e.g., `PAYMENT_PROCESSOR_GATEWAY`) | | `servicesProvided` | Description of the services or products the company offers | | `monthlyRevenueInUSD` | Monthly revenue in USD (e.g., `"250000"`) | | `monthlyTransactionVolumeInUSD` | Expected monthly volume range (e.g., `100K_TO_1M`) | | `sourceOfFunds` | Origin of funds (e.g., `OPERATING_SERVICE_REVENUE`) | | `transactionCounterparties` | Who the company sends money to or receives it from | | `isRegulatedActivity` | Whether the company carries out regulated activities | | `regulatedActivityDetails` | Required when `isRegulatedActivity` is `true` | `monthlyRevenueInUSD` takes a non-negative amount as a string, with up to 2 decimal places. `businessActivity`, `monthlyTransactionVolumeInUSD`, `sourceOfFunds`, and `transactionCounterparties` only accept the values listed in [Business activities](/additional-information/company-types) and [Onboarding fields](/additional-information/onboarding-fields). Any other value returns `400`. > [!NOTE] > > Customers created before October 28, 2026 keep the previous questionnaire, with fields such as `companyType`, `annualRevenue`, `jurisdictions`, and `involvedActivities`. Keep sending that body for them: a body that doesn't match the customer's questionnaire returns `400` with `VALIDATION_ERROR`. See [Previous questionnaire](/additional-information/onboarding-fields#previous-questionnaire) and the [changelog](/changelog#october-7-2026). For the full schema, see the [API Reference](/api-reference/customers/send-additional-information). **3. Upload company documents** Upload company documents using `POST /customers/{id}/documents`. Each document is sent as a `multipart/form-data` request. Required documents: * `INCORPORATION_ARTICLES` * `SHAREHOLDER_REGISTRY` * `DIRECTORS_REGISTRY` * `PROOF_OF_ADDRESS` (must be from the last 90 days) Optional documents: * `POWER_OF_ATTORNEY` * `REGULATED_ACTIVITY_DOCUMENT` * `SIGNED_BALANCE_SHEET` * `SIGNED_CORPORATE_STRUCTURE_CHART` * `SIGNED_INCOME_STATEMENT` * `OTHER` (any additional document) ```bash Upload incorporation articles theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/documents \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/path/to/incorporation-articles.pdf" \ -F "type=INCORPORATION_ARTICLES" \ -F "country=BRA" ``` ```bash Upload shareholder registry theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/documents \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/path/to/shareholder-registry.pdf" \ -F "type=SHAREHOLDER_REGISTRY" \ -F "country=BRA" ``` ```bash Upload directors registry theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/documents \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/path/to/directors-registry.pdf" \ -F "type=DIRECTORS_REGISTRY" \ -F "country=BRA" ``` Files must be JPG, PNG, or PDF with a maximum size of 50MB. **4. Add associated parties** Add the company's UBOs, shareholders, and representatives using `POST /customers/{id}/associated-parties`. Each associated party can have one or more roles. > [!WARNING] > > Before starting verification, the company must have at least one associated party assigned to each required role: **UBO**, **Shareholder**, and **Representative**. > >  A single individual may fulfill multiple roles, and multiple individuals may be assigned to the same role. There are three role types: * `UBO` (Ultimate Beneficial Owner): always `INDIVIDUAL` type. Requires `ownershipPercentage` (25–100%). * `SHAREHOLDER`: can be `INDIVIDUAL` or `BUSINESS` type. Requires `ownershipPercentage` (25–100%). * `REPRESENTATIVE`: always `INDIVIDUAL` type. No `ownershipPercentage` needed. Individual associated parties also require `monthlyIncomeInUSD`, a non-negative amount in USD as a string with up to 2 decimal places. Business associated parties don't take it. > [!NOTE] > > For customers created before October 28, 2026, associated parties keep the previous body: leave out `monthlyIncomeInUSD`, or the request returns `400`. See the [changelog](/changelog#october-7-2026). > [tab] UBO (Individual) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/customers/{id}/associated-parties \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -d '{ > "type": "INDIVIDUAL", > "roles": ["UBO"], > "name": "Maria Santos", > "birthDate": "1985-07-10", > "email": "maria.santos@example.com", > "taxId": "987.654.321-00", > "address": { > "country": "BRA", > "line1": "Rua das Acácias, 456", > "city": "Rio de Janeiro", > "state": "RJ", > "postalCode": "20000-000" > }, > "ownershipPercentage": 40, > "monthlyIncomeInUSD": "12000" > }' > ``` > > ```json Response theme={null} > { > "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", > "type": "INDIVIDUAL", > "roles": ["UBO"], > "name": "Maria Santos", > "taxId": "987.654.321-00", > "birthDate": "1985-07-10", > "email": "maria.santos@example.com", > "address": { > "country": "BRA", > "line1": "Rua das Acácias, 456", > "city": "Rio de Janeiro", > "state": "RJ", > "postalCode": "20000-000" > }, > "ownershipPercentage": 40, > "monthlyIncomeInUSD": "12000", > "verification": { > "status": "NOT_STARTED", > "level": "STANDARD", > "link": "https://in.sumsub.com/websdk/p/sbx_aA00bB11cC33dD44" > }, > "createdAt": "2024-01-15T11:00:00Z", > "updatedAt": "2024-01-15T11:00:00Z" > } > ``` > [tab] Shareholder (Individual) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/customers/{id}/associated-parties \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -d '{ > "type": "INDIVIDUAL", > "roles": ["SHAREHOLDER"], > "name": "Pedro Oliveira", > "birthDate": "1978-12-05", > "email": "pedro.oliveira@example.com", > "taxId": "111.222.333-44", > "address": { > "country": "BRA", > "line1": "Av. Brasil, 789", > "city": "São Paulo", > "state": "SP", > "postalCode": "01000-000" > }, > "ownershipPercentage": 35, > "monthlyIncomeInUSD": "9000" > }' > ``` > [tab] Shareholder (Business) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/customers/{id}/associated-parties \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -d '{ > "type": "BUSINESS", > "roles": ["SHAREHOLDER"], > "legalName": "Investment Fund ABC", > "incorporationDate": "2010-05-20", > "email": "contact@fundabc.com", > "taxId": "99.988.877/0001-66", > "address": { > "country": "USA", > "line1": "123 Wall Street", > "city": "New York", > "state": "NY", > "postalCode": "10005" > }, > "ownershipPercentage": 25 > }' > ``` > [tab] Representative > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/customers/{id}/associated-parties \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -d '{ > "type": "INDIVIDUAL", > "roles": ["REPRESENTATIVE"], > "name": "Carlos Mendes", > "birthDate": "1990-03-15", > "email": "carlos.mendes@example.com", > "taxId": "555.666.777-88", > "address": { > "country": "BRA", > "line1": "Rua Principal, 100", > "city": "Belo Horizonte", > "state": "MG", > "postalCode": "30000-000" > }, > "monthlyIncomeInUSD": "6000" > }' > ``` > > > [!NOTE] > > > > Representatives do not require `ownershipPercentage`. > [tab] Multiple roles > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/customers/{id}/associated-parties \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -d '{ > "type": "INDIVIDUAL", > "roles": ["UBO", "REPRESENTATIVE"], > "name": "Ana Silva", > "birthDate": "1982-09-25", > "email": "ana.silva@example.com", > "taxId": "444.555.666-77", > "address": { > "country": "BRA", > "line1": "Av. das Nações, 200", > "city": "Brasília", > "state": "DF", > "postalCode": "70000-000" > }, > "ownershipPercentage": 30, > "monthlyIncomeInUSD": "15000" > }' > ``` > > > [!NOTE] > > > > When an associated party has multiple roles, include all applicable roles in the `roles` array. If UBO or SHAREHOLDER is included, `ownershipPercentage` is required. Each associated party's `verification.link` follows the customer's terms of service acceptance: it's omitted until the customer's `TERMS_OF_SERVICE` requirement is `APPROVED`. **5. Upload associated party documents** Upload documents for each associated party using the same `POST /customers/{id}/documents` endpoint, but include the `associatedPartyId` field. Individual associated parties (UBOs, shareholders, and representatives) need an identity document (`ID_CARD`, `PASSPORT`, or `DRIVERS_LICENSE`) and a `PROOF_OF_ADDRESS` from the last 90 days: > [!NOTE] > > Identity documents (`ID_CARD` and `DRIVERS_LICENSE`) require the `side` field. Upload the front and back as separate requests. For the digital Brazilian driver's license (CNH digital), the `side` field is not needed. ```bash Upload identity document for associated party (front) theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/documents \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/path/to/document-front.jpg" \ -F "type=ID_CARD" \ -F "side=FRONT_SIDE" \ -F "country=BRA" \ -F "associatedPartyId=a1b2c3d4-e5f6-7890-abcd-ef1234567890" ``` ```bash Upload identity document for associated party (back) theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/documents \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/path/to/document-back.jpg" \ -F "type=ID_CARD" \ -F "side=BACK_SIDE" \ -F "country=BRA" \ -F "associatedPartyId=a1b2c3d4-e5f6-7890-abcd-ef1234567890" ``` ```bash Upload proof of address for associated party theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/documents \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/path/to/proof-of-address.pdf" \ -F "type=PROOF_OF_ADDRESS" \ -F "country=BRA" \ -F "associatedPartyId=a1b2c3d4-e5f6-7890-abcd-ef1234567890" ``` Business associated parties need company formation documents: ```bash Upload incorporation articles for business shareholder theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/documents \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/path/to/incorporation-articles.pdf" \ -F "type=INCORPORATION_ARTICLES" \ -F "country=USA" \ -F "associatedPartyId=b2c3d4e5-f6a7-8901-bcde-f12345678901" ``` ```bash Upload directors registry for business shareholder theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/documents \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/path/to/directors-registry.pdf" \ -F "type=DIRECTORS_REGISTRY" \ -F "country=USA" \ -F "associatedPartyId=b2c3d4e5-f6a7-8901-bcde-f12345678901" ``` **6. Start verification** Unlike individual customers, business verification must be started explicitly. Ensure all documents and associated parties are uploaded before calling this endpoint. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/verifications \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479" } ``` > [!WARNING] > > Before starting verification, ensure you have: > > * At least one associated party with each role type: UBO, SHAREHOLDER, and REPRESENTATIVE > * All required company documents uploaded > * All associated party documents uploaded > > Missing documents or role types will delay or prevent the verification review. **7. Monitor verification status** Check the verification status using the verification ID returned in the previous step: ```bash Request theme={null} curl https://api-sandbox.lumx.io/customers/{id}/verifications/{verificationId} \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "customerId": "c85cb5ef-0574-4450-806d-195944f1e309", "status": "UNDER_VERIFICATION", "level": "STANDARD" } ``` | Status | Description | | :- | :- | | `NOT_STARTED` | Verification created but review not yet started | | `UNDER_VERIFICATION` | Documents submitted and being reviewed | | `APPROVED` | Verification complete. Customer can transact | | `RFI` | Additional documentation required | | `FINAL_REJECTION` | Customer permanently rejected | You can also receive status updates via [webhooks](/developer/webhooks) instead of polling. > [!TIP] > > In sandbox, you can simulate different verification statuses using magic numbers in the customer's `taxId`. See [Sandbox magic numbers](/compliance/identity-verification#sandbox-magic-numbers) for the full list. ## Multi-level corporate structures If your customer has a multi-level ownership structure (e.g., a holding company that owns another company that owns the customer), you can use the `parentId` field to nest shareholders across multiple levels. For example, if "Investment Fund ABC" (a business shareholder) has its own individual shareholders, you can link them by passing the fund's associated party ID as `parentId`: ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/associated-parties \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "INDIVIDUAL", "roles": ["SHAREHOLDER"], "parentId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "name": "John Smith", "birthDate": "1975-04-20", "email": "john.smith@fundabc.com", "taxId": "123-45-6789", "address": { "country": "USA", "line1": "456 Broadway", "city": "New York", "state": "NY", "postalCode": "10013" }, "ownershipPercentage": 50, "monthlyIncomeInUSD": "10000" }' ``` This creates the following structure: ``` Your Customer (Business) └── Investment Fund ABC (Business Shareholder): 25% └── John Smith (Individual Shareholder): 50% ``` > [!NOTE] > > Associated parties nested under a shareholder (via `parentId`) can only have > the `SHAREHOLDER` role. Other roles such as `UBO` and `REPRESENTATIVE` are > only valid for top-level associated parties. You can repeat this pattern for as many levels as needed. Each associated party at every level requires its own documents and verification. ## Handling rejections When a business receives `RFI`, the verification response includes details about what needs to be corrected: ```json theme={null} { "customerId": "c85cb5ef-0574-4450-806d-195944f1e309", "status": "RFI", "level": "STANDARD", "statusReason": { "rejectLabels": ["screenshot", "unsatisfactory_photos"], "rejectSubLabels": ["proofOfAddress_listOfDocs", "badPhoto"], "documents": [ { "type": "PASSPORT", "status": "RFI", "comment": "Screenshots aren't accepted. Please upload a live photo of the document.", "rejectLabels": ["screenshot", "unsatisfactory_photos"] } ] } } ``` Re-upload the corrected documents and start a new verification. > [!WARNING] > > A `FINAL_REJECTION` is permanent. The customer cannot resubmit documents or be > re-verified. ## Review timeline Standard KYB verification is typically reviewed within 2 business days. See [Identity Verification](/compliance/identity-verification#verification-slas) for full details. ## Retrying verification for specific associated parties When only some associated parties are rejected, you can retry verification for those parties in isolation. Pass `associatedPartyIds` in the request body when starting a verification: ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/verifications \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "associatedPartyIds": [ "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "b2c3d4e5-f6a7-8901-bcde-f12345678901" ] }' ``` Only the listed associated parties are sent for verification again. The customer-level verification is not restarted, and any associated party not included in the list keeps its current verification status. > [!NOTE] > > Resubmit any corrected documents/information for the affected associated parties before starting the retry. This option applies only to BUSINESS customers. ## Related resources > [card] [Webhooks](/developer/webhooks) > > Receive real-time status updates. > [card] [Transaction Limits](/compliance/transaction-limits) > > Understand post-verification limits. > [card] [Identity Verification](/compliance/identity-verification) > > Full compliance requirements. # Create a Customer Source: https://docs.lumx.io/guides/create-a-customer Register individual and business customers on the Lumx platform A customer is the legal entity behind every transaction. Each one gets a dedicated wallet and has to complete identity verification before it can move money. ## Prerequisites * An API key from the [Dashboard](https://dashboard.lumx.io) * Your environment base URL (see [Environments](/get-started/environments)) ## Create a customer Send a `POST` request to `/customers`. The required fields depend on the customer type. > [tab] Individual > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/customers \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -d '{ > "type": "INDIVIDUAL", > "name": "William Default", > "taxId": "123.456.789-00", > "birthDate": "1990-01-01", > "country": "BRA", > "email": "william.default@example.com" > }' > ``` > > ```json Response theme={null} > { > "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "type": "INDIVIDUAL", > "name": "William Default", > "taxId": "123.456.789-00", > "birthDate": "1990-01-01", > "country": "BRA", > "email": "william.default@example.com", > "verification": { > "status": "NOT_STARTED", > "level": "STANDARD" > }, > "requirements": [ > { "name": "ID_CARD", "status": "NOT_SENT" }, > { "name": "PASSPORT", "status": "NOT_SENT" }, > { "name": "DRIVERS_LICENSE", "status": "NOT_SENT" }, > { "name": "BANK_STATEMENT", "status": "NOT_SENT" }, > { "name": "INCOME_TAX_RETURN", "status": "NOT_SENT" }, > { "name": "DELIVERY_RECEIPT", "status": "NOT_SENT" }, > { "name": "SELFIE", "status": "NOT_SENT" }, > { "name": "ADDITIONAL_INFORMATION", "status": "NOT_SENT" }, > { "name": "TERMS_OF_SERVICE", "status": "NOT_SENT" } > ], > "createdAt": "2021-01-01T00:00:00Z", > "updatedAt": "2021-01-01T00:00:00Z" > } > ``` > > ### Required fields > > | Field | Description | > | :- | :- | > | `type` | Must be `INDIVIDUAL` | > | `name` | Customer's full name | > | `taxId` | Valid tax ID for the customer's country (e.g., CPF for Brazil) | > | `birthDate` | Date of birth in `YYYY-MM-DD` format | > | `country` | Country code in ISO 3166-1 alpha-3 format (e.g., `BRA`) | > | `email` | Customer's email address | > [tab] Business > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/customers \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -d '{ > "type": "BUSINESS", > "legalName": "Lumx S.A", > "taxId": "42.887.120/0001-00", > "incorporationDate": "2020-01-01", > "country": "BRA", > "email": "hello@lumx.io" > }' > ``` > > ```json Response theme={null} > { > "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "type": "BUSINESS", > "legalName": "Lumx S.A", > "taxId": "42.887.120/0001-00", > "incorporationDate": "2020-01-01", > "country": "BRA", > "email": "hello@lumx.io", > "verification": { > "status": "NOT_STARTED", > "level": "STANDARD" > }, > "requirements": [ > { "name": "INCORPORATION_ARTICLES", "status": "NOT_SENT" }, > { "name": "POWER_OF_ATTORNEY", "status": "NOT_SENT" }, > { "name": "DIRECTORS_REGISTRY", "status": "NOT_SENT" }, > { "name": "SHAREHOLDER_REGISTRY", "status": "NOT_SENT" }, > { "name": "REGULATED_ACTIVITY_DOCUMENT", "status": "NOT_SENT" }, > { "name": "SIGNED_BALANCE_SHEET", "status": "NOT_SENT" }, > { "name": "SIGNED_CORPORATE_STRUCTURE_CHART", "status": "NOT_SENT" }, > { "name": "SIGNED_INCOME_STATEMENT", "status": "NOT_SENT" }, > { "name": "ADDITIONAL_INFORMATION", "status": "NOT_SENT" }, > { "name": "TERMS_OF_SERVICE", "status": "NOT_SENT" } > ], > "createdAt": "2021-01-01T00:00:00Z", > "updatedAt": "2021-01-01T00:00:00Z" > } > ``` > > ### Required fields > > | Field | Description | > | :- | :- | > | `type` | Must be `BUSINESS` | > | `legalName` | Company's registered legal name | > | `taxId` | Valid tax ID for the company's country (e.g., CNPJ for Brazil) | > | `incorporationDate` | Incorporation date in `YYYY-MM-DD` format | > | `country` | Country code in ISO 3166-1 alpha-3 format (e.g., `BRA`) | > | `email` | Company's email address | ## Provision fiat accounts To let the customer receive payments on local rails (PIX, SPEI, ACH, SEPA, FPS, etc.), include an `accounts` array on creation with the currencies you want to provision. Each currency creates a virtual account in `AWAITING_ONBOARDING`; provisioning is requested once the customer is approved. See [Accounts](/concepts/accounts) for the full lifecycle. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "INDIVIDUAL", "name": "William Default", "taxId": "123.456.789-00", "birthDate": "1990-01-01", "country": "BRA", "email": "william.default@example.com", "accounts": ["BRL", "USD", "EUR", "MXN", "GBP"] }' ``` The `accounts` array is optional. If you omit it, the customer is provisioned with a BRL account by default and can still send and receive stablecoin on-chain through their wallet. For the full list of supported currencies and rails, see [Coverage](/get-started/coverage). ## Testing verification statuses in sandbox In sandbox, the last digit of `taxId` is a sentinel that drives the simulated verification outcome. Webhooks fire almost instantly with the matching status, so you can exercise your full flow end-to-end. | `taxId` ending | Simulated status | | :- | :- | | `1` | `NOT_STARTED` — verification never starts automatically | | `2` | `RFI` — requests additional documents | | `3` | `FINAL_REJECTION` — permanent rejection | | Any other digit | `APPROVED` (default) | ## Understanding the response The creation response includes fields you'll use throughout the verification flow: * `verification` carries the current `status` (`NOT_STARTED` after creation) and the verification `level` (`STANDARD`). Its `link` to the hosted verification flow is omitted until the customer accepts the [terms of service](/compliance/terms-of-service) and the `TERMS_OF_SERVICE` requirement is `APPROVED`. Read the customer again after acceptance to get it. * `requirements` lists the documents and information needed for verification, each with a `status` indicating whether it has been submitted. * `transactionLimits` isn't returned by default. Call `GET /customers/{id}?includeTransactionLimits=true` to retrieve the single, daily, and monthly limits of each transaction type (`onramp`, `offramp`, `transfer`). > [!NOTE] > > The `wallets` array is only returned once verification reaches `APPROVED`. Subscribe to the `customer.approved` [webhook](/developer/webhooks) or call [Read a customer](/api-reference/customers/read-a-customer) to retrieve wallets after approval. ## Related resources > [card] [Customers](/concepts/customers) > > Understand customer entities and verification workflows. > [card] [Identity Verification](/compliance/identity-verification) > > Required data for KYC/KYB verification processes. > [card] [Transaction Limits](/compliance/transaction-limits) > > Understand post-verification transaction limits. ## What's next After creating a customer, complete the verification process: > [card] [Individual Verification (KYC)](/guides/individual-verification) > > Complete identity verification for individual customers. > [card] [Business Verification (KYB)](/guides/business-verification) > > Complete identity verification for business customers. # Individual Verification (KYC) Source: https://docs.lumx.io/guides/individual-verification Complete identity verification for individual customers After creating an individual customer, you must complete identity verification (KYC) before they can transact. This guide walks through each step of the process. > [!NOTE] > > If you don't want to build the verification flow via API, you can share the `verification.link` returned in the customer response directly with your customer. The link opens a guided flow where they can submit all required information and documents without any additional API integration. It's only returned once the customer has accepted the terms of service (step 1). ## Prerequisites * An individual customer already created (see [Create a Customer](/guides/create-a-customer)) * An API key from the [Dashboard](https://dashboard.lumx.io) * Your environment base URL (see [Environments](/get-started/environments)) ## Verification flow **1. Accept terms of service** Before starting verification, the customer must accept the terms of service. Send a `POST` request to `/customers/{id}/tos` to get the acceptance URL. You can optionally include a `redirectUrl` to redirect the customer after they accept. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/tos \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "redirectUrl": "https://yourapp.com/onboarding/next-step" }' ``` ```json Response theme={null} { "url": "https://dashboard.lumx.io/tos/sandbox/eyJhbGciOiJSUzI1...", "expiresAt": "2026-04-09T16:00:00Z" } ``` Share the returned URL with your customer. The link expires after 24 hours. Generate a new one if needed. The `requirements` array will show `TERMS_OF_SERVICE` as `NOT_SENT` until they accept. You can check the status by [reading the customer](/api-reference/customers/read-a-customer). > [!NOTE] > > The request body is optional. If you don't need to redirect the customer after acceptance, you can send the request without a body. **2. Send additional information** Send a `PATCH` request to `/customers/{id}/additional-information` with the customer's personal and transactional information. This has to be done before uploading documents. ```bash Request theme={null} curl -X PATCH https://api-sandbox.lumx.io/customers/{id}/additional-information \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "INDIVIDUAL", "phone": "+5511999999999", "firstName": "William", "lastName": "Default", "address": { "country": "BRA", "line1": "Rua das Flores, 123", "city": "São Paulo", "state": "SP", "postalCode": "01234-567" }, "professionalSituation": "EMPLOYEE", "professionalOccupation": "Software Engineer", "monthlyIncomeInUSD": "8000", "monthlyVolumeInUSD": "5000", "transactionVolumeInUSD": "1000", "sourceOfFunds": "SALARY_EMPLOYMENT_INCOME", "transactionCounterparties": ["SELF", "FAMILY"] }' ``` All fields are required unless noted otherwise. Here's a summary of the key fields: | Field | Description | | :- | :- | | `phone` | Phone number in international format | | `firstName`, `lastName` | Customer's name split into parts | | `middleName` | Customer's middle name (optional) | | `address` | Full address with `country`, `line1`, `city`, `state`, `postalCode` | | `professionalSituation` | Employment situation (e.g., `EMPLOYEE`, `BUSINESS_OWNER`) | | `professionalOccupation` | Free-text description of occupation | | `monthlyIncomeInUSD` | Monthly income in USD (e.g., `"8000"` or `"8000.50"`) | | `monthlyVolumeInUSD` | Expected monthly transaction volume in USD | | `transactionVolumeInUSD` | Expected volume per transaction in USD | | `sourceOfFunds` | Origin of funds (e.g., `SALARY_EMPLOYMENT_INCOME`) | | `transactionCounterparties` | Who the customer sends money to or receives it from (e.g., `SELF`, `FAMILY`) | `monthlyIncomeInUSD` takes a non-negative amount as a string, with up to 2 decimal places. `professionalSituation`, `sourceOfFunds`, and `transactionCounterparties` only accept the values listed in [Onboarding fields](/additional-information/onboarding-fields). Any other value returns `400`. > [!NOTE] > > Customers created before October 28, 2026 keep the previous questionnaire, with `jurisdictions` and `involvedActivities` and without `monthlyIncomeInUSD`. Keep sending that body for them: a body that doesn't match the customer's questionnaire returns `400` with `VALIDATION_ERROR`. See [Previous questionnaire](/additional-information/onboarding-fields#previous-questionnaire) and the [changelog](/changelog#october-7-2026). For the full schema, see the [API Reference](/api-reference/customers/send-additional-information). **3. Upload documents** Upload the required documents using `POST /customers/{id}/documents`. Each document is sent as a `multipart/form-data` request. Required documents: * One identity document: `ID_CARD`, `PASSPORT`, or `DRIVERS_LICENSE` * `BANK_STATEMENT` (must be from the last 6 months) * `PROOF_OF_ADDRESS` (must be from the last 90 days) Optional documents: * `INCOME_TAX_RETURN` (must be from the last calendar year) * `DELIVERY_RECEIPT` (must be from the last calendar year) * `OTHER` (any additional document) > [!NOTE] > > Identity documents (`ID_CARD` and `DRIVERS_LICENSE`) require the `side` field. Upload the front and back as separate requests. For the digital Brazilian driver's license (CNH digital), the `side` field is not needed. ```bash Upload identity document (front) theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/documents \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/path/to/document-front.jpg" \ -F "type=ID_CARD" \ -F "side=FRONT_SIDE" \ -F "country=BRA" ``` ```bash Upload identity document (back) theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/documents \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/path/to/document-back.jpg" \ -F "type=ID_CARD" \ -F "side=BACK_SIDE" \ -F "country=BRA" ``` ```bash Upload bank statement theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/documents \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/path/to/bank-statement.pdf" \ -F "type=BANK_STATEMENT" \ -F "country=BRA" ``` Files must be JPG, PNG, or PDF with a maximum size of 50MB. **4. Complete liveness check** The customer must complete a liveness check by accessing the `verification.link` returned in the customer response. This link opens a guided selfie flow. `verification.link` is omitted from the customer until the `TERMS_OF_SERVICE` requirement is `APPROVED`. Once the customer has accepted the terms, read the customer to get it: ```bash theme={null} curl https://api-sandbox.lumx.io/customers/{id} \ -H "Authorization: Bearer YOUR_API_KEY" ``` > [!NOTE] > > For individual customers, verification starts automatically after the liveness check is completed. You do not need to call a separate endpoint to start verification. **5. Monitor verification status** Check the verification status by reading the customer's verification: ```bash Request theme={null} curl https://api-sandbox.lumx.io/customers/{id}/verifications/{verificationId} \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "status": "UNDER_VERIFICATION", "level": "STANDARD" } ``` | Status | Description | | :- | :- | | `NOT_STARTED` | Customer created but verification not yet initiated | | `UNDER_VERIFICATION` | Documents submitted and being reviewed | | `APPROVED` | Verification complete. Customer can transact | | `RFI` | Additional documentation required | | `FINAL_REJECTION` | Customer permanently rejected | You can also receive status updates via [webhooks](/developer/webhooks) instead of polling. > [!TIP] > > In sandbox, you can simulate different verification statuses using magic numbers in the customer's `taxId`. See [Sandbox magic numbers](/compliance/identity-verification#sandbox-magic-numbers) for the full list. ## Handling rejections When a customer receives `RFI`, the verification response includes details about what needs to be corrected: ```json theme={null} { "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "status": "RFI", "level": "STANDARD", "statusReason": { "rejectLabels": ["screenshot", "unsatisfactory_photos"], "documents": [ { "type": "PASSPORT", "status": "RFI", "comment": "Screenshots aren't accepted. Please upload a live photo of the document.", "rejectLabels": ["screenshot", "unsatisfactory_photos"] } ] } } ``` Re-upload the corrected documents and the verification will automatically resume. > [!WARNING] > > A `FINAL_REJECTION` is permanent. The customer cannot resubmit documents or be re-verified. ## Review timeline Standard KYC verification is typically reviewed within 1 business day. See [Identity Verification](/compliance/identity-verification#verification-slas) for full details. ## Related resources > [card] [Webhooks](/developer/webhooks) > > Receive real-time status updates. > [card] [Transaction Limits](/compliance/transaction-limits) > > Understand post-verification limits. > [card] [Identity Verification](/compliance/identity-verification) > > Full compliance requirements. # KYC Reuse Source: https://docs.lumx.io/guides/kyc-reuse Reuse an existing Sumsub verification to onboard customers without repeating KYC KYC reuse lets you onboard a customer with identity verification (KYC) they already completed on your own Sumsub account, instead of asking them to submit documents and complete a liveness check again. You pass a Sumsub share token when creating the customer, and Lumx imports the existing applicant data. > [!NOTE] > > KYC reuse is only available for individual customers. Business customers must go through the standard [Business Verification (KYB)](/guides/business-verification) flow. ## Prerequisites * A data-sharing agreement between you and Lumx on Sumsub. Both parties must agree to share applicant data before tokens can be exchanged. Contact [compliance@lumx.io](mailto:compliance@lumx.io) to set this up. * Your own Sumsub account with the customer already verified as an applicant * An API key from the [Dashboard](https://dashboard.lumx.io) * Your environment base URL (see [Environments](/get-started/environments)) ## Example flow The exact steps depend on what your Sumsub applicant already contains: data imported through the share token doesn't need to be submitted again, and Lumx may still require anything that's missing. The flow below shows a common scenario where the identity documents and liveness check are reused, and the remaining requirements are completed on Lumx. In practice, you'll still need to send the additional information (Step 4). Reuse typically saves the document upload and liveness check, not the questionnaire. The minimum data Lumx needs is the same as [Create a Customer](/guides/create-a-customer). **1. Generate a share token on Sumsub** Generate a share token for the applicant using the [Sumsub share token endpoint](https://docs.sumsub.com/reference/generate-share-token). The token authorizes Lumx to import the applicant's verification data from your Sumsub account. Share tokens are single-use and expire after the `ttlInSecs` you set when generating them. Sumsub invalidates a token once it's used, so generate a fresh one for each attempt. **2. Create the customer with the share token** Send a `POST` request to `/customers` with the customer's information and the `shareToken` field. The customer data must match the applicant data on Sumsub. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "shareToken": "_act-sbx-jwt-token-from-sumsub", "type": "INDIVIDUAL", "name": "William Default", "taxId": "123.456.789-00", "birthDate": "1990-01-01", "country": "BRA", "email": "william.default@example.com", "accounts": ["BRL"] }' ``` The `shareToken` field is optional. Omitting it creates a customer that goes through the standard [Individual Verification (KYC)](/guides/individual-verification) flow. For the full list of fields, see [Create a Customer](/guides/create-a-customer). **3. Accept terms of service** The customer must still accept the Lumx terms of service. Send a `POST` request to `/customers/{id}/tos` to get the acceptance URL and share it with your customer. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/tos \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "redirectUrl": "https://yourapp.com/onboarding/next-step" }' ``` ```json Response theme={null} { "url": "https://dashboard.lumx.io/tos/sandbox/eyJhbGciOiJSUzI1...", "expiresAt": "2026-04-09T16:00:00Z" } ``` **4. Send additional information** Send a `PATCH` request to `/customers/{id}/additional-information` with the customer's personal and transactional information. ```bash Request theme={null} curl -X PATCH https://api-sandbox.lumx.io/customers/{id}/additional-information \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "INDIVIDUAL", "phone": "+5511999999999", "firstName": "William", "lastName": "Default", "address": { "country": "BRA", "line1": "Rua das Flores, 123", "city": "São Paulo", "state": "SP", "postalCode": "01234-567" }, "professionalSituation": "EMPLOYEE", "professionalOccupation": "Software Engineer", "monthlyIncomeInUSD": "8000", "monthlyVolumeInUSD": "5000", "transactionVolumeInUSD": "1000", "sourceOfFunds": "SALARY_EMPLOYMENT_INCOME", "transactionCounterparties": ["SELF", "FAMILY"] }' ``` For the accepted values of each field, see [Onboarding fields](/additional-information/onboarding-fields) and the [API Reference](/api-reference/customers/send-additional-information). Customers created before October 28, 2026 keep the previous questionnaire; see [Individual Verification (KYC)](/guides/individual-verification#verification-flow). **5. Start the verification** Send a `POST` request to `/customers/{id}/verifications` to start the verification. Because the documents and liveness check are imported from Sumsub, there is no liveness step to trigger it automatically, so you start it explicitly. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/verifications \ -H "Authorization: Bearer YOUR_API_KEY" ``` Monitor the verification status by [reading the customer](/api-reference/customers/read-a-customer) or subscribing to [webhooks](/developer/webhooks). Once the status reaches `APPROVED`, the customer can move money. ## Errors Sending a `shareToken` for a business customer is rejected. Reuse is only available for individuals. If customer creation fails with a share token, the most common causes are a token that's expired, already used, or generated for a different applicant. Share tokens are single-use, so generate a fresh one and create the customer again. If reuse keeps failing, fall back to the standard [Individual Verification (KYC)](/guides/individual-verification) flow. ## Related resources > [card] [Create a Customer](/guides/create-a-customer) > > Register individual and business customers. > [card] [Individual Verification (KYC)](/guides/individual-verification) > > The standard verification flow without reuse. > [card] [Identity Verification](/compliance/identity-verification) > > Full compliance requirements. > [card] [Webhooks](/developer/webhooks) > > Receive real-time status updates. # Limit Increase Requests Source: https://docs.lumx.io/guides/limit-increase-requests Request higher transaction limits for a customer through the API A limit increase request raises a customer's per-transaction, daily, and monthly limits for one transaction type beyond what their verification level grants. You upload a document that justifies the new limits, submit the requested amounts, and Lumx compliance reviews the request. > [!NOTE] > > Limit increase requests can also be submitted from the [Lumx Dashboard](https://dashboard.lumx.io). For the default limits per verification level and the Dashboard flow, see [Transaction Limits](/compliance/transaction-limits). ## Prerequisites * A customer with approved identity verification (KYC/B) * An API key with the `WRITE_CUSTOMERS` scope. Creating, tracking, and reading a request all work with that single scope. * Your environment base URL (see [Environments](/get-started/environments)) **1. Upload the supporting document** Send a `POST` request to `/customers/{id}/documents` using the document type that matches the file: | **Type code** | **Document** | **Customer profile** | | :- | :- | :- | | `LIMIT_REQUEST_BANK_STATEMENT` | Bank statement | Individual and business | | `LIMIT_REQUEST_TAX_RETURN` | Personal or corporate tax return | Individual and business | | `LIMIT_REQUEST_FINANCIAL_STATEMENTS` | Financial statements | Business | ```bash Upload supporting document theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/documents \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/path/to/bank-statement.pdf" \ -F "type=LIMIT_REQUEST_BANK_STATEMENT" \ -F "country=BRA" ``` ```json Response theme={null} { "documentId": "9d1e2f3a-4b5c-6d7e-8f90-1a2b3c4d5e6f" } ``` Keep the `documentId` from the response. Files must be JPG, PNG, or PDF with a maximum size of 10MB. **2. Create the limit request** Send a `POST` request to `/customers/{id}/limit-requests`, referencing that `documentId` as `supportingDocumentId`. `requested.single` cannot exceed `requested.daily`, which cannot exceed `requested.monthly`. `type` is required and picks the [transaction type](/compliance/transaction-limits#limits-per-transaction-type) whose limits you want to raise: `ONRAMP`, `OFFRAMP`, or `TRANSFER`. To raise more than one type, send one request per type. ```bash Request a limit increase theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{id}/limit-requests \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "OFFRAMP", "requested": { "single": "15000.00", "daily": "60000.00", "monthly": "120000.00" }, "supportingDocumentId": "9d1e2f3a-4b5c-6d7e-8f90-1a2b3c4d5e6f" }' ``` ```json Response theme={null} { "id": "b2e1a2d4-1234-4a3b-9c4d-5e6f7a8b9c0d", "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "type": "OFFRAMP", "requested": { "single": "15000.00", "daily": "60000.00", "monthly": "120000.00" }, "approved": { "single": null, "daily": null, "monthly": null }, "supportingDocumentType": "LIMIT_REQUEST_BANK_STATEMENT", "supportingDocumentId": "9d1e2f3a-4b5c-6d7e-8f90-1a2b3c4d5e6f", "status": "IN_REVIEW", "reviewComment": null, "createdAt": "2024-01-15T10:00:00Z", "updatedAt": "2024-01-15T10:00:00Z" } ``` A customer can have one pending request per type. Creating a second request for a type that already has a pending one returns `409` with `LIMIT_REQUEST_ALREADY_PENDING`. Requests for other types aren't affected. **3. Track the request** Fetch a single request with `GET /customers/{id}/limit-requests/{id}`, or list every request the customer has made with `GET /customers/{id}/limit-requests`. While compliance reviews the request, `status` stays `IN_REVIEW` and the `approved` limits stay `null`. After the review: * `status` becomes `APPROVED`, `PARTIALLY_APPROVED`, or `REJECTED` * `approved` carries the granted limits * `reviewComment` carries the reviewer's note on a rejection or partial approval * `updatedAt` is the moment the request was reviewed If you'd rather not poll, subscribe to the `customer.limit_request.*` [webhook events](/developer/webhooks#available-events) instead. ```bash Check status theme={null} curl https://api-sandbox.lumx.io/customers/{id}/limit-requests/{id} \ -H "Authorization: Bearer YOUR_API_KEY" ``` Every request in the response carries its `type`. Requests created before October 28, 2026 have `type: null`. See the full request and response schemas in the [API Reference](/api-reference/customers/read-a-limit-request). ## Review outcomes `APPROVED` grants the new limits in full, `PARTIALLY_APPROVED` grants limits below what was requested based on what the supporting document justifies, and `REJECTED` keeps the customer on their current limits. The approved limits become active immediately and apply only to the request's `type`. The limits of the other transaction types stay the same. For the full breakdown and review timelines, see [Review outcomes](/compliance/transaction-limits#review-outcomes). ## Sandbox magic numbers In sandbox, the cents of `requested.single` are a sentinel that drives the simulated review outcome, so no manual back-office review is needed. The create response already reflects the final status, and the matching [webhook events](/developer/webhooks#available-events) fire right away. | `requested.single` cents | Simulated status | | :- | :- | | `.02` | `PARTIALLY_APPROVED` (50% of each requested limit) | | `.03` | `REJECTED` | | Any other value | `APPROVED` (default, in full) | For example, requesting `"15000.03"` as `requested.single` rejects the request instantly. In production this field has no special effect, and every request waits for manual review. ## Related resources > [card] [Transaction Limits](/compliance/transaction-limits) > > Default limits per verification level and the Dashboard flow. > [card] [Webhooks](/developer/webhooks) > > Receive real-time status updates. > [card] [Document Types](/additional-information/document-types) > > Every accepted document type code. > [card] [API Reference](/api-reference/customers/read-a-limit-request) > > Full request and response schemas. # Global accounts Source: https://docs.lumx.io/guides/use-cases/global-accounts Provision local-currency accounts so each customer can be paid worldwide Enable your customers to receive payments worldwide through local payment rails. You choose which currencies each customer should have an account in. To receive a payment, you create an on-ramp transaction with the exact amount of the expected deposit. Lumx returns rail-specific payment details; when the deposit matches, the funds are converted to stablecoin and credited to the customer's wallet. See [Coverage](/get-started/coverage) for the supported currencies and rails. ## Step 1: Generate your API key Head over to [dashboard.lumx.io](https://dashboard.lumx.io). Once logged in, generate a new API key and store it securely. See [Authentication](/get-started/authentication) for details. ## Step 2: Create a customer with the accounts you need Send a `POST /customers` request including an `accounts` array with the currencies you want to provision. Each currency triggers a virtual account that enters verification alongside the customer. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "type": "INDIVIDUAL", "name": "William Default", "taxId": "123.456.789-00", "birthDate": "1990-01-01", "country": "BRA", "email": "william.default@example.com", "accounts": ["BRL", "USD", "EUR", "MXN", "GBP"] }' ``` ```json Response theme={null} { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "type": "INDIVIDUAL", "verification": { "status": "NOT_STARTED", "level": "STANDARD" } } ``` > [!NOTE] > > The `wallets` array is only returned once the customer's verification status is `APPROVED`. Subscribe to the `customer.approved` [webhook](/developer/webhooks) or call [Read a customer](/api-reference/customers/read-a-customer) to retrieve wallets after approval. See [Create a customer](/guides/create-a-customer) for the full payload reference, including business customers. The list of supported currencies and rails is in [Coverage](/get-started/coverage). ## Step 3: Collect ToS acceptance and KYC Before the customer can transact, they must accept Lumx's terms of service and complete identity verification. Generate a ToS acceptance link and share it with the customer: ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers/{customerId}/tos \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "redirectUrl": "https://yourapp.com/onboarding/next-step" }' ``` ```json Response theme={null} { "url": "https://dashboard.lumx.io/tos/eyJhbGciOiJSUzI1...", "expiresAt": "2026-04-09T16:00:00Z" } ``` Once the customer accepts, the `TERMS_OF_SERVICE` requirement moves to `APPROVED` and the customer's `verification.link` becomes available. Read the customer and share that link so they can complete KYC. See [Individual verification](/guides/individual-verification) or [Business verification](/guides/business-verification) for the full flow. ## Step 4: Wait for accounts to activate Each requested account stays in `AWAITING_ONBOARDING` until the customer is approved, then provisioning is requested and the account goes through its own verification. | **Status** | **Description** | | :- | :- | | `AWAITING_ONBOARDING` | Account exists but the customer has not completed verification yet | | `REQUESTED` | Customer approved and provisioning requested | | `PROVISIONING` | Account is being provisioned and verified | | `RFI` | Additional information is required to continue verification | | `ACTIVE` | Account is verified and can receive funds | | `REJECTED` | Account application was rejected | | `INACTIVE` | Account was deactivated | Subscribe to the `account.active` webhook to know the moment an account is ready to receive funds. See [Webhooks](/developer/webhooks) for the full event list. ```json account.active event theme={null} { "eventType": "account.active", "data": { "id": "af04e979-360a-428a-84e6-cbd8ffc4942b", "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "currency": "USD", "status": "ACTIVE" } } ``` ## Step 5: Create an on-ramp for each expected deposit To receive a payment in any of the customer's provisioned currencies, create an on-ramp transaction with the exact amount that will be deposited. The response includes rail-specific payment details: a PIX `brCode`, a SPEI CLABE, wire instructions, an IBAN, or a UK sort code and account number. Share those details with the depositor; when the matching amount lands, Lumx converts the fiat to stablecoin and credits the customer's wallet. > [!NOTE] > > Each deposit must be matched to an on-ramp transaction created in advance with the exact amount. If the deposit lands after the on-ramp expires, Lumx refunds the sender on the same rail. See [Late deposits](/concepts/transactions#late-deposits). > [tab] USD (ACH) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/transactions/on-ramp \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "rail": "ACH", > "sourceCurrency": "USD", > "sourceAmount": "10000.00", > "targetCurrency": "USDC", > "purpose": "PERSONAL_ACCOUNT" > }' > ``` > [tab] BRL (PIX) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/transactions/on-ramp \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "rail": "PIX", > "sourceCurrency": "BRL", > "sourceAmount": "50000.00", > "targetCurrency": "USDC", > "purpose": "PERSONAL_ACCOUNT" > }' > ``` > [tab] MXN (SPEI) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/transactions/on-ramp \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "rail": "SPEI", > "sourceCurrency": "MXN", > "sourceAmount": "200000.00", > "targetCurrency": "USDC", > "purpose": "PERSONAL_ACCOUNT" > }' > ``` > [tab] EUR (SEPA) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/transactions/on-ramp \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "rail": "SEPA", > "sourceCurrency": "EUR", > "sourceAmount": "10000.00", > "targetCurrency": "USDC", > "purpose": "PERSONAL_ACCOUNT" > }' > ``` > [tab] GBP (FPS) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/transactions/on-ramp \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "rail": "FPS", > "sourceCurrency": "GBP", > "sourceAmount": "10000.00", > "targetCurrency": "USDC", > "purpose": "PERSONAL_ACCOUNT" > }' > ``` > [tab] USD (SWIFT) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/transactions/on-ramp \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "rail": "SWIFT", > "sourceCurrency": "USD", > "sourceAmount": "10000.00", > "targetCurrency": "USDC", > "purpose": "PERSONAL_ACCOUNT" > }' > ``` The response includes `state.payment` with the rail-specific details to share with the depositor (PIX `brCode`, SPEI `clabe`, ACH/wire `accountNumber` + `routingNumber`, SEPA/SWIFT `iban` + `bic`, FPS `sortCode` + `accountNumber`). Subscribe to `onramp.success` to know the moment the deposit lands and the wallet is credited. See [Transactions](/concepts/transactions) for the on-ramp lifecycle. > [!TIP] > > For products with prices fixed in stablecoin, fetch a locked exchange rate first and pass `exchangeRateId` instead of `sourceAmount`. The depositor is shown an exact local-currency amount and the conversion settles at that rate. See [Exchange Rates](/concepts/exchange-rates). ## Related resources > [card] [Accounts](/concepts/accounts) > > Understand virtual fiat accounts and their lifecycle. > [card] [Coverage](/get-started/coverage) > > Supported currencies, rails and countries. > [card] [Webhooks](/developer/webhooks) > > Subscribe to account and customer events. > [card] [Accounts API](/api-reference/accounts/read-all-accounts) > > Full API reference for accounts. # Marketplaces Source: https://docs.lumx.io/guides/use-cases/marketplaces Take a platform fee on every sale and settle sellers anywhere in their local currency Build a marketplace where your platform takes a cut on every transaction and pays sellers anywhere in the world. Each seller is onboarded as a customer. This guide covers two scenarios depending on how buyers pay your platform. In scenario A, your marketplace runs the checkout and buyers pay in their local currency via instant local rails. The marketplace fee is taken on the way in, and the seller's wallet receives stablecoin net of the fee. See [Coverage](/get-started/coverage) for supported rails. In scenario B, sellers earn outside your marketplace and receive USD payouts from external platforms like Amazon or Shopify into a USD virtual account issued by a Lumx banking partner. They then off-ramp to their local currency. Both scenarios share the same setup (steps 1–3) and the same final seller payout (step 6). ## Step 1: Generate your API key Head over to [dashboard.lumx.io](https://dashboard.lumx.io). Once logged in, generate a new API key and store it securely. See [Authentication](/get-started/authentication) for details. ## Step 2: Create your marketplace partner fee Configure once how much of every transaction the marketplace keeps and where the fee should be sent. The `walletAddress` is the marketplace's own wallet. Rates are in basis points (e.g., `"500"` = 5%). ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/partner-fees \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Marketplace platform fee", "walletAddress": "0x76b74209f3542d172ca1575b5b18064594360299", "fees": { "onRamp": { "rate": "500", "flatAmount": "0" }, "offRamp": { "rate": "500", "flatAmount": "0" } } }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174004", "name": "Marketplace platform fee", "walletAddress": "0x76b74209f3542d172ca1575b5b18064594360299", "fees": { "onRamp": { "rate": "500", "flatAmount": "0" }, "offRamp": { "rate": "500", "flatAmount": "0" } }, "createdAt": "2026-05-16T12:00:00Z" } ``` Store the returned `id`. You'll reference it on every transaction. See [Partner Fees](/concepts/partner-fees) for the full schema. ## Step 3: Onboard each seller For each seller that lists on your marketplace, create a customer: individual for sole proprietors, business for incorporated sellers. They must accept Lumx's terms of service (`POST /customers/{id}/tos`) and complete identity verification before they can receive funds. If your sellers will receive USD payouts from external platforms (Scenario B), provision a USD virtual account on the same call by passing `accounts: ["USD"]`. Sellers who only sell through your local checkout (Scenario A) don't need an account; incoming on-ramps credit their wallet directly. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "type": "BUSINESS", "legalName": "Star Trading Ltd.", "taxId": "1234567", "incorporationDate": "2017-09-20", "country": "HKG", "email": "finance@startrading.hk", "accounts": ["USD"] }' ``` See [Create a customer](/guides/create-a-customer) for the full payload reference and [Business verification](/guides/business-verification) / [Individual verification](/guides/individual-verification) for the full onboarding flow. ## Scenario A: local checkout for global sellers Use this flow when your marketplace runs the checkout itself and buyers pay you in their local currency. PIX (BRL) and SPEI (MXN) are the two realistic rails: instant, push-based, and the way buyers already expect to pay online in those countries. ### Step 4A: Create a checkout for each sale When a buyer is ready to pay a seller, create an on-ramp transaction on the seller's behalf, referencing your `partnerFeeId`. The response includes the payment details (a PIX `brCode` or a SPEI CLABE) which you display in your checkout flow. Use `purpose: "TRADE_TRANSACTIONS"` for goods and services sales. > [tab] PIX (BRL) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/transactions/on-ramp \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "rail": "PIX", > "sourceCurrency": "BRL", > "sourceAmount": "250.00", > "targetCurrency": "USDC", > "purpose": "TRADE_TRANSACTIONS", > "partnerFeeId": "123e4567-e89b-12d3-a456-426614174004" > }' > ``` > [tab] SPEI (MXN) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/transactions/on-ramp \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "rail": "SPEI", > "sourceCurrency": "MXN", > "sourceAmount": "5000.00", > "targetCurrency": "USDC", > "purpose": "TRADE_TRANSACTIONS", > "partnerFeeId": "123e4567-e89b-12d3-a456-426614174004" > }' > ``` > [!TIP] > > For products with prices fixed in stablecoin, fetch a locked exchange rate first and pass `exchangeRateId` instead of `sourceAmount`. The buyer is shown an exact local-currency amount and the conversion settles at that rate. See [Exchange Rates](/concepts/exchange-rates). ### Step 5A: Settle the buyer payment When the buyer pays the returned instructions, the transaction follows this lifecycle: `AWAITING_FUNDS` → `TRANSFERRING_FIAT` → `TRADING` → `TRANSFERRING_STABLECOIN` → `SUCCESS` On `SUCCESS`, the seller's wallet receives stablecoin net of your fee, and your marketplace wallet receives the partner fee, both in the same flow, no extra API call needed. Subscribe to `onramp.success` and `onramp.failed` webhooks to mark the order as paid in your marketplace and unlock fulfillment. See [Webhooks](/developer/webhooks). ## Scenario B: USD collection from external platforms Use this flow when sellers earn on platforms outside your marketplace (Amazon, Shopify, Etsy, foreign clients on wire) and your product gives them a USD virtual account (issued by a Lumx banking partner) to receive those payouts. Once the wallet is funded, the seller off-ramps to their local currency. ### Step 4B: Create an on-ramp for each expected payout For every payout the seller is expecting from Amazon, Shopify or another payer, create an on-ramp transaction with the exact amount of that payout. The response returns ACH and wire instructions the seller pastes into the external platform's payout settings. When the deposit matches the amount, Lumx converts USD to stablecoin and credits the seller's wallet. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/transactions/on-ramp \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "rail": "ACH", "sourceCurrency": "USD", "sourceAmount": "8500.00", "targetCurrency": "USDC", "purpose": "TRADE_TRANSACTIONS", "partnerFeeId": "123e4567-e89b-12d3-a456-426614174004" }' ``` > [!NOTE] > > Each payout must be matched to an on-ramp transaction created in advance with the exact amount, suited to sellers who know what they're expecting (Amazon order settlements, invoice payments). Subscribe to `onramp.success` and `onramp.failed` to know when payouts land or fail. For the full on-ramp flow by rail, see [Global accounts](/guides/use-cases/global-accounts). ## Step 6: Settle to the seller's destination This step applies to both scenarios. Before a seller can cash out, register their own destination with `holder.relationship: "SELF"`. Local sellers receive in their home currency; sellers in jurisdictions Lumx doesn't settle in receive USD via SWIFT. > [!NOTE] > > Sellers in jurisdictions outside Lumx's settlement currencies (e.g., Hong Kong, Singapore) receive USD via SWIFT into their local USD-denominated account. See [Coverage](/get-started/coverage) for the full list of supported settlement currencies and rails. > [tab] PIX (BRL) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Café Verde - Payout Account", > "rail": "PIX", > "currency": "BRL", > "identifier": { > "keyType": "CNPJ", > "keyValue": "42.887.120/0001-00" > }, > "holder": { > "type": "BUSINESS", > "relationship": "SELF", > "legalName": "Café Verde Ltda.", > "taxId": "42.887.120/0001-00", > "incorporationDate": "2020-01-01", > "email": "owner@cafeverde.com.br", > "address": { > "line1": "Rua Voluntários da Pátria 89", > "city": "Rio de Janeiro", > "state": "RJ", > "postalCode": "22270-000", > "country": "BRA" > } > } > }' > ``` > [tab] SPEI (MXN) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Tienda Sol - Payout Account", > "rail": "SPEI", > "currency": "MXN", > "identifier": { > "keyType": "CLABE", > "keyValue": "012180001234567897" > }, > "holder": { > "type": "BUSINESS", > "relationship": "SELF", > "legalName": "Tienda Sol S.A. de C.V.", > "taxId": "TSO200115ABC", > "incorporationDate": "2020-01-15", > "email": "owner@tiendasol.mx", > "address": { > "line1": "Calzada de Tlalpan 3465", > "city": "Mexico City", > "state": "CDMX", > "postalCode": "04650", > "country": "MEX" > } > } > }' > ``` > [tab] ACH (USD) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Acme Goods - Payout Account", > "rail": "ACH", > "currency": "USD", > "identifier": { > "type": "CHECKING", > "accountNumber": "123456789012", > "routingNumber": "021000021" > }, > "bank": { > "name": "JPMorgan Chase Bank", > "address": { > "line1": "383 Madison Avenue", > "city": "New York", > "state": "NY", > "postalCode": "10179", > "country": "USA" > } > }, > "holder": { > "type": "BUSINESS", > "relationship": "SELF", > "legalName": "Acme Goods Inc.", > "taxId": "12-3456789", > "incorporationDate": "2018-06-01", > "email": "owner@acmegoods.com", > "address": { > "line1": "100 Wall Street", > "city": "New York", > "state": "NY", > "postalCode": "10005", > "country": "USA" > } > } > }' > ``` > [tab] SEPA (EUR) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Boutique Paris - Payout Account", > "rail": "SEPA", > "currency": "EUR", > "identifier": { > "iban": "FR1420041010050500013M02606", > "bic": "BNPAFRPP" > }, > "bank": { > "name": "BNP Paribas", > "address": { > "line1": "16 Boulevard des Italiens", > "city": "Paris", > "state": "IDF", > "postalCode": "75009", > "country": "FRA" > } > }, > "holder": { > "type": "BUSINESS", > "relationship": "SELF", > "legalName": "Boutique Paris SAS", > "taxId": "98765432100015", > "incorporationDate": "2019-04-10", > "email": "owner@boutiqueparis.fr", > "address": { > "line1": "45 Avenue Montaigne", > "city": "Paris", > "state": "IDF", > "postalCode": "75008", > "country": "FRA" > } > } > }' > ``` > [tab] FPS (GBP) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "London Goods - Payout Account", > "rail": "FPS", > "currency": "GBP", > "identifier": { > "sortCode": "040509", > "accountNumber": "00125977" > }, > "bank": { > "name": "Mock Bank", > "address": { > "line1": "1 Bank Street", > "city": "London", > "state": "LND", > "postalCode": "EC1A 1BB", > "country": "GBR" > } > }, > "holder": { > "type": "BUSINESS", > "relationship": "SELF", > "legalName": "London Goods Ltd", > "taxId": "1234567890", > "incorporationDate": "2018-09-03", > "email": "owner@londongoods.co.uk", > "address": { > "line1": "221 Baker Street", > "city": "London", > "state": "LND", > "postalCode": "NW1 6XE", > "country": "GBR" > } > } > }' > ``` > [tab] SWIFT (international, USD) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Star Trading HK - Payout Account", > "rail": "SWIFT", > "currency": "USD", > "identifier": { > "accountNumber": "399876543210", > "bic": "HSBCHKHHXXX" > }, > "bank": { > "name": "HSBC Hong Kong", > "address": { > "line1": "1 Queen's Road Central", > "city": "Hong Kong", > "state": "Hong Kong", > "postalCode": "999077", > "country": "HKG" > } > }, > "holder": { > "type": "BUSINESS", > "relationship": "SELF", > "legalName": "Star Trading Ltd.", > "taxId": "1234567", > "incorporationDate": "2017-09-20", > "email": "finance@startrading.hk", > "address": { > "line1": "23 Queen's Road Central, Suite 1801", > "city": "Hong Kong", > "state": "Hong Kong", > "postalCode": "999077", > "country": "HKG" > } > } > }' > ``` Once the destination is approved, trigger the off-ramp. Use `purpose: "PERSONAL_ACCOUNT"` since the seller is moving funds to their own account. Include `partnerFeeId` only if your marketplace charges a separate withdrawal fee; in Scenario A the platform fee was already collected on the on-ramp, so applying it again here would double-charge the seller. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/transactions/off-ramp \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284", "sourceCurrency": "USDC", "sourceAmount": "237.50", "purpose": "PERSONAL_ACCOUNT" }' ``` ## Related resources > [card] [Partner Fees](/concepts/partner-fees) > > Configure platform fees on on-ramp and off-ramp transactions. > [card] [Transactions](/concepts/transactions) > > On-ramp lifecycle, purpose codes and rails. > [card] [Exchange Rates](/concepts/exchange-rates) > > Lock FX rates for stablecoin-priced catalogs. > [card] [Destinations](/concepts/destinations) > > Seller withdrawal destinations. # Payroll Source: https://docs.lumx.io/guides/use-cases/payroll Run global payroll by funding employers in stablecoin and paying employees in local fiat Build a payroll product where your customers (employers) pay their employees and contractors anywhere in the world using stablecoin rails. Each employer is onboarded as a business customer. You register their payees as destinations and trigger payouts on their behalf; Lumx converts stablecoin to local fiat and settles into each payee's account. ## Step 1: Generate your API key Head over to [dashboard.lumx.io](https://dashboard.lumx.io). Once logged in, generate a new API key and store it securely. See [Authentication](/get-started/authentication) for details. ## Step 2: Onboard the employer For each employer using your product, create a business customer. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "type": "BUSINESS", "legalName": "Acme Inc.", "taxId": "42.887.120/0001-00", "incorporationDate": "2020-01-01", "country": "BRA", "email": "finance@acme.com", "accounts": ["USD"] }' ``` Before the employer can transact, they must accept Lumx's terms of service (`POST /customers/{id}/tos`) and complete KYB. See [Business verification](/guides/business-verification) for the full onboarding flow. ## Step 3: Fund the employer's wallet Once the employer's USD account is `ACTIVE`, create an on-ramp transaction with the exact amount the employer plans to deposit. The response includes wire instructions for that specific deposit. Share them with the employer's finance team. When the wire lands matching the amount, Lumx converts USD to stablecoin and credits the employer's wallet. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/transactions/on-ramp \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "rail": "FEDWIRE", "sourceCurrency": "USD", "sourceAmount": "50000.00", "targetCurrency": "USDC", "purpose": "PERSONAL_ACCOUNT" }' ``` > [!NOTE] > > Each deposit must be matched to an on-ramp transaction created in advance with the exact amount. If the employer already holds stablecoin on-chain, they can also transfer it directly to the wallet returned at customer creation, no on-ramp needed. Subscribe to the `onramp.success` webhook to know the moment the wallet is funded and ready to run payouts. For the full deposit flow by rail, see [Global accounts](/guides/use-cases/global-accounts). ## Step 4: Register each payee's destination For each employee or contractor the employer wants to pay, register a destination under the employer's customer record. Set `holder.relationship` to `EMPLOYEE` so the destination is correctly categorized for compliance. The `taxId` format varies by country; see [Tax IDs by country](/additional-information/tax-ids-by-country) and [Coverage](/get-started/coverage) for the full list of supported rails and currencies. > [tab] PIX (BRL) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Maria Santos - Engineering", > "rail": "PIX", > "currency": "BRL", > "identifier": { > "keyType": "CPF", > "keyValue": "123.456.789-00" > }, > "holder": { > "type": "INDIVIDUAL", > "relationship": "EMPLOYEE", > "name": "Maria Santos", > "taxId": "123.456.789-00", > "birthDate": "1990-05-15", > "email": "maria.santos@acme.com", > "address": { > "line1": "Rua Augusta 100", > "city": "São Paulo", > "state": "SP", > "postalCode": "01304-000", > "country": "BRA" > } > } > }' > ``` > [tab] SPEI (MXN) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Carlos Hernández - Operations", > "rail": "SPEI", > "currency": "MXN", > "identifier": { > "keyType": "CLABE", > "keyValue": "012180001234567897" > }, > "holder": { > "type": "INDIVIDUAL", > "relationship": "EMPLOYEE", > "name": "Carlos Hernández", > "taxId": "HEGC900515ABC", > "birthDate": "1990-05-15", > "email": "carlos.hernandez@acme.com", > "address": { > "line1": "Calzada de Tlalpan 3465", > "city": "Mexico City", > "state": "CDMX", > "postalCode": "04650", > "country": "MEX" > } > } > }' > ``` > [tab] ACH (USD) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Jane Doe - Marketing", > "rail": "ACH", > "currency": "USD", > "identifier": { > "type": "CHECKING", > "accountNumber": "123456789012", > "routingNumber": "021000021" > }, > "bank": { > "name": "JPMorgan Chase Bank", > "address": { > "line1": "383 Madison Avenue", > "city": "New York", > "state": "NY", > "postalCode": "10179", > "country": "USA" > } > }, > "holder": { > "type": "INDIVIDUAL", > "relationship": "EMPLOYEE", > "name": "Jane Doe", > "taxId": "123-45-6789", > "birthDate": "1985-09-12", > "email": "jane.doe@acme.com", > "address": { > "line1": "350 5th Avenue", > "city": "New York", > "state": "NY", > "postalCode": "10118", > "country": "USA" > } > } > }' > ``` > [tab] FEDWIRE (USD) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Sarah Lee - Executive", > "rail": "FEDWIRE", > "currency": "USD", > "identifier": { > "type": "CHECKING", > "accountNumber": "987654321098", > "routingNumber": "021000021" > }, > "bank": { > "name": "JPMorgan Chase Bank", > "address": { > "line1": "383 Madison Avenue", > "city": "New York", > "state": "NY", > "postalCode": "10179", > "country": "USA" > } > }, > "holder": { > "type": "INDIVIDUAL", > "relationship": "EMPLOYEE", > "name": "Sarah Lee", > "taxId": "234-56-7890", > "birthDate": "1982-04-08", > "email": "sarah.lee@acme.com", > "address": { > "line1": "1 Market Street", > "city": "San Francisco", > "state": "CA", > "postalCode": "94105", > "country": "USA" > } > } > }' > ``` > [tab] SEPA (EUR) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Lucas Martin - Engineering", > "rail": "SEPA", > "currency": "EUR", > "identifier": { > "iban": "FR1420041010050500013M02606", > "bic": "BNPAFRPP" > }, > "bank": { > "name": "BNP Paribas", > "address": { > "line1": "16 Boulevard des Italiens", > "city": "Paris", > "state": "IDF", > "postalCode": "75009", > "country": "FRA" > } > }, > "holder": { > "type": "INDIVIDUAL", > "relationship": "EMPLOYEE", > "name": "Lucas Martin", > "taxId": "1850612345678", > "birthDate": "1991-11-30", > "email": "lucas.martin@acme.com", > "address": { > "line1": "45 Avenue Montaigne", > "city": "Paris", > "state": "IDF", > "postalCode": "75008", > "country": "FRA" > } > } > }' > ``` > [tab] FPS (GBP) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Oliver Smith - Engineering", > "rail": "FPS", > "currency": "GBP", > "identifier": { > "sortCode": "040509", > "accountNumber": "00125977" > }, > "bank": { > "name": "Mock Bank", > "address": { > "line1": "1 Bank Street", > "city": "London", > "state": "LND", > "postalCode": "EC1A 1BB", > "country": "GBR" > } > }, > "holder": { > "type": "INDIVIDUAL", > "relationship": "EMPLOYEE", > "name": "Oliver Smith", > "taxId": "QQ123456C", > "birthDate": "1990-03-22", > "email": "oliver.smith@acme.com", > "address": { > "line1": "10 Downing Lane", > "city": "London", > "state": "LND", > "postalCode": "SW1A 2AA", > "country": "GBR" > } > } > }' > ``` > [tab] SWIFT (international) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "John Doe - Design", > "rail": "SWIFT", > "currency": "USD", > "identifier": { > "iban": "GB29NWBK60161331926819", > "bic": "NWBKGB2L" > }, > "bank": { > "name": "NatWest Bank", > "address": { > "line1": "250 Bishopsgate", > "city": "London", > "postalCode": "EC2M 4AA", > "country": "GBR" > } > }, > "holder": { > "type": "INDIVIDUAL", > "relationship": "EMPLOYEE", > "name": "John Doe", > "taxId": "AB123456C", > "birthDate": "1988-03-22", > "email": "john.doe@acme.com", > "address": { > "line1": "10 Downing Street", > "city": "London", > "postalCode": "SW1A 2AA", > "country": "GBR" > } > } > }' > ``` Destinations go through verification before they can be used. Subscribe to the `destinations.approved` webhook to know when a payee is ready to receive payouts. See [Destinations](/concepts/destinations) for the full reference. ## Step 5: Run payroll For each approved payee, create an off-ramp transaction debiting the employer's wallet. Use `purpose: "PROFESSIONAL_SERVICES"` for contractor and salary payments (the closest available code; there's no dedicated payroll purpose), or `EXPENSES_REIMBURSEMENT` for reimbursements. See [Purpose codes](/additional-information/purpose-codes) for the full list. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/transactions/off-ramp \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284", "sourceCurrency": "USDC", "sourceAmount": "5000.00", "purpose": "PROFESSIONAL_SERVICES" }' ``` ```json Response theme={null} { "id": "9b1c5b8a-3d1f-4a8f-b6e0-2c1b6a7e9f33", "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "type": "OFF_RAMP", "request": { "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284", "sourceCurrency": "USDC", "sourceAmount": "5000.00", "purpose": "PROFESSIONAL_SERVICES" }, "state": { "status": "TRANSFERRING_STABLECOIN" } } ``` Repeat the request per payee using a fresh `Idempotency-Key` each time. There's no batch endpoint, so run the calls in parallel from your job runner if you need to push a full payroll cycle at once. > [!TIP] > > Lock in an exchange rate before the payout to guarantee the amount the payee receives. See [Exchange Rates](/concepts/exchange-rates). ## Step 6: Track payout status Each off-ramp transaction follows this lifecycle: `TRANSFERRING_STABLECOIN` → `TRADING` → `TRANSFERRING_FIAT` → `SUCCESS` Subscribe to the `offramp.success` and `offramp.failed` webhooks to reconcile payouts as they complete and surface status back to the employer in your product. See [Webhooks](/developer/webhooks) and the [Transactions](/concepts/transactions) lifecycle reference. ## Related resources > [card] [Global accounts](/guides/use-cases/global-accounts) > > Fund the employer's wallet via local fiat rails. > [card] [Destinations](/concepts/destinations) > > Holder relationships and verification. > [card] [Transactions](/concepts/transactions) > > Off-ramp lifecycle and purpose codes. > [card] [Off-ramp API](/api-reference/transactions/off-ramp) > > Full API reference for off-ramp transactions. # Remittances Source: https://docs.lumx.io/guides/use-cases/remittances Send money cross-border by funding senders in local currency and paying recipients on local rails Build a P2P remittance product where individuals send money to family and friends abroad. Each sender is onboarded as an individual customer who funds their wallet in local currency. To send, you register the recipient's destination under the sender and trigger a payout. Lumx converts stablecoin to the recipient's local fiat and delivers it on local rails. ## Step 1: Generate your API key Head over to [dashboard.lumx.io](https://dashboard.lumx.io). Once logged in, generate a new API key and store it securely. See [Authentication](/get-started/authentication) for details. ## Step 2: Onboard the sender For each sender using your product, create an individual customer. Provision an account in their home currency so they can fund the wallet via a local rail. Senders must accept Lumx's terms of service (`POST /customers/{id}/tos`) and complete KYC before they can move funds. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "type": "INDIVIDUAL", "name": "Lucia Pereira", "taxId": "123-45-6789", "birthDate": "1985-09-12", "country": "USA", "email": "lucia.pereira@example.com", "accounts": ["USD"] }' ``` See [Individual verification](/guides/individual-verification) for the full onboarding flow. ## Step 3: Fund the sender's wallet Once the sender's account is `ACTIVE`, create an on-ramp transaction for the exact amount the sender plans to deposit. The response includes the rail-specific payment details: ACH/wire for USD, PIX `brCode` for BRL, SPEI CLABE for MXN, IBAN for EUR, sort code and account number for GBP. Show them in your app for the sender to pay; when the deposit matches, Lumx converts the fiat to stablecoin and credits the sender's wallet. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/transactions/on-ramp \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "rail": "ACH", "sourceCurrency": "USD", "sourceAmount": "500.00", "targetCurrency": "USDC", "purpose": "PERSONAL_ACCOUNT" }' ``` > [!NOTE] > > Each deposit must be matched to an on-ramp transaction created in advance with the exact amount. Subscribe to the `onramp.success` webhook to know the moment the wallet is funded and the sender can confirm the remittance. For the full deposit flow by rail, see [Global accounts](/guides/use-cases/global-accounts). If the sender pays after the on-ramp expires, Lumx refunds them on the same rail. See [Late deposits](/concepts/transactions#late-deposits). ## Step 4: Register the recipient's destination Register a destination for the recipient under the sender. Use `holder.relationship` of `RELATIVE` for family or `FRIEND` for friends. This is how the recipient is categorized for compliance. See [Coverage](/get-started/coverage) for the full list of supported rails and currencies. > [tab] PIX (BRL) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Mother - Brazil", > "rail": "PIX", > "currency": "BRL", > "identifier": { > "keyType": "CPF", > "keyValue": "123.456.789-00" > }, > "holder": { > "type": "INDIVIDUAL", > "relationship": "RELATIVE", > "name": "Ana Pereira", > "taxId": "123.456.789-00", > "birthDate": "1960-04-22", > "email": "ana.pereira@example.com", > "address": { > "line1": "Rua das Flores 250", > "city": "Belo Horizonte", > "state": "MG", > "postalCode": "30130-110", > "country": "BRA" > } > } > }' > ``` > [tab] SPEI (MXN) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Sister - Mexico", > "rail": "SPEI", > "currency": "MXN", > "identifier": { > "keyType": "CLABE", > "keyValue": "012180001234567897" > }, > "holder": { > "type": "INDIVIDUAL", > "relationship": "RELATIVE", > "name": "Sofia Hernández", > "taxId": "HEHS920811XYZ", > "birthDate": "1992-08-11", > "email": "sofia.hernandez@example.com", > "address": { > "line1": "Av. Reforma 500", > "city": "Mexico City", > "state": "CDMX", > "postalCode": "06600", > "country": "MEX" > } > } > }' > ``` > [tab] SEPA (EUR) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Cousin - France", > "rail": "SEPA", > "currency": "EUR", > "identifier": { > "iban": "FR1420041010050500013M02606", > "bic": "BNPAFRPP" > }, > "bank": { > "name": "BNP Paribas", > "address": { > "line1": "16 Boulevard des Italiens", > "city": "Paris", > "state": "IDF", > "postalCode": "75009", > "country": "FRA" > } > }, > "holder": { > "type": "INDIVIDUAL", > "relationship": "RELATIVE", > "name": "Emma Martin", > "taxId": "1850612345678", > "birthDate": "1985-06-12", > "email": "emma.martin@example.com", > "address": { > "line1": "12 Rue de Rivoli", > "city": "Paris", > "state": "IDF", > "postalCode": "75004", > "country": "FRA" > } > } > }' > ``` > [tab] FPS (GBP) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Sister - UK", > "rail": "FPS", > "currency": "GBP", > "identifier": { > "sortCode": "040509", > "accountNumber": "00125977" > }, > "bank": { > "name": "Mock Bank", > "address": { > "line1": "1 Bank Street", > "city": "London", > "state": "LND", > "postalCode": "EC1A 1BB", > "country": "GBR" > } > }, > "holder": { > "type": "INDIVIDUAL", > "relationship": "RELATIVE", > "name": "Sophie Default", > "taxId": "QQ123456C", > "birthDate": "1992-08-14", > "email": "sophie.default@example.com", > "address": { > "line1": "10 Downing Lane", > "city": "London", > "state": "LND", > "postalCode": "SW1A 2AA", > "country": "GBR" > } > } > }' > ``` > [tab] SWIFT (international) > > ```bash Request theme={null} > curl -X POST https://api-sandbox.lumx.io/destinations \ > -H "Authorization: Bearer YOUR_API_KEY" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: $(uuidgen)" \ > -d '{ > "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", > "name": "Friend - UK", > "rail": "SWIFT", > "currency": "USD", > "identifier": { > "iban": "GB29NWBK60161331926819", > "bic": "NWBKGB2L" > }, > "bank": { > "name": "NatWest Bank", > "address": { > "line1": "250 Bishopsgate", > "city": "London", > "postalCode": "EC2M 4AA", > "country": "GBR" > } > }, > "holder": { > "type": "INDIVIDUAL", > "relationship": "FRIEND", > "name": "Oliver Smith", > "taxId": "AB123456C", > "birthDate": "1990-03-22", > "email": "oliver.smith@example.com", > "address": { > "line1": "10 Downing Street", > "city": "London", > "postalCode": "SW1A 2AA", > "country": "GBR" > } > } > }' > ``` Destinations go through verification before they can be used. Subscribe to the `destinations.approved` webhook to know when a recipient is ready to receive funds. See [Destinations](/concepts/destinations) for the full reference. ## Step 5: Lock an exchange rate Senders want to know exactly how much the recipient will receive before confirming the transfer. Fetch a locked exchange rate first and reference its `id` in the off-ramp request to guarantee the conversion price. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/exchange-rates \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "type": "LOCKED", "rail": "PIX", "sourceCurrency": "USDC", "targetCurrency": "BRL", "sourceAmount": "500.00", "timelock": "1m" }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "type": "LOCKED", "rail": "PIX", "sourceCurrency": "USDC", "sourceAmount": "500.00", "targetCurrency": "BRL", "baseTargetAmount": "2587.50", "baseExchangeRate": "5.1750", "finalTargetAmount": "2581.81", "finalExchangeRate": "5.1636", "expiresAt": "2026-05-16T15:45:00Z" } ``` Show the resulting `finalTargetAmount` to the sender. If they confirm before `expiresAt`, the off-ramp will settle at `finalExchangeRate`. See [Exchange Rates](/concepts/exchange-rates). ## Step 6: Send the remittance Create an off-ramp transaction debiting the sender's wallet. Reference the locked `exchangeRateId` and use `purpose: "BILLS"` (the closest available code for P2P remittances; see [Purpose codes](/additional-information/purpose-codes)). ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/transactions/off-ramp \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284", "exchangeRateId": "123e4567-e89b-12d3-a456-426614174000", "purpose": "BILLS" }' ``` ```json Response theme={null} { "id": "9b1c5b8a-3d1f-4a8f-b6e0-2c1b6a7e9f33", "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "type": "OFF_RAMP", "request": { "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284", "exchangeRateId": "123e4567-e89b-12d3-a456-426614174000", "purpose": "BILLS" }, "state": { "status": "TRANSFERRING_STABLECOIN" } } ``` ## Step 7: Notify the sender on completion Each off-ramp follows this lifecycle: `TRANSFERRING_STABLECOIN` → `TRADING` → `TRANSFERRING_FIAT` → `SUCCESS` Subscribe to `offramp.success` and `offramp.failed` webhooks to push a confirmation back to the sender in your app the moment the recipient receives the funds. See [Webhooks](/developer/webhooks). ## Related resources > [card] [Global accounts](/guides/use-cases/global-accounts) > > Provision the sender's funding account. > [card] [Exchange Rates](/concepts/exchange-rates) > > Lock FX rates for predictable payouts. > [card] [Destinations](/concepts/destinations) > > Holder relationships and rail-specific identifiers. > [card] [Transactions](/concepts/transactions) > > Off-ramp lifecycle and purpose codes. # Treasury management Source: https://docs.lumx.io/guides/use-cases/treasury-management Hold working capital in stablecoin, sweep funds between entities, and settle to local fiat on demand Build a treasury product for corporate customers that need to hold working capital across multiple currencies, sweep funds between related entities, and execute FX conversions on demand. Balances are centralized in stablecoin in each entity's wallet, and converted to local fiat only when needed. ## Step 1: Generate your API key Head over to [dashboard.lumx.io](https://dashboard.lumx.io). Once logged in, generate a new API key and store it securely. See [Authentication](/get-started/authentication) for details. ## Step 2: Onboard the corporate with multi-currency accounts Create a business customer for the entity that will hold the treasury. Provision the fiat accounts they need so incoming deposits are converted to stablecoin and consolidated in the customer's wallet automatically. See [Coverage](/get-started/coverage) for supported currencies and rails. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/customers \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "type": "BUSINESS", "legalName": "Acme Holdings Ltd.", "taxId": "123456-7", "incorporationDate": "2019-01-15", "country": "CYM", "email": "treasury@acme.com", "accounts": ["USD", "EUR", "BRL", "MXN", "GBP"] }' ``` Before the corporate can transact, they must accept Lumx's terms of service (`POST /customers/{id}/tos`) and complete KYB. See [Business verification](/guides/business-verification) for the full onboarding flow. ## Step 3: Fund the corporate's wallet Once each account is `ACTIVE`, create an on-ramp transaction for every expected deposit, passing the exact amount the corporate plans to fund. The response returns rail-specific payment details in `state.payment`. Share them with the corporate's treasury team; when the deposit matches the amount, Lumx converts the fiat to stablecoin and consolidates it in the customer's wallet. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/transactions/on-ramp \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "rail": "FEDWIRE", "sourceCurrency": "USD", "sourceAmount": "1000000.00", "targetCurrency": "USDC", "purpose": "PERSONAL_ACCOUNT" }' ``` > [!NOTE] > > Each deposit must be matched to an on-ramp transaction created in advance with the exact amount. All deposits land as stablecoin in the same wallet. The corporate doesn't hold separate per-currency balances. If the corporate already holds stablecoin on-chain, they can also transfer it directly to the wallet returned at customer creation, no on-ramp needed. Subscribe to the `onramp.success` webhook to know the moment the wallet is credited and balances are ready to be moved or converted. For the full deposit flow by rail, see [Global accounts](/guides/use-cases/global-accounts). ## Step 4: Onboard subsidiaries and counterparty destinations How you model the corporate group depends on whether each entity needs its own wallet. Multi-entity (each subsidiary holds its own balance): onboard each subsidiary as its own business customer. Each gets a dedicated wallet, and you can move funds between them with internal transfers. Single-entity (parent holds all balances): register each related external destination under the parent customer with the appropriate `holder.relationship` so transfers out are correctly categorized. | **Relationship** | **Use when** | | :- | :- | | `SELF` | The destination belongs to the same legal entity as the customer | | `HOLDING_COMPANY` | The destination belongs to the parent of the customer | | `SUBSIDIARY_COMPANY` | The destination belongs to a subsidiary of the customer | The `taxId` format varies by jurisdiction; see [Tax IDs by country](/additional-information/tax-ids-by-country) for the expected value per country. ```bash Register a subsidiary's destination under the parent theme={null} curl -X POST https://api-sandbox.lumx.io/destinations \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "name": "Acme US - Operations", "rail": "FEDWIRE", "currency": "USD", "identifier": { "type": "CHECKING", "accountNumber": "123456789012", "routingNumber": "021000021" }, "bank": { "name": "JPMorgan Chase Bank", "address": { "line1": "383 Madison Avenue", "city": "New York", "state": "NY", "postalCode": "10179", "country": "USA" } }, "holder": { "type": "BUSINESS", "relationship": "SUBSIDIARY_COMPANY", "legalName": "Acme US Inc.", "taxId": "12-3456789", "incorporationDate": "2020-03-15", "email": "us@acme.com", "address": { "line1": "100 Wall Street", "city": "New York", "state": "NY", "postalCode": "10005", "country": "USA" } } }' ``` See [Destinations](/concepts/destinations) for all supported rails and the full holder schema. ## Step 5: Move funds between entities When subsidiaries are onboarded as separate customers, move stablecoin between their wallets without going through fiat. Transfers settle in seconds and don't incur FX cost. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/transactions/transfer \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "from": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "to": "980dc26b-42fd-4044-8a42-2271d20a2eb9", "currency": "USDC", "amount": "100000.000000", "metadata": { "memo": "Q2 working capital injection - Acme US" } }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174004", "type": "TRANSFER", "state": { "status": "PENDING" }, "request": { "currency": "USDC", "from": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "to": "980dc26b-42fd-4044-8a42-2271d20a2eb9", "amount": "100000.000000" } } ``` ## Step 6: Convert balances to fiat on demand When the corporate needs to settle in fiat (paying a supplier, repatriating profits, funding a subsidiary's local account), create an off-ramp transaction debiting the wallet. Use the purpose code that matches the move: | **Scenario** | **Purpose** | | :- | :- | | Sweep to the entity's own external account | `PERSONAL_ACCOUNT` (requires a destination with `SELF` holder relationship) | | Loan or capital injection between related entities | `LOAN` | | Cross-border supplier or invoice payment | `TRADE_TRANSACTIONS` | | Tax remittance | `TAX` | | Bill payment | `BILLS` | See [Purpose codes](/additional-information/purpose-codes) for the full list and rules. ```bash Request theme={null} curl -X POST https://api-sandbox.lumx.io/transactions/off-ramp \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284", "sourceCurrency": "USDC", "sourceAmount": "50000.00", "purpose": "LOAN" }' ``` > [!TIP] > > For predictable FX (budgeting, monthly settlements, locked supplier quotes), fetch a locked exchange rate first and pass its `id` as `exchangeRateId` on the off-ramp request. See [Exchange Rates](/concepts/exchange-rates). ## Step 7: Track and reconcile Subscribe to `offramp.success`, `offramp.failed`, `transfer.success`, and `account.active` webhooks to keep balances, transfers, and conversion status in sync inside your product. See [Webhooks](/developer/webhooks). ## Related resources > [card] [Global accounts](/guides/use-cases/global-accounts) > > Provision multi-currency virtual accounts. > [card] [Exchange Rates](/concepts/exchange-rates) > > Lock FX rates for predictable conversions. > [card] [Destinations](/concepts/destinations) > > Holder relationships for inter-company moves. > [card] [Transactions](/concepts/transactions) > > Transfer, off-ramp lifecycle and purpose codes. # Audit an existing Lumx integration Source: https://docs.lumx.io/prompts/audit-lumx-integration A copy-paste prompt that audits live Lumx code for what costs money — duplicate payments, unverified events, message matching, undisclosed parties. Paste this prompt into your coding agent to audit an integration that already runs, including one you inherited. The agent reviews a live integration for duplicate-payment risk, unverified events, fragile error handling, compliance gaps, and enum drift, and ranks what it finds. ## Before you start * Access to the integration's code, including webhook handlers and batch jobs. ## Prompt ```text Prompt theme={null} You are a senior engineer auditing a live Lumx integration. Find what will cost money or fail a compliance review, and rank by that, not by code style. Ground truth. Read both before writing any findings and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Every page has a .md twin at its own path. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. Enum and required-field claims come from here. Audit these seven, each against the sources, and cite file and line for every finding. 1. Duplicate payment risk. Every money-moving POST needs an Idempotency-Key generated once, persisted before the call, and reused on retry. A key minted inside a retry loop is the same as no key. Keys expire after 24 hours, so a retry a day later is a new payment. 2. Event trust. Webhook handlers must verify the signature over the raw body, strip the whsec_ prefix before decoding, accept any valid signature in the header, and deduplicate on webhook-id. Any handler acting on an unverified payload is a finding, not a style note. 3. Error matching. Handlers must branch on code, not on message — the message is explicitly subject to change and is generic on every 5xx. Any string comparison against a message is a finding. 4. Undisclosed parties. Read /compliance/nested-payments and check the money flow, not the code: every transaction must be the onboarded customer's own funds paying its own counterparty. Pooling, sub-balances used to track other parties, or payouts to parties nobody onboarded are compliance findings and outrank everything above. 5. Holder relationship and purpose. Each destination carries a holder.relationship from a closed enum and each transaction a purpose from a closed enum, and they have to be true rather than defaulted. PERSONAL_ACCOUNT is valid only with a SELF destination. 6. Limits awareness. Read GET /customers/{id}?includeTransactionLimits=true — limits are split by transaction type into onramp, offramp and transfer, and each transaction only counts against its own type — and check the code reads the matching type and handles a rejected transaction from a hit limit as a business state, not an exception. 7. Pagination and drift. Every list read — GET /customers, GET /transactions, GET /destinations, GET /accounts, GET /customers/{id}/limit-requests, GET /autoconversion-rules — needs size and cursor. Then compare the enums hardcoded in the codebase against the spec today and report each one that has drifted. Constraints: - Do not fix anything. Findings only. A wrong automated fix in a money path is worse than the finding. - Do not state a rate limit. None is published and the spec declares no 429 response. Check only that retries back off. - Where the spec and the prose disagree, report both and say which one the code followed. Deliverables: - Findings ranked by money and compliance exposure, each with file, line and the source that makes it a finding. - The list of hardcoded enums that have drifted from the spec. - The questions only Lumx can answer, and what each one blocks. ``` ## How to use 1. Point the agent at the whole integration, including the webhook handler and any batch job — the findings that matter are usually in the job nobody has read for a year. 2. Take finding class 4 to whoever owns compliance before you touch code. It is a business-model question, not a bug. 3. Re-run after fixing. Drifted enums come back every time the API adds a value. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `GET /customers/{id}` | Checks how the code reads transaction limits | | `GET /customers` | Checks pagination | | `GET /transactions` | Checks pagination | | `GET /destinations` | Checks pagination | | `GET /accounts` | Checks pagination | | `GET /customers/{id}/limit-requests` | Checks pagination | | `GET /autoconversion-rules` | Checks pagination | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | Same `Idempotency-Key` reused with a different body. | | `TRANSACTION_LIMIT_EXCEEDED` | 422 | Exceeds the customer's single, daily, or monthly limit for that transaction type. | | `PERSONAL_ACCOUNT_RELATIONSHIP_REQUIRED` | 400 | Personal transactions require a `SELF` bank account. | | `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded. | ## Related resources > [card] [Idempotency](/developer/idempotency) > > Safe retries with Idempotency-Key. > [card] [Nested payments](/compliance/nested-payments) > > Whose funds can move, and for whom. > [card] [Production readiness checklist](/prompts/sandbox-to-production-checklist) > > Audit a working Sandbox integration before real money moves. > [card] [Debug Lumx webhook delivery](/prompts/debug-webhooks) > > Find out why webhooks don't arrive or fail verification. # Build global contractor payouts Source: https://docs.lumx.io/prompts/build-contractor-payouts A copy-paste prompt that builds a payout run for contractors abroad — fund once in stablecoin, then settle each payee in their own local currency. Paste this prompt into your coding agent to pay a list of contractors in several countries from one funded balance. The agent onboards the employer, registers each contractor with a complete destination, checks limits before the run, and pays everyone in a resumable, idempotent batch. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * Your contractor list with each person's country, currency, and bank details. * A webhook endpoint registered under **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io). See [Webhooks](/developer/webhooks). ## Prompt ```text Prompt theme={null} You are a senior backend engineer building contractor payouts on Lumx: the employer is funded once in stablecoin, then each contractor is paid in their own local currency on a schedule. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index; /guides/use-cases/payroll walks this flow in prose and /compliance/nested-payments sets the limits on it. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. Destination shapes and every enum come from here. 1. Onboard the employer as a BUSINESS customer with POST /customers and take it to APPROVED. The employer's wallet is the funding source; a wallet keeps its balance between operations, so fund once per cycle rather than per payee. 2. Register each contractor with POST /destinations under the employer, generating the branch from the spec for that rail. Set holder.relationship to EMPLOYEE for employees and contractors — this is the field a compliance review reads, so it must be true. 3. Collect what each rail actually needs before the first run. A contractor paid over PIX needs a key and its type; over SPEI a CLABE; over ACH or FEDWIRE an account and routing number plus the account type; over SEPA an IBAN and a BIC; over FPS a sort code and an account number. A contractor whose data is incomplete stays out of the run and appears in the exception list. 4. Check headroom before paying, not after. Read GET /customers/{id}?includeTransactionLimits=true and compare the run total against the offramp limits — limits are split by transaction type — using the per-transaction maximum and the daily and monthly remaining amounts. A run that exceeds them fails midway and leaves half the team paid. 5. Pay with POST /transactions/off-ramp, one call per contractor, each with its own Idempotency-Key generated once and persisted before the call, and a purpose from the request enum that describes the payment. 6. Track each payout with the offramp.* events and confirm with GET /transactions/{id}. Report per contractor, not per run — "the run succeeded" is not a thing anyone can act on. 7. Make the run resumable and idempotent end to end: re-running after a crash must pay exactly the contractors who were not paid. Constraints: - The employer pays its own contractors. A design where one account pays the contractors of several employers is nesting and is not allowed; read the payroll rows in /compliance/nested-payments and stop if that is the design. - Do not state a rate limit for the run. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. - Do not pick a rail for a contractor. Take it from the data you hold and flag anyone whose country has no rail in the spec's enum. Deliverables: - A contractor registry with a per-rail completeness check. - A resumable, idempotent run with a pre-flight limit check. - Per-contractor status reporting and an exception list. ``` ## How to use 1. Bring the contractor list with country, currency and whatever bank data you already hold. The completeness check is the first useful output and it usually surprises people. 2. Fund the employer's wallet before the first real run; an unfunded run fails on balance and tells you nothing about your code. 3. Ask for a limit increase before the first big cycle if the pre-flight check says the run will not fit. You can request it through the API or from the customer's page in the Dashboard; either way it takes a supporting document and a compliance review. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `POST /customers` | Onboards the employer as a business customer | | `GET /customers/{id}` | Reads the employer's remaining transaction limits | | `POST /destinations` | Registers each contractor's bank account | | `POST /transactions/off-ramp` | Pays each contractor, one call per person | | `GET /transactions/{id}` | Confirms each payout | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `INSUFFICIENT_BALANCE` | 422 | Balance is too low for the operation. | | `TRANSACTION_LIMIT_EXCEEDED` | 422 | Exceeds the customer's single, daily, or monthly limit for that transaction type. | | `RAIL_NOT_ENABLED` | 403 | Rail is not enabled for this project. | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | Same `Idempotency-Key` reused with a different body. | ## Related resources > [card] [Payroll](/guides/use-cases/payroll) > > Pay employees and contractors abroad. > [card] [Limit increase requests](/guides/limit-increase-requests) > > Raise a customer's transaction limits. > [card] [Build marketplace seller payouts](/prompts/build-marketplace-payouts) > > Pay marketplace sellers and take a platform fee on every sale. > [card] [Integrate off-ramps to local bank rails](/prompts/integrate-offramp) > > Pay out from a stablecoin balance to a local bank account. # Build marketplace seller payouts Source: https://docs.lumx.io/prompts/build-marketplace-payouts A copy-paste prompt that builds a marketplace where each seller is onboarded, the platform takes a fee on every sale and sellers settle in local currency. Paste this prompt into your coding agent to build the payout side of a marketplace, with the platform fee taken on every sale. The agent sets up the platform fee, onboards each seller as its own customer, takes buyer payments in, pays sellers out, and reconciles all three sides of every sale. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * An answer from your compliance owner on how seller funds flow. See [Nested payments](/compliance/nested-payments). * A webhook endpoint registered under **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io). See [Webhooks](/developer/webhooks). ## Prompt ```text Prompt theme={null} You are a senior backend engineer building seller payouts for a marketplace on Lumx. The platform takes a cut of every sale; each seller gets paid in their own local currency. Ground truth. Read all three before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/compliance/nested-payments — this decides whether the design is legal before it decides whether it works. Read it first. 2. https://docs.lumx.io/llms.txt — the documentation index; /guides/use-cases/marketplaces walks the same flow in prose. 3. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec, for every field, enum and required flag. 1. Settle the structure first. Every seller must be onboarded as its own Lumx customer and receive funds in its own wallet. A design where buyer money pools in the platform's account and the platform distributes it is nesting and is not allowed. If the product spec requires that, stop and report it. 2. Configure the platform's cut once with POST /partner-fees: a name, the platform's own walletAddress, and rate in basis points plus flatAmount for onRamp and offRamp. Keep the id. 3. Onboard each seller with POST /customers — BUSINESS for incorporated sellers, INDIVIDUAL for sole traders — and pass the accounts array only for sellers who receive fiat from outside your checkout. Sellers paid through your own checkout need no account; the on-ramp credits their wallet directly. 4. Take the money in. For a buyer paying on your checkout, create POST /transactions/on-ramp on the seller's behalf with your partnerFeeId (for a locked rate, pass it when creating the exchange rate instead), then render state.payment for the buyer. For a seller collecting from an external platform into a virtual account, the deposit arrives on the account instead. 5. Pay the seller out. Register the seller's own bank account with POST /destinations using holder.relationship SELF — the seller is paying itself — and settle with POST /transactions/off-ramp. 6. Reconcile three ledgers per sale: what the buyer paid, what the seller received, and what the platform kept. Read the fee breakdown from the locked-rate response or the transaction receipt; never compute the platform's revenue by subtraction. Constraints: - A seller who has not reached APPROVED cannot receive funds. Read the status from GET /customers/{id} and model it as a product state with its own screen, not as an error. - Purpose comes from the request enum and must describe the real trade. PERSONAL_ACCOUNT is only valid with a SELF destination. - Do not state a rate limit while paying a batch of sellers. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. Deliverables: - A seller onboarding flow with the pending and rejected states handled. - Checkout and external-collection paths, both carrying partnerFeeId. - A three-way reconciliation per sale, read from API data rather than derived. - A written answer to the nesting question in step 1. ``` ## How to use 1. Answer the nesting question with whoever owns compliance before building. It decides the product, and no amount of code fixes a pooled design. 2. Set the platform rate with whoever owns pricing; it lands in every buyer-facing quote. 3. Onboard two sandbox sellers in different countries. One seller hides every problem this flow has. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `POST /partner-fees` | Configures the platform's cut once | | `POST /customers` | Onboards each seller | | `GET /customers/{id}` | Reads each seller's verification status | | `POST /destinations` | Registers each seller's own bank account | | `POST /transactions/on-ramp` | Takes a buyer's payment on the seller's behalf | | `POST /transactions/off-ramp` | Pays the seller out | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `KYC_NOT_APPROVED` | 403 | Customer identity verification (KYC/KYB) is not approved. | | `PARTNER_FEE_NOT_FOUND` | 404 | Partner fee does not exist. | | `INVALID_WALLET_ADDRESS` | 400 | Partner wallet address is invalid for the blockchain. | | `INSUFFICIENT_BALANCE` | 422 | Balance is too low for the operation. | ## Related resources > [card] [Marketplaces](/guides/use-cases/marketplaces) > > Collect from buyers and pay sellers. > [card] [Nested payments](/compliance/nested-payments) > > Whose funds can move, and for whom. > [card] [Set up partner fees and revenue share](/prompts/setup-partner-fees) > > Earn a fee on every transaction you route. > [card] [Build global contractor payouts](/prompts/build-contractor-payouts) > > Pay contractors in several countries from one funded balance. # Build a Lumx reconciliation ledger Source: https://docs.lumx.io/prompts/build-reconciliation-ledger A copy-paste prompt that builds a ledger reconciling Lumx transactions against your own records, with late and failed treated as different states. Paste this prompt into your coding agent to build the ledger that answers "did this actually settle" without anyone opening a dashboard. The agent models each transaction type's statuses, ingests from both webhooks and the API, and tells a late payment apart from a failed one using the cut-off table. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * Your internal ledger or accounting schema, so the agent can join to it. * A webhook endpoint registered under **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io). See [Webhooks](/developer/webhooks). ## Prompt ```text Prompt theme={null} You are a senior backend engineer building a reconciliation ledger over Lumx transactions. The job is to answer, for any date, which movements settled, which are still in flight, and which failed — and to tell late apart from broken. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Read /additional-information/sla-and-cutoffs and /concepts/transactions before designing the state model. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. Status enums differ per transaction type; take each one from the spec rather than sharing one enum across types. 1. Model the three types separately. On-ramp, off-ramp and transfer each have their own status progression in the spec, and collapsing them into one enum is what makes a ledger lie. Read GET /transactions/{id} and map each type's statuses, including the terminal failure. 2. Ingest twice, from two directions. Webhooks give you the state change as it happens; GET /transactions with size and cursor gives you the backfill and the truth after an outage. Treat the API read as authoritative and the webhook as a trigger. 3. Key every row on the Lumx transaction id and your own reference. Put your reference in the metadata object on creation so the join exists before you need it — you cannot add it afterwards. 4. Read the receipt, not the request. On success the receipt carries the amounts, the rate, the fees and the chain transaction hash; the request carries what you asked for. A ledger built on request values overstates every conversion. 5. Distinguish late from failed using the per-rail cut-off table. PIX, SPEI and FPS are instant and 24/7; ACH, FEDWIRE and SWIFT have same-day cut-offs in US Eastern time and do not run on weekends or US holidays; SEPA is instant, except payments of €100k or more sent between 4:30 PM and 2:30 AM UK time, which route to SEPA Credit Transfer and settle the same or next business day. An off-ramp submitted after a rail's cut-off is not late until the next business day in that rail's own timezone. The transaction resource does not carry rail for an off-ramp, so join through the destination to learn which rail a payout used. 6. Define settled the way Lumx does: the off-ramp is complete once funds leave the banking partner. The beneficiary bank may still hold them, so your ledger needs a state for "sent, not yet credited" or it will report false failures. 7. Run the backfill sequentially and resumably, persisting the cursor, so a restart does not re-read the whole history. Constraints: - Do not state a rate limit for the backfill. None is published and the spec declares no 429 response, so paginate conservatively, handle 429 TOO_MANY_REQUESTS with exponential backoff, and record the missing limit as an open question. - Never infer a status the spec does not list for that type. Flag an unknown status rather than mapping it to the nearest known one. - state.error is an untyped object in the spec, and the failed-event examples carry only a generic message with no code. Build for an opaque blob, do not match on a code key that may not exist, and record the missing failure taxonomy as a question. - onramp.expired carries status EXPIRED, which the on-ramp status enum in the spec does not list. Treat it as terminal, and log any event or status you cannot map rather than coercing it into one you know. Deliverables: - A ledger schema with per-type status columns and a late-versus-failed derivation. - A resumable backfill plus a webhook-triggered incremental update. - A daily report of unreconciled rows, each with the reason it is unreconciled. ``` ## How to use 1. Start putting your own reference into transaction metadata today, even before the ledger exists. Rows created without it are the ones you will reconcile by hand. 2. Give the agent your existing internal schema; a ledger that does not join to your books is a second set of numbers. 3. Read the open question on the rate limit and take it to Lumx before the backfill runs against real volume. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `GET /transactions` | Backfills the ledger with paginated reads | | `GET /transactions/{id}` | Reads a single transaction's status and receipt | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `TRANSACTION_NOT_FOUND` | 404 | Transaction does not exist. | | `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded. | ## Related resources > [card] [Transactions](/concepts/transactions) > > Transaction types and status progressions. > [card] [Payment rail cut-off times](/additional-information/sla-and-cutoffs) > > Cut-offs and settlement times per rail. > [card] [Audit an existing Lumx integration](/prompts/audit-lumx-integration) > > Find what costs money or fails compliance in a live integration. > [card] [Migrate from one PSP per country](/prompts/migrate-from-one-psp-per-country) > > Collapse one payment provider per country into one integration. # Connect your agent to the Lumx docs MCP Source: https://docs.lumx.io/prompts/connect-lumx-docs-mcp A copy-paste prompt that wires the Lumx documentation MCP server into your coding agent so it reads real schemas instead of inventing field names. Paste this prompt into your coding agent to stop it guessing Lumx field names. This connects it to the docs, not to the API — no money can move through it. The agent installs the docs MCP server for your host, checks which tools it exposes, and reads field names from the spec through it instead of from memory. ## Before you start * A coding agent that supports MCP servers, such as Claude Code, Cursor, VS Code, or Codex. ## Prompt ```text Prompt theme={null} You are configuring your own tooling. Connect yourself to the Lumx documentation MCP server, verify it works, and then use it instead of your memory for every Lumx schema question. The server gives you documentation, not API access. It cannot create a customer, quote a rate or move money — that needs the REST API and a server-side key, which this does not give you. Do not assume it is strictly read-only either: check the tool list for anything that writes, and do not call such a tool on my behalf without asking. Ground truth for the setup itself: 1. https://docs.lumx.io/developer/mcp-server — the server URL and the install line for each host. Also read https://docs.lumx.io/llms.txt for the documentation index. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec, which is also reachable through the server once connected. 1. Install the server for this host. The transport is HTTP and there is no authentication and no API key. On Claude Code the whole setup is: claude mcp add --transport http lumx-docs https://docs.lumx.io/mcp — for Cursor, VS Code, Codex or Claude Web, take the exact config block for your host from the docs page and do not improvise a stdio wrapper. 2. Verify the connection by listing the tools. Report the names you actually get back rather than the names you expected — the docs page describes two and the server may expose more. 3. Learn what each tool is for: semantic search across the docs for "where do I find X", and shell-style access over the raw doc and OpenAPI files, with rg, head, cat and tree, for "what exactly are the required fields of X". 4. Find the spec through the server rather than downloading it: list the tree and locate the OpenAPI files, then read the production one from there — a staging spec sits beside it, so name the file you read and never mix the two in one answer. Confirm you can grep an enum out of it. 5. Prove it changes an answer. Pick any Lumx request body, write down the field names from memory first, then read them from the spec through the server, and diff the two lists out loud. 6. Adopt the rule for the rest of this session: any Lumx field name, enum value or endpoint path you are about to write, you read first. If the server is unreachable, say so and fall back to the two URLs above rather than to memory. Constraints: - Do not describe this server as API access. It reads published documentation. - Do not configure discovery through any .well-known path; use the published server URL directly. - Do not send any credential to this server. It does not take one, and anything you send it leaves your machine. Deliverables: - The working config for this host, in the right file. - The tool list you actually received. - The memory-versus-spec diff from step 5, which is the evidence this was worth doing. ``` ## How to use 1. Run the install line yourself if your host needs a restart to pick up a new server — most do. 2. Do step 5 with a schema you think you know. The diff is the whole point, and it is usually not empty. 3. If your host has no MCP support, skip this and keep the two URLs in your project instructions instead. ## Related resources > [card] [MCP server](/developer/mcp-server) > > Connect AI tools to these docs. > [card] [OpenAPI specification](/developer/openapi) > > Download the API spec. > [card] [Lumx sandbox quickstart](/prompts/sandbox-quickstart) > > Prove a deposit-to-stablecoin round trip in Sandbox. > [card] [Integrate Lumx into a Next.js app](/prompts/integrate-lumx-nextjs) > > Add Lumx to a Next.js App Router project without leaking the key. # Debug Lumx webhook delivery Source: https://docs.lumx.io/prompts/debug-webhooks A copy-paste prompt that walks the real failure taxonomy of a Lumx webhook endpoint — raw body, rotated secret, replay window, retries, silent drops. Paste this prompt into your coding agent when events are not arriving, or are arriving and failing verification. The agent works through the causes in order, from an unreachable endpoint to a re-serialized body, a prefixed secret, a rotation, and clock skew, and names the one that applies. ## Before you start * One failing delivery from the [Dashboard](https://dashboard.lumx.io): its id, timestamp, and body. ## Prompt ```text Prompt theme={null} You are a senior engineer debugging a Lumx webhook endpoint that is not working. Diagnose before you change anything: the symptom "signature invalid" has four different causes and three of them are not the secret. Ground truth. Read both before changing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/developer/webhooks — the signature scheme, the event catalog, the retry schedule and the delivery IPs. Also read https://docs.lumx.io/llms.txt for the rest of the documentation. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec, for the resource shapes inside the payload. Work through the causes in this order, cheapest first, and report what you ruled out at each step. 1. Nothing arrives at all. Check the endpoint is registered in the Dashboard under Developers → Webhooks, is publicly reachable, and returns 2xx to a bare POST. If the infrastructure filters inbound traffic, confirm the published Lumx delivery IPs are allowed — the same set serves sandbox and production. 2. It arrives and 401s or 403s before your handler. If the endpoint URL carries basic-auth credentials, Lumx extracts them and sends an Authorization header; a proxy that strips or double-handles it breaks delivery before any signature check. 3. Signature fails on every event. The most common cause is a re-serialized body: verification runs over the exact bytes received, so capture the raw body before any JSON parser touches it. Second cause: the secret was used as-is — the whsec_ prefix must be stripped and the remainder base64-decoded before it becomes the HMAC key. 4. Signature fails only since a secret rotation. During a rotation Lumx signs with the old and the new secret for 24 hours, so the header carries a space-delimited list. Code that reads only the first signature fails intermittently for exactly one day. Accept any valid entry. 5. Signature fails only on some events. Suspect the replay window: the signed content is "{webhook-id}.{webhook-timestamp}.{body}" and a clock skew on your side rejects valid events. Log the computed skew before rejecting. 6. Events arrive more than once, or out of order. That is expected. Delivery is attempted up to eight times in total, with growing backoff after any non-2xx, so deduplicate on webhook-id and treat state from GET /transactions/{id} as authoritative over arrival order. 7. Events stop after a while. After the retries are exhausted the message is marked failed and can only be replayed from the Dashboard. Check whether your endpoint was returning non-2xx during a deploy. Constraints: - Do not weaken verification to make events flow, including in sandbox. - Do not claim an event type exists without finding it in the catalog on the docs page. - Log the failure cause; never swallow a rejected event silently. Deliverables: - A diagnosis naming which of the seven causes applied, with the evidence. - The fix, plus a unit test that would have caught it. - A replay script that posts a stored event locally so the next diagnosis needs no live traffic. ``` ## How to use 1. Collect one failing delivery from the Dashboard first — its id, timestamp and body. Debugging from logs alone costs an extra round. 2. Rotate the signing secret yourself if step 4 is the suspect; the agent must not rotate credentials. 3. Keep the replay script in the repo. Every webhook bug after this one is cheaper with it. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `GET /transactions/{id}` | Confirms state when events arrive out of order | ## Related resources > [card] [Webhooks](/developer/webhooks) > > Event catalog, signatures, and retries. > [card] [Transactions](/concepts/transactions) > > Transaction types and status progressions. > [card] [Integrate webhooks with signature checks](/prompts/integrate-webhooks) > > Receive signed events that survive retries and secret rotation. > [card] [Production readiness checklist](/prompts/sandbox-to-production-checklist) > > Audit a working Sandbox integration before real money moves. # Integrate autoconversion rules Source: https://docs.lumx.io/prompts/integrate-autoconversion-rules A copy-paste prompt for a standing fiat-to-stablecoin rule on a Lumx account — one setup call, no per-deposit API call, tested in sandbox. Paste this prompt into your coding agent to convert every deposit that lands in an account, without a call per deposit. The agent creates a rule on an account, renders the deposit details for each rail, tracks every conversion, and proves the loop with the sandbox simulator. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * A customer with an `ACTIVE` account. See [Accounts](/concepts/accounts). * A webhook endpoint registered under **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io). See [Webhooks](/developer/webhooks). ## Prompt ```text Prompt theme={null} You are a senior backend engineer setting up Lumx autoconversion — a standing rule that converts fiat deposits to stablecoin as they land, with no per-transaction call. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/guides/autoconversion-rules — the rule lifecycle, the per-currency deposit details and the sandbox simulator. Also read https://docs.lumx.io/llms.txt for the rest of the documentation. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec, for the request body and the sourceDepositInfo shape. Work against sandbox at https://api-sandbox.lumx.io with a server-side key sent as "Authorization: Bearer ". 1. Find the target account with GET /accounts?customerId={id}. The rule attaches to an account, and the account's currency decides the source currency — you do not send it. 2. Create the rule with POST /autoconversion-rules, sending accountId, targetCurrency, purpose and name, plus blockchain and partnerFeeId when you need them. 3. Store sourceDepositInfo from the response and render it per rail: brCode for PIX, clabe plus reference for SPEI, accountNumber with routingNumber or bic for USD, iban plus bic for SEPA (EUR), sortCode plus accountNumber for FPS (GBP). Re-read it with GET /autoconversion-rules/{id} rather than caching it in your own database. 4. Enforce the two documented limits in your own UI before calling: each deposit must be worth at least 50 USD in the account's currency, and a customer can hold only one active rule per USD, EUR or GBP account. A second one returns 409 AUTOCONVERSION_RULE_ALREADY_EXISTS. 5. Track conversions through the onramp.* webhook events — each matched deposit runs as an on-ramp transaction, from onramp.transferring_fiat to onramp.success. 6. Prove the loop in sandbox with POST /autoconversion-rules/simulate-deposit, passing accountId, amount, and the rule's depositIdentifier or reference as "reference". For a USD, EUR or GBP rule, omit reference. The response is matchedRule. Constraints: - Rules convert fiat to stablecoin only. The prose marks the reverse direction as coming; do not build against it. - The guide tells you to delete a rule to replace a USD, EUR or GBP one, but the spec exposes no delete operation. Do not guess a method or a path — flag it and ask. - The simulator is sandbox-only and returns 404 in production. Keep it out of any shared code path. - Do not state a rate limit while listing rules with GET /autoconversion-rules. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. Deliverables: - A rule setup function and a renderer for sourceDepositInfo covering PIX, SPEI, USD, SEPA and FPS. - A webhook handler that ties each converted deposit back to its rule. - A sandbox test that simulates a matching and a non-matching deposit and asserts matchedRule. ``` ## How to use 1. Have an `ACTIVE` account before you start — a rule cannot attach to an account still provisioning. 2. Decide the purpose code with whoever owns compliance. It is reported on every conversion the rule runs, not just the first. 3. Read the agent's flag on the missing delete operation and take it to support before you design a rule-replacement flow. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `GET /accounts` | Finds the account the rule attaches to | | `POST /autoconversion-rules` | Creates the rule and returns the deposit details | | `GET /autoconversion-rules/{id}` | Re-reads a rule's deposit details | | `GET /autoconversion-rules` | Lists a customer's rules | | `POST /autoconversion-rules/simulate-deposit` | Simulates a deposit in Sandbox to test matching | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `AUTOCONVERSION_RULE_ALREADY_EXISTS` | 409 | Customer already has an active USD, EUR, or GBP autoconversion rule. | | `INVALID_AUTOCONVERSION_RULE` | 400 | Autoconversion rule is invalid. | | `ACCOUNT_NOT_ACTIVE` | 409 | Account is not in a usable state. | | `AUTOCONVERSION_RULE_NOT_FOUND` | 404 | Autoconversion rule does not exist. | ## Related resources > [card] [Autoconversion rules](/guides/autoconversion-rules) > > Convert every deposit with a standing rule. > [card] [Accounts](/concepts/accounts) > > Virtual accounts, provisioning, and status. > [card] [Integrate local-currency virtual accounts](/prompts/integrate-virtual-accounts) > > Collect in local currency through accounts in your customer's name. > [card] [Build the BRL corridor end to end](/prompts/integrate-brl-corridor) > > Build BRL in over PIX and out again as working code. # Build the BRL corridor end to end Source: https://docs.lumx.io/prompts/integrate-brl-corridor A copy-paste prompt that builds the whole Brazil corridor in code — PIX in, USDC out, standing conversion, webhooks and reconciliation against cut-offs. Paste this prompt into your coding agent to build the Brazil corridor as working code, not as a currency pair on a page. The agent onboards a customer with a BRL account, chooses between a standing rule and a per-deposit on-ramp, quotes the conversion, and closes the loop with a payout. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * A webhook endpoint registered under **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io). See [Webhooks](/developer/webhooks). ## Prompt ```text Prompt theme={null} You are a senior backend engineer building the Brazil corridor on Lumx: BRL in over PIX, stablecoin held, BRL or foreign currency out. Build the flow, not a converter. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Read /get-started/coverage for the supported pairs and /additional-information/sla-and-cutoffs for the timing. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec, for field names, required flags and enums. 1. Onboard the customer with POST /customers, passing accounts: ["BRL"]. BRL provisions immediately once the customer is APPROVED, so poll GET /accounts?customerId={id} until the account is ACTIVE and treat the wait as a product state. 2. Choose how BRL arrives, and write down why. A standing rule with POST /autoconversion-rules converts every deposit with no per-deposit call and returns a static PIX brCode. A per-deposit POST /transactions/on-ramp expects one known amount and returns a brCode for that amount. Recurring collection wants the rule; checkout wants the transaction. 3. Price it before the customer commits. POST /exchange-rates with type LOCKED returns an id and an expiresAt under a timelock of 30s, 1m or 5m, and with type FLOATING prices at execution. Pass the locked id on the transaction and handle EXCHANGE_RATE_EXPIRED as a re-quote, not an error screen. 4. Render the PIX payload correctly: the brCode is the string the payer pastes or scans, and for a standing rule the depositIdentifier embedded in it is what matches the deposit. Take both field names from the spec, not from memory. 5. Pick the stablecoin and the chain deliberately. Read the supported-pairs table: BRL converts to USDC and USDT on the chains listed there, and one chain is on-ramp only. Pass blockchain explicitly rather than relying on the project default. 6. Close the loop out. For BRL out, register a PIX destination with POST /destinations and settle with POST /transactions/off-ramp; for foreign currency out, use the destination branch for that rail. 7. Reconcile on PIX terms: PIX has no cut-off and runs 24/7/365, so a BRL leg that has not settled is a real problem rather than a timing artifact — unlike ACH, FEDWIRE, SWIFT and SEPA Credit Transfer, which have cut-offs or business-day windows. Constraints: - Only currencies and chains present in the spec's enums may appear in the code. Confirm each pair against the coverage table and flag any combination it does not list. - A deposit under the documented minimum for a standing rule is never converted, so validate the amount before showing a code. - Do not state a rate limit. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. Deliverables: - The chosen collection mode with the reason recorded in the code. - A quote-to-settlement path with expiry handled. - A sandbox run proving BRL in and BRL out, printing each status change. ``` ## How to use 1. Decide standing rule versus per-deposit transaction before running this, or check the agent's reason in step 2. It is the one choice that changes the whole product. 2. Pick the chain with whoever holds the treasury; fees and finality differ and the code should not decide it. 3. Test with a sandbox amount above the minimum, or the conversion never starts and the failure looks like a bug. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `POST /customers` | Creates the customer with a BRL account | | `GET /accounts` | Waits for the BRL account to become active | | `POST /autoconversion-rules` | Creates a standing rule with a static PIX code | | `POST /exchange-rates` | Quotes the conversion before the customer commits | | `POST /transactions/on-ramp` | Creates a per-deposit on-ramp with a PIX code for one amount | | `POST /destinations` | Registers the destination for the payout | | `POST /transactions/off-ramp` | Sends BRL or foreign currency out | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `EXCHANGE_RATE_EXPIRED` | 422 | Locked rate has expired. | | `MINIMUM_AMOUNT_NOT_MET` | 422 | Amount is below the supported minimum for the currency. | | `ACCOUNT_NOT_ACTIVE` | 409 | Account is not in a usable state. | | `INSUFFICIENT_BALANCE` | 422 | Balance is too low for the operation. | ## Related resources > [card] [Coverage](/get-started/coverage) > > Supported currencies, rails, and stablecoin pairs. > [card] [Autoconversion rules](/guides/autoconversion-rules) > > Convert every deposit with a standing rule. > [card] [Integrate on-ramps from fiat to stablecoin](/prompts/integrate-onramp) > > Take fiat in and deliver stablecoin to the customer's wallet. > [card] [Integrate autoconversion rules](/prompts/integrate-autoconversion-rules) > > Convert every deposit to stablecoin with a standing rule. # Integrate customer onboarding and verification Source: https://docs.lumx.io/prompts/integrate-customer-onboarding A copy-paste prompt that builds Lumx KYC and KYB end to end — terms, documents, associated parties, RFI, and the sandbox sentinels to test it. Paste this prompt into your coding agent to onboard businesses and individuals up to an approved customer that can move money. The agent creates the customer, collects terms, the questionnaire, and documents, registers associated parties for businesses, and drives verification to a final status. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * A page on your own domain to host the terms of service redirect. * A webhook endpoint registered under **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io). See [Webhooks](/developer/webhooks). ## Prompt ```text Prompt theme={null} You are a senior backend engineer building Lumx customer onboarding — KYB for businesses, KYC for individuals — inside an existing product. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Every page has a .md twin at its own path. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. Every enum below is long and closed; read it from the spec rather than from memory. Work against sandbox at https://api-sandbox.lumx.io with a server-side key sent as "Authorization: Bearer ". 1. Create the customer with POST /customers. The body is a union on type and both branches require email and country: BUSINESS adds legalName, taxId and incorporationDate, INDIVIDUAL adds name, taxId and birthDate. Each branch keeps its required list in an allOf sibling rather than on the branch itself, so read it there — the branch alone declares nothing required. INDIVIDUAL country is an enum with a single value today (BRA) while BUSINESS country is a free string; do not widen the individual form past the enum. Request fiat currencies in "accounts" in the same call. 2. Get terms accepted with POST /customers/{id}/tos. It returns a url and an expiresAt — redirect a human to it and handle the expiry, do not automate acceptance. verification.link stays absent from the customer until the TERMS_OF_SERVICE requirement is APPROVED. 3. Send the questionnaire with PATCH /customers/{id}/additional-information. It is a union on type and the enums are closed: professionalSituation, sourceOfFunds and transactionCounterparties for individuals; businessActivity, monthlyTransactionVolumeInUSD, sourceOfFunds and transactionCounterparties for businesses. monthlyIncomeInUSD (individuals) and monthlyRevenueInUSD (businesses) are required USD amount strings. Customers created before October 28, 2026 keep the previous questionnaire (jurisdictions, involvedActivities, companyType); a body that doesn't match the customer's questionnaire returns 400 VALIDATION_ERROR. Build your form from the spec values. 4. Upload documents with POST /customers/{id}/documents as multipart with file, type from the document enum and country (ISO alpha-3). Add side for ID_CARD, PASSPORT and DRIVERS_LICENSE, and associatedPartyId when the document belongs to an associated party. 5. For a business, register every UBO, shareholder and representative with POST /customers/{id}/associated-parties (individual parties require monthlyIncomeInUSD, except for customers created before October 28, 2026), then start review with POST /customers/{id}/verifications and send no body. The spec says associatedPartyIds retries named parties in isolation and does not restart the customer's overall verification, so it is not how you open the first review. 6. Keep the two vocabularies apart. The webhook events are customer.created, customer.under_verification, customer.rfi, customer.approved and customer.final_rejection; polling GET /customers/{id} gives verification.status, whose spec enum is NOT_STARTED, UNDER_VERIFICATION, APPROVED, TEMPORARY_REJECTION and FINAL_REJECTION. There is no created state and no RFI string in that enum — the docs call that same state RFI. Do not match an event name against a status field, and flag the naming conflict instead of choosing a side. The state is resumable; FINAL_REJECTION is terminal. 7. Test every branch in sandbox with the taxId sentinel: last digit 1 gives NOT_STARTED, 2 gives the RFI state the spec enum calls TEMPORARY_REJECTION, 3 gives FINAL_REJECTION, any other digit gives APPROVED. Constraints: - Never hardcode an enum you did not read in the spec today. Flag any value your form needs that is not there. - Wallets are absent from the create response by design. Read them from GET /customers/{id} or wait for customer.approved. - Do not state a rate limit. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. Deliverables: - A typed onboarding client covering the seven endpoints above. - A resumable state machine with an explicit RFI path. - A sandbox suite that drives all four sentinel outcomes. ``` ## How to use 1. Decide first whether you onboard businesses, individuals, or both — the two paths differ in required fields and in which documents a reviewer will ask for. 2. Host the terms redirect yourself. The agent builds the handler, but the URL has to live on a domain your customers already trust. 3. Run the sandbox suite before you show the form to anyone: the `RFI` branch is the one real onboarding hits and the one teams skip. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `POST /customers` | Creates a business or individual customer | | `POST /customers/{id}/tos` | Returns the terms of service link and its expiry | | `PATCH /customers/{id}/additional-information` | Sends the onboarding questionnaire | | `POST /customers/{id}/documents` | Uploads identity and company documents | | `POST /customers/{id}/associated-parties` | Registers UBOs, shareholders, and representatives | | `POST /customers/{id}/verifications` | Submits the customer for review | | `GET /customers/{id}` | Reads the verification status | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `VALIDATION_ERROR` | 400 | Request body failed validation. Details in `validationErrors`. | | `CUSTOMER_NOT_FOUND` | 404 | Customer does not exist. | | `CUSTOMER_LIMIT_REACHED` | 403 | Project reached its maximum number of customers. | | `RESOURCE_ALREADY_EXISTS` | 409 | A resource with the same unique value already exists. | | `INVALID_OR_EXPIRED_TOKEN` | 401 | Terms of service token is invalid or expired. | ## Related resources > [card] [Create a customer](/guides/create-a-customer) > > The first call in every integration. > [card] [Identity verification](/compliance/identity-verification) > > KYC, KYB, and sandbox outcomes. > [card] [Integrate local-currency virtual accounts](/prompts/integrate-virtual-accounts) > > Collect in local currency through accounts in your customer's name. > [card] [Integrate webhooks with signature checks](/prompts/integrate-webhooks) > > Receive signed events that survive retries and secret rotation. # Integrate Lumx into a Next.js app Source: https://docs.lumx.io/prompts/integrate-lumx-nextjs A copy-paste prompt that wires Lumx into a Next.js App Router app — server-only key, route handlers, and the raw-body webhook trap that bites everyone. Paste this prompt into your coding agent to add Lumx payments to a Next.js app without leaking the key or breaking signature checks. The agent keeps the API key on the server, builds route handlers for customers and deposits, verifies webhooks over the raw body, and connects the docs MCP server. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * A Next.js app on the App Router. * A webhook endpoint registered under **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io). See [Webhooks](/developer/webhooks). ## Prompt ```text Prompt theme={null} You are a senior full-stack engineer adding Lumx payments to an existing Next.js App Router application. The API is server-side only, so every design decision follows from that. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Every page has a .md twin at its own path. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec, for field names, required flags and enums. 1. Put the key somewhere it cannot leak. Read it from a server-only environment variable, never one prefixed for the client, and add the env file to .gitignore. If the repo has a client component importing anything that touches the key, that is the first thing to fix. 2. Build one server-side Lumx module that every route handler shares: base URL from configuration, the bearer header, the Idempotency-Key header, and error parsing that keeps requestId and branches on code. 3. Expose route handlers, not direct calls from components: one to create a customer with POST /customers, one to read status with GET /customers/{id}, one to start a deposit with POST /transactions/on-ramp, and one to read a transaction with GET /transactions/{id}. The browser talks only to these. 4. Handle the webhook in a route handler that reads the raw body. In the App Router that means awaiting request.text() and verifying over that exact string — calling request.json() first re-serializes the payload and every signature check fails with a message that looks like a wrong secret. 5. Render the payment details returned in state.payment on the server and pass only display values to the client. Its shape follows the rail, so take field names from the API reference examples for the rail you use and flag anything you cannot find there. 6. Generate the Idempotency-Key when the user starts a payment, persist it with your own order row, and reuse it if the request is retried. A key minted inside a retry is the same as no key. 7. Add the docs MCP server to the project so schema lookups do not come from memory: claude mcp add --transport http lumx-docs https://docs.lumx.io/mcp Constraints: - No Lumx call from a client component, a server action invoked with client-supplied amounts, or a middleware. Amounts and customer ids come from your own server-side state. - Do not cache a wallet address in the client or the database. The docs do not state that it is permanent; read it when you use it and flag the question. - Do not state a rate limit. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. Deliverables: - The shared server module, plus the four route handlers. - The webhook route with a raw-body test that fails if someone switches it to request.json(). - A .env.example listing the server-only variables, with no real values. ``` ## How to use 1. Tell the agent which Next.js version and whether you are on the App or Pages Router before it starts — the raw-body handling differs and this prompt assumes App Router. 2. Create the key and the webhook endpoint yourself; the tunnel URL has to exist before the handler can be tested. 3. Review the client-server boundary by hand at the end. It is the one thing in this prompt where a mistake is invisible until it is public. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `POST /customers` | Creates a customer from a route handler | | `GET /customers/{id}` | Reads customer status from a route handler | | `POST /transactions/on-ramp` | Starts a deposit from a route handler | | `GET /transactions/{id}` | Reads a transaction from a route handler | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `UNAUTHORIZED` | 401 | API key is missing, invalid, or inactive. | | `VALIDATION_ERROR` | 400 | Request body failed validation. Details in `validationErrors`. | | `KYC_NOT_APPROVED` | 403 | Customer identity verification (KYC/KYB) is not approved. | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | Same `Idempotency-Key` reused with a different body. | ## Related resources > [card] [Authentication](/get-started/authentication) > > API keys and the production IP allowlist. > [card] [Webhooks](/developer/webhooks) > > Event catalog, signatures, and retries. > [card] [Connect your agent to the Lumx docs MCP](/prompts/connect-lumx-docs-mcp) > > Connect your agent to these docs so it stops guessing field names. > [card] [Integrate on-ramps from fiat to stablecoin](/prompts/integrate-onramp) > > Take fiat in and deliver stablecoin to the customer's wallet. # Integrate off-ramps to local bank rails Source: https://docs.lumx.io/prompts/integrate-offramp A copy-paste prompt that builds Lumx payouts end to end — register a destination with the right holder relationship, quote, off-ramp, settle. Paste this prompt into your coding agent to pay out from a customer's stablecoin balance to a local bank account. The agent builds a destination for each rail you use, quotes the payout, sends the off-ramp, and tracks it to settlement. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * A customer in `APPROVED` status with a funded wallet. See [Create a customer](/guides/create-a-customer). * A webhook endpoint registered under **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io). See [Webhooks](/developer/webhooks). ## Prompt ```text Prompt theme={null} You are a senior backend engineer adding Lumx off-ramps — stablecoin out, fiat into a bank account — to an existing server-side application. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Every page has a .md twin at its own path. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. Take field names, required flags and enum values from here; the prose explains the compliance rules the spec cannot. Work against sandbox at https://api-sandbox.lumx.io with a server-side key sent as "Authorization: Bearer ". 1. Generate the destination branches from the spec, do not paraphrase them. POST /destinations is a oneOf with one branch per rail and a different required set on each: PIX takes keyType and keyValue; SPEI takes clabe; ACH and FEDWIRE take accountNumber, routingNumber and type; SWIFT requires bic with either iban or accountNumber; SEPA requires iban and bic; FPS requires sortCode and accountNumber. On SEPA and FPS the holder and bank addresses need only country. Every branch but PIX also requires a bank object — and one of them lists bank as required without defining it, so flag that instead of inventing a shape. 2. Set holder.relationship on every destination from the closed enum — SELF for the customer's own account, or one of the twelve third-party values such as SUPPLIER, EMPLOYEE, CREDITOR. This is a compliance field, not a label: pick the one that is true. 3. Create the destination and keep its id. Confirm with GET /destinations?customerId={id} and handle destinations.under_verification, destinations.approved and destinations.final_rejection. The verification status enums in the spec carry values with no matching event (TEMPORARY_REJECTION on most branches, REJECTED on FPS), so read status from the API and do not drive it from webhooks alone. 4. Quote with POST /exchange-rates when the payer needs a number before committing; type LOCKED returns an id and expiresAt, type FLOATING prices at execution. 5. Pay with POST /transactions/off-ramp using either destinationId + sourceCurrency + sourceAmount + purpose, or destinationId + exchangeRateId + purpose. Send an Idempotency-Key header with a UUID v4 you generated and persisted alongside the payout row before sending. Reuse the stored key on every retry; a retry that mints a new key pays twice. 6. Follow offramp.transferring_stablecoin, offramp.trading, offramp.transferring_fiat, offramp.success and offramp.failed, and confirm with GET /transactions/{id} before you mark the payout settled. Constraints: - Every payout debits the wallet of the customer that owns the destination. Do not design a pooled account that pays third parties who are not onboarded — read /compliance/nested-payments and stop if the design requires it. - PERSONAL_ACCOUNT as purpose is only valid with a SELF destination. Anything else returns 400 PERSONAL_ACCOUNT_RELATIONSHIP_REQUIRED. - Map errors by code, never by message: INSUFFICIENT_BALANCE, RAIL_NOT_ENABLED, HOLDER_TAX_ID_MISMATCH, DESTINATION_NOT_FOUND, EXCHANGE_RATE_EXPIRED. Treat an unknown code as retryable-unknown and surface it. - Do not state a rate limit. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. Deliverables: - A destination builder with one typed branch per rail and a test per branch. - A payout function that is idempotent on your own payout id. - A webhook handler for the five offramp.* events plus the three destinations.* events. - A note listing every enum value you took from the spec and every rule you took from the prose. ``` ## How to use 1. Onboard one sandbox customer to `APPROVED` and fund its wallet before running this — an off-ramp against an empty wallet fails on balance, not on your code. 2. Decide the holder relationship for your real payout flow yourself, with whoever owns compliance. The agent should not guess it. 3. Review the branch tests: a rail you do not use yet is the cheapest thing to delete now and the most expensive to guess later. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `POST /destinations` | Registers the bank account that receives the payout, one branch per rail | | `GET /destinations` | Reads the destination's verification status | | `POST /exchange-rates` | Locks a quote when the payer needs a number before committing | | `POST /transactions/off-ramp` | Sends the payout with an idempotency key | | `GET /transactions/{id}` | Confirms the final status before marking the payout settled | Each destination carries a `holder.relationship` that tells Lumx who owns the receiving account. See [Holder relationships](/concepts/destinations#holder-relationships) for the full list. ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `INSUFFICIENT_BALANCE` | 422 | Balance is too low for the operation. | | `RAIL_NOT_ENABLED` | 403 | Rail is not enabled for this project. | | `HOLDER_TAX_ID_MISMATCH` | 403 | `holderTaxId` differs from the customer tax ID on `SELF` destinations. | | `DESTINATION_NOT_FOUND` | 404 | Destination does not exist. | | `EXCHANGE_RATE_EXPIRED` | 422 | Locked rate has expired. | | `PERSONAL_ACCOUNT_RELATIONSHIP_REQUIRED` | 400 | Personal transactions require a `SELF` bank account. | ## Related resources > [card] [Destinations](/concepts/destinations) > > Rails, required fields, and holder relationships. > [card] [Nested payments](/compliance/nested-payments) > > Whose funds can move, and for whom. > [card] [Integrate on-ramps from fiat to stablecoin](/prompts/integrate-onramp) > > Take fiat in and deliver stablecoin to the customer's wallet. > [card] [Integrate webhooks with signature checks](/prompts/integrate-webhooks) > > Receive signed events that survive retries and secret rotation. # Integrate on-ramps from fiat to stablecoin Source: https://docs.lumx.io/prompts/integrate-onramp Give your coding agent the exact steps, endpoints and enums to build a Lumx on-ramp — quote, create, render payment details, settle on webhooks. Paste this prompt into your coding agent to build a complete fiat-to-stablecoin on-ramp against the Lumx sandbox. The agent creates the customer, quotes the conversion, creates the on-ramp, shows the payer where to send funds, and follows the transaction to settlement. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * A webhook endpoint registered under **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io). See [Webhooks](/developer/webhooks). ## Prompt ```text Prompt theme={null} You are a senior backend engineer adding Lumx on-ramps — fiat in, stablecoin out — to an existing server-side application. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Every page has a .md twin at its own path. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. Take field names, required flags and enum values from here; the prose explains what the spec leaves untyped. Work against sandbox at https://api-sandbox.lumx.io with a server-side key sent as "Authorization: Bearer ". The key never reaches a client bundle. 1. Create the customer with POST /customers. Request the fiat currencies you need in "accounts" (BRL, USD, EUR, MXN, GBP) — each virtual account is created with the customer and provisions after verification. 2. Wait for verification to reach APPROVED, then read GET /customers/{id} for the "wallets" and "accounts" arrays. In sandbox the last digit of taxId drives the outcome: 1 NOT_STARTED, 2 RFI, 3 FINAL_REJECTION, any other digit APPROVED. 3. Quote with POST /exchange-rates. type LOCKED returns an id and expiresAt under a timelock of 30s, 1m or 5m; type FLOATING prices at execution. Choose one and record why in the code. 4. Create the transaction with POST /transactions/on-ramp in one of the two shapes the spec defines: rail + sourceCurrency + sourceAmount + targetCurrency + purpose, or exchangeRateId + purpose. Send an Idempotency-Key header with a UUID v4 you persist before sending, so a retry reuses it. 5. Render state.payment from the response for the payer. Its shape follows the rail — brCode for PIX; an identifier plus holder for bank rails, such as iban and bic for SEPA or sortCode and accountNumber for FPS. The spec types it as a free-form object, so take field names from the examples on /api-reference/transactions/on-ramp and flag any field you cannot find there instead of inventing one. 6. Drive the lifecycle from webhooks: onramp.awaiting_funds, onramp.transferring_fiat, onramp.trading, onramp.transferring_stablecoin, onramp.success, onramp.failed, onramp.expired. Confirm with GET /transactions/{id} before you credit anything in your own product. Constraints: - Read the purpose enum from the request schema. The response schema in the spec lists different values; use the request enum and flag the mismatch. - PERSONAL_ACCOUNT is only valid with SELF-relationship destinations. - Do not cache a wallet address. The docs do not state that it is permanent — read it when you use it and flag the question. - Amounts are decimal strings, never numbers. - Do not state a rate limit. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. Deliverables: - A typed client for the five endpoints above. - A webhook handler covering the seven onramp.* events, idempotent on webhook-id. - A sandbox script that runs one on-ramp end to end and prints every status change. - A list of every field you could not confirm in either source. ``` ## How to use 1. Create a sandbox key in the Dashboard under Developers → API Keys and export it in your shell. The agent never creates the key. 2. Register a webhook endpoint under Developers → Webhooks, pointing at a tunnel to your machine, before you run the script. 3. Read the agent's list of unconfirmed fields and decide each one yourself — that list is where a wrong integration would start. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `POST /customers` | Creates the customer and requests its virtual accounts | | `GET /customers/{id}` | Reads verification status, wallets, and accounts | | `POST /exchange-rates` | Quotes the conversion, locked or floating | | `POST /transactions/on-ramp` | Creates the on-ramp and returns the payment details | | `GET /transactions/{id}` | Confirms the final status before crediting your product | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `KYC_NOT_APPROVED` | 403 | Customer identity verification (KYC/KYB) is not approved. | | `EXCHANGE_RATE_EXPIRED` | 422 | Locked rate has expired. | | `MINIMUM_AMOUNT_NOT_MET` | 422 | Amount is below the supported minimum for the currency. | | `TRANSACTION_LIMIT_EXCEEDED` | 422 | Exceeds the customer's single, daily, or monthly limit for that transaction type. | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | Same `Idempotency-Key` reused with a different body. | ## Related resources > [card] [Transactions](/concepts/transactions) > > Transaction types and status progressions. > [card] [Exchange rates](/concepts/exchange-rates) > > Locked and floating quotes. > [card] [Integrate off-ramps to local bank rails](/prompts/integrate-offramp) > > Pay out from a stablecoin balance to a local bank account. > [card] [Integrate webhooks with signature checks](/prompts/integrate-webhooks) > > Receive signed events that survive retries and secret rotation. # Integrate local-currency virtual accounts Source: https://docs.lumx.io/prompts/integrate-virtual-accounts A copy-paste prompt for named virtual accounts in your customer's own name — request the currencies, track provisioning, collect the deposit. Paste this prompt into your coding agent to collect in local currency through accounts issued in your customer's own name. The agent requests accounts with the customer, tracks each one through provisioning, and renders the deposit coordinates for every rail you collect on. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * A webhook endpoint registered under **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io). See [Webhooks](/developer/webhooks). ## Prompt ```text Prompt theme={null} You are a senior backend engineer adding Lumx virtual accounts to a B2B product that collects money in more than one country. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Every page has a .md twin at its own path. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. Take field names, required flags and enum values from here; the prose carries the lifecycle the spec does not describe. Work against sandbox at https://api-sandbox.lumx.io with a server-side key sent as "Authorization: Bearer ". 1. Request the accounts when you create the customer. POST /customers takes an "accounts" array of currencies from BRL, USD, EUR, MXN and GBP. There is no endpoint that creates an account on its own — check the spec and confirm this before designing any screen that implies one. 2. Model the account lifecycle as a state machine over the status enum in the spec: AWAITING_ONBOARDING, REQUESTED, PROVISIONING, RFI, ACTIVE, REJECTED, INACTIVE, CLOSED. The prose documents seven of these; take the set from the spec and flag the difference. 3. Drive the transitions from the account.* webhooks — account.awaiting_onboarding through account.active — and reconcile with GET /accounts?customerId={id}. Paginate with size and cursor; do not assume one page. 4. Expect BRL, MXN, EUR and GBP to reach ACTIVE immediately after the customer is APPROVED, and USD to take up to one business day. Your UI has to show a pending account, not a missing one. 5. Get the deposit coordinates. GET /accounts returns id, customerId, status, currency and timestamps — no bank details. The coordinates come from POST /autoconversion-rules, in sourceDepositInfo, for a standing conversion, or from POST /transactions/on-ramp, in state.payment, for a single expected deposit. Pick one and say which in the code. 6. Render the coordinates per rail: brCode for PIX, clabe plus reference for SPEI, accountNumber with routingNumber or bic for USD, iban plus bic for SEPA (EUR), sortCode plus accountNumber for FPS (GBP). Take every field name from the spec or from the examples in the API reference, and flag anything you cannot find in either. Constraints: - The account is issued in your customer's name, not yours. Do not design a flow that pools several parties' funds in one account — read /compliance/nested-payments first. - One currency per account. A customer holds several accounts, and the rails available on each follow the currency. - Do not state a rate limit while paginating. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. Deliverables: - A provisioning state machine driven by webhooks, with the RFI and REJECTED paths handled. - A collection screen that renders deposit coordinates per rail from live API data. - A sandbox script that creates a customer with BRL and USD accounts and prints each status change. - A list of every field you could not confirm in either source. ``` ## How to use 1. Decide which currencies your customers actually need before running this. Requesting all five creates five accounts you then have to explain to a compliance reviewer. 2. Create the sandbox key and register the webhook endpoint yourself; the agent stops at the first authenticated call without them. 3. Check the agent's choice in step 5 against how your product collects — a standing rule and a per-deposit transaction are different products, and it should not decide that for you. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `POST /customers` | Requests the accounts, one per currency, when the customer is created | | `GET /accounts` | Reads each account's status and reconciles the webhooks | | `POST /autoconversion-rules` | Returns deposit coordinates for a standing conversion | | `POST /transactions/on-ramp` | Returns deposit coordinates for one expected deposit | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `ACCOUNT_NOT_FOUND` | 404 | Account does not exist. | | `ACCOUNT_NOT_ACTIVE` | 409 | Account is not in a usable state. | | `ACCOUNT_ALREADY_EXISTS` | 409 | Customer already has an account in that currency. | | `KYC_NOT_APPROVED` | 403 | Customer identity verification (KYC/KYB) is not approved. | ## Related resources > [card] [Accounts](/concepts/accounts) > > Virtual accounts, provisioning, and status. > [card] [Coverage](/get-started/coverage) > > Supported currencies, rails, and stablecoin pairs. > [card] [Integrate customer onboarding and verification](/prompts/integrate-customer-onboarding) > > Take a business or individual from signup to an approved customer. > [card] [Integrate autoconversion rules](/prompts/integrate-autoconversion-rules) > > Convert every deposit to stablecoin with a standing rule. # Integrate webhooks with signature checks Source: https://docs.lumx.io/prompts/integrate-webhooks A copy-paste prompt that builds a Lumx webhook endpoint correctly — raw body, HMAC over the signed content, secret rotation, replay and retries. Paste this prompt into your coding agent to build a Lumx webhook endpoint that verifies signatures and survives retries. The agent builds an endpoint that verifies each signature over the raw body, survives secret rotation, deduplicates retries, and confirms state with the API before acting. ## Before you start * A webhook endpoint registered under **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io), with its signing secret in your own secret store. ## Prompt ```text Prompt theme={null} You are a senior backend engineer building the Lumx webhook receiver for an existing service. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/developer/webhooks — the signature scheme, the event catalog and the retry schedule. Also read https://docs.lumx.io/llms.txt for the rest of the documentation. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec, for the resource shapes that arrive inside the event payload. 1. Capture the raw request body before any JSON middleware parses it. Signature verification runs over bytes; a re-serialized body fails and the failure looks like a wrong secret. 2. Read the three headers: webhook-id, webhook-timestamp, webhook-signature. 3. Build the signed content as "{webhook-id}.{webhook-timestamp}.{body}", compute HMAC-SHA256 with the signing secret, and base64-encode the result. The secret arrives prefixed with whsec_ — strip the prefix and base64-decode the remainder before using it as the key. 4. Accept any valid signature in the header, not the first one. The header holds a space-delimited list of version-prefixed signatures, and during a secret rotation Lumx signs with the old and the new secret for 24 hours. Compare in constant time. 5. Reject anything older than your replay window using webhook-timestamp, and deduplicate on webhook-id — Lumx delivers up to eight times with increasing backoff, so the same event will arrive twice. 6. Return 2xx immediately, then process asynchronously. Route by eventType over the documented families: onramp.*, offramp.*, transfer.*, customer.*, customer.limit_request.*, account.* and destinations.*. 7. Treat the payload as a notification, not as state. Before you credit, release or settle anything, confirm with GET /transactions/{id} or GET /customers/{id}. Constraints: - Do not trust an event whose signature did not verify, including in sandbox. - Do not enumerate event types from memory. Take the list from the docs page and flag any type your code receives that is not on it. - The envelope is eventId, eventType and data. Anything else you need is inside data and its shape follows the resource. - Do not state a rate limit for the confirmation calls. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. Deliverables: - A verification function with unit tests covering a good signature, a tampered body, an expired timestamp and a rotation header with two signatures. - An idempotent dispatcher keyed on webhook-id. - A replay script that posts a stored event to the local endpoint so the handler can be tested without waiting for a transaction. ``` ## How to use 1. Register the endpoint in the Dashboard under Developers → Webhooks and copy the signing secret into your own secret store before running the agent. 2. If your infrastructure filters inbound IPs, take the published list from the webhooks page and add it yourself. 3. Run the rotation test after the agent finishes: rotate the secret in the Dashboard and confirm nothing drops during the 24-hour overlap. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `GET /transactions/{id}` | Confirms a transaction's state before acting on an event | | `GET /customers/{id}` | Confirms a customer's state before acting on an event | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `TRANSACTION_NOT_FOUND` | 404 | Transaction does not exist. | | `CUSTOMER_NOT_FOUND` | 404 | Customer does not exist. | | `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded. | ## Related resources > [card] [Webhooks](/developer/webhooks) > > Event catalog, signatures, and retries. > [card] [Idempotency](/developer/idempotency) > > Safe retries with Idempotency-Key. > [card] [Integrate on-ramps from fiat to stablecoin](/prompts/integrate-onramp) > > Take fiat in and deliver stablecoin to the customer's wallet. > [card] [Integrate off-ramps to local bank rails](/prompts/integrate-offramp) > > Pay out from a stablecoin balance to a local bank account. # Migrate from one PSP per country Source: https://docs.lumx.io/prompts/migrate-from-one-psp-per-country A copy-paste prompt for teams running a different provider per country — collapse the clients, the reconciliations and the webhook formats into one. Paste this prompt into your coding agent if you run a different payment provider in each country you operate in. The agent inventories your providers by country, maps them onto customers, accounts, and destinations, and plans a country-by-country cutover behind a flag. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * The list of payment providers you use in each country, with their contract end dates. ## Prompt ```text Prompt theme={null} You are a senior backend engineer collapsing several country-specific payment integrations into one Lumx integration. The problem is not any single provider: it is that every country has its own client, its own webhook format, its own reconciliation and its own contract. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Read /get-started/coverage and /additional-information/sla-and-cutoffs before designing anything. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. The rail and currency enums in the spec define what can be consolidated today. 1. Inventory before code: one row per country, with the current provider, the rail it settles on, the fields it needs, and its reconciliation format. Then mark each row as covered by the spec's rail enum, or not covered. A country with no matching rail stays where it is, and saying so is part of the deliverable. 2. Map the concepts once, not per country. Each current provider's merchant account becomes one POST /customers with the currencies it needs in the accounts array; each payout target becomes a destination with a holder.relationship from the closed enum; each payment becomes an on-ramp, off-ramp or transfer. 3. Collapse the collection side. A customer can hold several accounts, one per currency, and each account exposes every rail its banking partner offers for that currency. Read GET /accounts?customerId={id} and confirm against the spec's currency enum rather than assuming your country list maps one to one. 4. Collapse the payout side. Build destinations with POST /destinations, generating each branch from the spec, then pay with POST /transactions/off-ramp. One client, one idempotency scheme, one error envelope. 5. Collapse the eventing. Replace every provider-specific webhook parser with one handler over the documented event catalog, verified once, deduplicated on webhook-id. 6. Collapse the reconciliation. GET /transactions with size and cursor is now the single ledger source. Use the per-rail cut-off table, not one global timezone, to decide when a payment is late instead of failed, and read the rail from the destination — an off-ramp transaction does not carry it. 7. Cut over one country at a time behind a flag, keeping the incumbent live for that country until a full cycle reconciles clean. Constraints: - Only rails present in the spec's enum may appear in the plan. The coverage page badges some rails and currencies for a future quarter. A rail name in the enum does not make a badged country or currency available — SWIFT in EUR is badged even though the enum accepts it — so treat those as not yet consolidatable and flag them. - Do not state a rate limit for the migration backfill. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff and keep the backfill resumable. - Read /compliance/nested-payments before consolidating anything: every flow must stay the onboarded customer's own money. Deliverables: - The country inventory, with the not-covered rows named. - One client, one webhook handler, one reconciliation query replacing the per-country versions. - A per-country cutover order with the flag and the rollback. ``` ## How to use 1. Bring the contract end dates with you. The technical cutover order and the commercial one rarely match, and the flag has to survive whichever comes first. 2. Decide yourself what happens to countries the agent marks as not covered — running one provider for one country is a legitimate outcome. 3. Do not decommission anything until the reconciliation for that country is quiet for a full cycle. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `POST /customers` | Replaces each provider's merchant account | | `GET /accounts` | Reads the accounts and rails available per currency | | `POST /destinations` | Rebuilds each payout target | | `POST /transactions/off-ramp` | Sends payouts through one client | | `GET /transactions` | Becomes the single ledger source | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `RAIL_NOT_ENABLED` | 403 | Rail is not enabled for this project. | | `ACCOUNT_NOT_ACTIVE` | 409 | Account is not in a usable state. | | `INSUFFICIENT_BALANCE` | 422 | Balance is too low for the operation. | | `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded. | ## Related resources > [card] [Coverage](/get-started/coverage) > > Supported currencies, rails, and stablecoin pairs. > [card] [Accounts](/concepts/accounts) > > Virtual accounts, provisioning, and status. > [card] [Migrate manual bank payouts to the Lumx API](/prompts/migrate-from-spreadsheet-payouts) > > Replace a spreadsheet and a bank portal with API payouts. > [card] [Migrate SWIFT wires to local payout rails](/prompts/migrate-from-swift-wires) > > Move international wires onto local payout rails. # Migrate manual bank payouts to the Lumx API Source: https://docs.lumx.io/prompts/migrate-from-spreadsheet-payouts A copy-paste prompt that turns a spreadsheet and a bank portal into an API payout run — beneficiaries become destinations, with no double payment. Paste this prompt into your coding agent if payouts today mean a spreadsheet, a bank portal and someone typing. The agent maps your spreadsheet columns to destinations, migrates beneficiaries, and runs the payouts as a resumable batch alongside the manual process for one cycle. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * A redacted CSV export of your current payout spreadsheet. ## Prompt ```text Prompt theme={null} You are a senior backend engineer replacing a manual payout process — a spreadsheet of beneficiaries and a human in a bank portal — with the Lumx API, without paying anyone twice during the cutover. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Every page has a .md twin at its own path. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. Take field names, required flags and enum values from here. Work against sandbox at https://api-sandbox.lumx.io with a server-side key sent as "Authorization: Bearer ". Do not touch production in this task. 1. Read the current spreadsheet and produce a mapping table before writing code: one row per column, with the destination field it maps to, or "no equivalent". Flag every unmapped column instead of guessing — that table is the deliverable a human reviews. 2. Onboard the paying entity with POST /customers and take it to APPROVED. Payouts debit that customer's own wallet; there is no pooled balance. 3. Convert each beneficiary to a destination with POST /destinations. The body is a union, one branch per rail, so the required identifier differs: PIX takes keyType and keyValue; SPEI takes clabe; ACH and FEDWIRE take type, accountNumber and routingNumber with a bank object; SWIFT takes bic plus iban or accountNumber with a bank object; SEPA takes iban and bic with a bank object; FPS takes sortCode and accountNumber with a bank object. 4. Set holder.relationship per beneficiary from the closed enum — SUPPLIER, EMPLOYEE, CREDITOR, CUSTOMER and the rest, or SELF for the company's own account. A spreadsheet rarely records this. Leave a row unmigrated and report it rather than picking a value. 5. Pay with POST /transactions/off-ramp, one call per beneficiary, each carrying an Idempotency-Key that is a UUID v4 generated once per row and written to the run record before the call. Rerunning after a crash reads the stored key back; minting a new one pays twice. The same key with a different body returns 409. 6. Walk the run sequentially and paginate reads with size and cursor. Collect failures into an exception list keyed on the error code — INSUFFICIENT_BALANCE, RAIL_NOT_ENABLED, HOLDER_TAX_ID_MISMATCH, DESTINATION_NOT_FOUND — never on the message, which changes. Confirm each settled payout with GET /transactions/{id}. 7. Run both processes for one cycle: the manual run stays the source of truth while the API run writes to a shadow ledger, and reconcile the two on amount, beneficiary and date before you cut over. Constraints: - Do not state a rate limit for the batch. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff and keep the run resumable. - Every payout must be your own obligation to your own counterparty. Read /compliance/nested-payments and stop if the spreadsheet contains someone else's beneficiaries. - purpose comes from the request schema enum, and PERSONAL_ACCOUNT is only valid with a SELF destination. Deliverables: - The column mapping table, including the unmapped columns. - A resumable batch runner with deterministic idempotency keys. - An exception report grouped by error code. - A reconciliation script comparing the shadow run against the manual one. ``` ## How to use 1. Export the current spreadsheet to CSV and hand the agent a redacted copy — real beneficiary data does not need to be in the prompt for the mapping to work. 2. Decide the holder relationship for every row the agent leaves unmigrated. This is the step that cannot be automated and the one that blocks a real cutover. 3. Keep the manual process running for the first full cycle. Cut over only after the reconciliation script is quiet. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `POST /customers` | Onboards the entity that pays | | `POST /destinations` | Converts each beneficiary to a destination | | `POST /transactions/off-ramp` | Pays each beneficiary with a stored idempotency key | | `GET /transactions/{id}` | Confirms each settled payout | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `INSUFFICIENT_BALANCE` | 422 | Balance is too low for the operation. | | `RAIL_NOT_ENABLED` | 403 | Rail is not enabled for this project. | | `HOLDER_TAX_ID_MISMATCH` | 403 | `holderTaxId` differs from the customer tax ID on `SELF` destinations. | | `DESTINATION_NOT_FOUND` | 404 | Destination does not exist. | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | Same `Idempotency-Key` reused with a different body. | ## Related resources > [card] [Destinations](/concepts/destinations) > > Rails, required fields, and holder relationships. > [card] [Idempotency](/developer/idempotency) > > Safe retries with Idempotency-Key. > [card] [Migrate SWIFT wires to local payout rails](/prompts/migrate-from-swift-wires) > > Move international wires onto local payout rails. > [card] [Integrate off-ramps to local bank rails](/prompts/integrate-offramp) > > Pay out from a stablecoin balance to a local bank account. # Migrate SWIFT wires to local payout rails Source: https://docs.lumx.io/prompts/migrate-from-swift-wires A copy-paste prompt that maps each SWIFT corridor to the local rail Lumx settles on, rebuilds beneficiaries as destinations and cuts over safely. Paste this prompt into your coding agent if you pay suppliers or staff abroad by international wire today. The agent builds a corridor table, rebuilds beneficiaries as destinations, and moves each corridor to a local rail where one exists, with wires as the fallback. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * Your beneficiary list with country, currency, and the bank identifiers you hold. ## Prompt ```text Prompt theme={null} You are a senior backend engineer moving an existing international wire flow — MT103 instructions, correspondent banks, IBAN and BIC on file — onto Lumx, settling on a local rail wherever one exists. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Every page has a .md twin at its own path; /get-started/coverage carries the rails and settlement times. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. The rail enum in the spec is the list you are allowed to build against. Work against sandbox at https://api-sandbox.lumx.io with a server-side key sent as "Authorization: Bearer ". 1. Build the corridor table first, from the spec's rail enum and the coverage page: which of your destination countries settles on a local rail, which stays on SWIFT, and what each one costs in settlement time. This table is the decision, and it is what a human reviews before any code ships. 2. Rebuild each beneficiary as a destination with POST /destinations. A wire record maps onto the SWIFT branch — bic plus iban or accountNumber, with a bank object — and for EUR onto SEPA, which takes iban and bic. It does not map onto PIX, SPEI or FPS, which need a local identifier you do not have on file. Mark those rows as needing collection from the beneficiary. 3. Set holder.relationship from the closed enum on every destination. A wire instruction does not carry it, so it has to come from your own records. 4. Quote with POST /exchange-rates when the payer compares the wire cost against the local rail; type LOCKED returns an id and an expiresAt you must respect. 5. Pay with POST /transactions/off-ramp, sending an Idempotency-Key that is a UUID v4 stored against your payment reference before the call, so a retry reuses it and cannot duplicate a payment a wire system would have sent once. 6. Replace wire-confirmation polling with the offramp.* webhook events, and confirm with GET /transactions/{id} before marking a supplier paid. There is no MT103 to reconcile against; the transaction receipt is the record. 7. Cut over one corridor at a time behind a feature flag, keeping wires available as the fallback until a full cycle reconciles. Constraints: - Do not put a rail in your mapping that is not in the spec's rail enum. The coverage page also lists rails badged for a future quarter; those are not available to build against today, and naming one in code is the failure mode this task exists to avoid. - Do not state a rate limit while iterating beneficiaries. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. - Match errors on code, never on message. Deliverables: - The corridor table: country, currency, rail, settlement time, and the beneficiary fields still missing. - A destination builder per rail branch, with the unmigratable rows listed rather than filled in. - A flagged payout path that runs local-rail and wire side by side for one cycle. ``` ## How to use 1. Export your current beneficiary list with country, currency and the identifiers you hold. The corridor table is only as good as that input. 2. Collect the missing local identifiers from beneficiaries yourself — a PIX key or a CLABE is not derivable from an IBAN, and the agent will correctly refuse to invent one. 3. Pick the first corridor to cut over from the table, not from volume. The one with the cleanest beneficiary data is the one that proves the path. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `POST /destinations` | Rebuilds each beneficiary as a destination | | `POST /exchange-rates` | Quotes the local-rail payout against the wire cost | | `POST /transactions/off-ramp` | Sends the payout | | `GET /transactions/{id}` | Confirms the payout before marking a supplier paid | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `RAIL_NOT_ENABLED` | 403 | Rail is not enabled for this project. | | `EXCHANGE_RATE_EXPIRED` | 422 | Locked rate has expired. | | `INVALID_PAYMENT_DESTINATION` | 400 | Payment destination is invalid. | | `INSUFFICIENT_BALANCE` | 422 | Balance is too low for the operation. | ## Related resources > [card] [Coverage](/get-started/coverage) > > Supported currencies, rails, and stablecoin pairs. > [card] [Payment rail cut-off times](/additional-information/sla-and-cutoffs) > > Cut-offs and settlement times per rail. > [card] [Migrate manual bank payouts to the Lumx API](/prompts/migrate-from-spreadsheet-payouts) > > Replace a spreadsheet and a bank portal with API payouts. > [card] [Integrate off-ramps to local bank rails](/prompts/integrate-offramp) > > Pay out from a stablecoin balance to a local bank account. # Prompts for coding agents Source: https://docs.lumx.io/prompts/overview Copy-paste prompts that give your coding agent the exact Lumx endpoints, enums, and constraints for onboarding, payouts, webhooks, migrations, and go-live. Each prompt gives your coding agent the endpoints, enum values, and constraints for one Lumx task, so it builds against the API instead of guessing. Paste a prompt into Claude Code, Cursor, Codex, or any agent that can read a URL, and it reads these docs and the [OpenAPI spec](/developer/openapi) before writing code. Every prompt follows the same shape: the sources to read first, numbered steps with literal endpoints, constraints, and the deliverables to expect. No prompt creates an API key, so create your Sandbox key before you start. See [Authentication](/get-started/authentication). > [!TIP] > > Connect the [docs MCP server](/developer/mcp-server) before you paste a prompt. The agent can then look up any field it is unsure of instead of relying on memory. ## Setup and testing Get to a working Sandbox integration, then harden it for production. * [**Lumx sandbox quickstart**](/prompts/sandbox-quickstart) — Prove a deposit-to-stablecoin round trip in Sandbox. * [**Connect your agent to the Lumx docs MCP**](/prompts/connect-lumx-docs-mcp) — Connect your agent to these docs so it stops guessing field names. * [**Set up partner fees and revenue share**](/prompts/setup-partner-fees) — Earn a fee on every transaction you route. * [**Debug Lumx webhook delivery**](/prompts/debug-webhooks) — Find out why webhooks don't arrive or fail verification. * [**Production readiness checklist**](/prompts/sandbox-to-production-checklist) — Audit a working Sandbox integration before real money moves. * [**Audit an existing Lumx integration**](/prompts/audit-lumx-integration) — Find what costs money or fails compliance in a live integration. ## End-to-end integrations Build one Lumx capability from the first call to settlement. * [**Integrate customer onboarding and verification**](/prompts/integrate-customer-onboarding) — Take a business or individual from signup to an approved customer. * [**Integrate local-currency virtual accounts**](/prompts/integrate-virtual-accounts) — Collect in local currency through accounts in your customer's name. * [**Integrate on-ramps from fiat to stablecoin**](/prompts/integrate-onramp) — Take fiat in and deliver stablecoin to the customer's wallet. * [**Integrate off-ramps to local bank rails**](/prompts/integrate-offramp) — Pay out from a stablecoin balance to a local bank account. * [**Integrate autoconversion rules**](/prompts/integrate-autoconversion-rules) — Convert every deposit to stablecoin with a standing rule. * [**Integrate webhooks with signature checks**](/prompts/integrate-webhooks) — Receive signed events that survive retries and secret rotation. * [**Integrate Lumx into a Next.js app**](/prompts/integrate-lumx-nextjs) — Add Lumx to a Next.js App Router project without leaking the key. ## Build a product Combine several capabilities into a complete payment product. * [**Build the BRL corridor end to end**](/prompts/integrate-brl-corridor) — Build BRL in over PIX and out again as working code. * [**Build global contractor payouts**](/prompts/build-contractor-payouts) — Pay contractors in several countries from one funded balance. * [**Build marketplace seller payouts**](/prompts/build-marketplace-payouts) — Pay marketplace sellers and take a platform fee on every sale. * [**Build a Lumx reconciliation ledger**](/prompts/build-reconciliation-ledger) — Answer which transactions settled without opening a dashboard. ## Migrate from an existing setup Move a payment flow you already run onto Lumx. * [**Migrate manual bank payouts to the Lumx API**](/prompts/migrate-from-spreadsheet-payouts) — Replace a spreadsheet and a bank portal with API payouts. * [**Migrate SWIFT wires to local payout rails**](/prompts/migrate-from-swift-wires) — Move international wires onto local payout rails. * [**Migrate from one PSP per country**](/prompts/migrate-from-one-psp-per-country) — Collapse one payment provider per country into one integration. ## Related resources > [card] [Ship with AI](/guides/build-with-ai/overview) > > Builder guides, the docs MCP, and the API. > [card] [MCP server](/developer/mcp-server) > > Connect AI tools to these docs. # Lumx sandbox quickstart Source: https://docs.lumx.io/prompts/sandbox-quickstart A copy-paste prompt that takes your agent from an empty project to a full converted deposit in the Lumx sandbox, without a real bank transfer. Paste this prompt into your coding agent to prove a full deposit-to-stablecoin round trip in sandbox before you write any product code. The agent creates an approved customer with a BRL account, sets up an autoconversion rule, simulates a deposit, and follows the conversion to a receipt. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). ## Prompt ```text Prompt theme={null} You are a senior backend engineer proving that a Lumx integration works end to end in sandbox, before any product code exists. The goal is one completed conversion, not a feature. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Every page has a .md twin at its own path. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. Take field names, required flags and enum values from here. Sandbox is https://api-sandbox.lumx.io and it runs test stablecoins on testnet against mock banking partners, so no real money moves. Production is https://api.lumx.io. Never point this script at production. 1. Confirm the key works with one authenticated read: GET /customers. A 401 here means the key or the environment is wrong, and nothing below will work. 2. Create a customer with POST /customers, requesting accounts: ["BRL"]. Choose a taxId whose last digit is not 1, 2 or 3 — in sandbox that digit is a sentinel and any other digit simulates APPROVED. 3. Poll GET /customers/{id} until verification.status is APPROVED, then read the wallets array from the same response. Wallets are omitted from the create response by design. 4. Poll GET /accounts?customerId={id} until the BRL account reaches ACTIVE. BRL provisions immediately once the customer is APPROVED. 5. Create a standing rule with POST /autoconversion-rules for that accountId, targetCurrency USDC, a purpose from the request enum, and a name. Keep sourceDepositInfo[0].depositIdentifier from the response. 6. Fire the deposit with POST /autoconversion-rules/simulate-deposit, passing accountId, that depositIdentifier as reference, and an amount worth at least 50 USD in BRL. Assert matchedRule is true. 7. Watch the conversion: the matched deposit runs as an on-ramp, so follow onramp.transferring_fiat through onramp.success, then read GET /transactions/{id} and print the receipt. 8. Re-run the whole script from scratch and prove it is repeatable. Constraints: - Repeat steps 2 and 3 with taxId ending in 2 and in 3 and assert you get RFI and FINAL_REJECTION. Those are the branches a real integration hits. - There is no documented sandbox sentinel that forces a transaction to fail, only verification outcomes. Do not fabricate one and do not simulate failure by mangling a request — report the gap. - simulate-deposit exists only in sandbox and returns 404 in production. Keep it out of any shared code path. - Do not state a rate limit while polling. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. Deliverables: - One script that runs the whole path and exits non-zero on any failed assertion. - The three verification outcomes asserted from the sentinels. - A short log of what each step returned, so a human can see where it stopped. ``` ## How to use 1. Create the sandbox key in the Dashboard under Developers → API Keys. New accounts start in sandbox; production access is a conversation with the Lumx team, not a setting. 2. Register a webhook endpoint pointing at a tunnel before step 7, or the script can only poll. 3. Keep the script. It becomes the smoke test you run before every release, and the thing you hand support when something breaks. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `GET /customers` | Confirms the API key works | | `POST /customers` | Creates a Sandbox customer with a BRL account | | `GET /customers/{id}` | Waits for approval and reads the wallets | | `GET /accounts` | Waits for the BRL account to become active | | `POST /autoconversion-rules` | Creates the rule that converts the deposit | | `POST /autoconversion-rules/simulate-deposit` | Simulates the deposit | | `GET /transactions/{id}` | Reads the conversion's receipt | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `UNAUTHORIZED` | 401 | API key is missing, invalid, or inactive. | | `VALIDATION_ERROR` | 400 | Request body failed validation. Details in `validationErrors`. | | `ACCOUNT_NOT_ACTIVE` | 409 | Account is not in a usable state. | | `MINIMUM_AMOUNT_NOT_MET` | 422 | Amount is below the supported minimum for the currency. | ## Related resources > [card] [Environments](/get-started/environments) > > Sandbox and production base URLs. > [card] [Identity verification](/compliance/identity-verification) > > KYC, KYB, and sandbox outcomes. > [card] [Production readiness checklist](/prompts/sandbox-to-production-checklist) > > Audit a working Sandbox integration before real money moves. > [card] [Integrate autoconversion rules](/prompts/integrate-autoconversion-rules) > > Convert every deposit to stablecoin with a standing rule. # Production readiness checklist Source: https://docs.lumx.io/prompts/sandbox-to-production-checklist A copy-paste prompt that audits a working Lumx sandbox integration against everything that changes in production, before the first real payment. Paste this prompt into your coding agent when the integration works in sandbox and the next payment would be real money. The agent audits a working Sandbox integration for what changes in production: hosts, keys, the IP allowlist, idempotency, webhook hardening, error handling, and pagination. ## Before you start * An integration that already works end to end in Sandbox. ## Prompt ```text Prompt theme={null} You are a senior engineer auditing a working Lumx sandbox integration for the move to production. You are looking for what breaks when the environment changes, not for bugs in business logic. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/llms.txt — the documentation index. Every page has a .md twin at its own path. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. Note its servers block lists the sandbox host only; the production base URL comes from the prose. Audit the repository as it stands. Produce findings, not refactors. 1. Find every hardcoded host. Sandbox is https://api-sandbox.lumx.io and production is https://api.lumx.io. Both must come from configuration, and the sandbox-only simulate-deposit call must be unreachable in the production path — it returns 404 there. 2. Trace the API key. It must be server-side only, never in a client bundle, never in version control, and read from a secret store. Production keys are issued per project and shown once. 3. Check the IP allowlist story. The allowlist applies to production only; sandbox accepts any IP. Read /get-started/authentication and report what it says about enforcement today, including whether the date it gives has already passed — do not assume either way, and do not treat a grace period as permanent. 4. Verify idempotency coverage. Every POST, PUT and PATCH that moves money or creates a customer needs an Idempotency-Key that is generated once, persisted before the call, and reused on retry. Keys expire after 24 hours; the same key with a different body returns 409. 5. Verify webhook hardening: raw-body signature check, the whsec_ prefix stripped before decoding, acceptance of any valid signature in the header so a secret rotation does not drop events, a replay window on webhook-timestamp, and deduplication on webhook-id. 6. Check error handling against the documented envelope. Every branch must match on code, never on message, and an unknown code must degrade gracefully rather than throw. On 5xx the message is always generic and only the code is specific. 7. Prove the read path against real data volumes: GET /transactions, GET /accounts and every other list call need size and cursor pagination, and no code may assume a single page. Confirm the same for GET /customers/{id} reads that drive a screen. Constraints: - Do not state a rate limit. None is published and the spec declares no 429 response. Report that as an open question and make sure retries back off. - Flag anything you cannot verify from the two sources as a question for Lumx, not as a pass. Deliverables: - A checklist with pass, fail or unverifiable per item, each citing the file and line. - The list of questions for Lumx, with what each one blocks. - A go-live order: what must be fixed before the first real payment and what can follow. ``` ## How to use 1. Run this against the branch you intend to ship, not against main. 2. Take the questions list to Lumx before go-live. The allowlist status and the rate limit are the two that a code review cannot settle. 3. Fix the fail items yourself. This prompt is deliberately an auditor, not a refactorer — you do not want an agent rewriting money paths unsupervised. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `GET /customers/{id}` | Checks the reads that drive your screens | | `GET /accounts` | Checks pagination on account reads | | `GET /transactions` | Checks pagination on transaction reads | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `UNAUTHORIZED` | 401 | API key is missing, invalid, or inactive. | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | Same `Idempotency-Key` reused with a different body. | | `IDEMPOTENCY_KEY_IN_FLIGHT` | 409 | A request with that key is still processing. | | `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded. | ## Related resources > [card] [Environments](/get-started/environments) > > Sandbox and production base URLs. > [card] [Authentication](/get-started/authentication) > > API keys and the production IP allowlist. > [card] [Lumx sandbox quickstart](/prompts/sandbox-quickstart) > > Prove a deposit-to-stablecoin round trip in Sandbox. > [card] [Audit an existing Lumx integration](/prompts/audit-lumx-integration) > > Find what costs money or fails compliance in a live integration. # Set up partner fees and revenue share Source: https://docs.lumx.io/prompts/setup-partner-fees A copy-paste prompt that configures a Lumx partner fee, applies it correctly on locked and floating rates, and proves collection in sandbox first. Paste this prompt into your coding agent to take a cut of every transaction you route through Lumx. The agent creates the fee, applies it to locked and floating rates, shows customers the final numbers, and reconciles what was collected in Sandbox. ## Before you start * A Sandbox API key. See [Authentication](/get-started/authentication). * A wallet you control to receive the fees. ## Prompt ```text Prompt theme={null} You are a senior backend engineer configuring Lumx partner fees so the platform earns on each transaction it routes, and wiring them into the existing payment paths. Ground truth. Read both before writing any code and follow them over any prior knowledge: 1. https://docs.lumx.io/concepts/partner-fees — what the fee is, how it is applied and where it is paid. Also read https://docs.lumx.io/llms.txt for the documentation index. 2. https://lumx-docs-public-prod.s3.us-east-1.amazonaws.com/api-production.yaml — the OpenAPI 3.1 spec. The request body is smaller than the prose suggests; build from the spec. Work against sandbox at https://api-sandbox.lumx.io with a server-side key. 1. Read the PartnerFeeRequest schema in the spec before writing the payload. It takes exactly name, walletAddress and fees, with fees.onRamp and fees.offRamp each holding rate and flatAmount. Send nothing else. If the prose mentions a field the schema does not define, flag it and leave it out. 2. Decide the wallet that receives the fees. It can be your own treasury address or a wallet Lumx provisions by onboarding your own entity as a customer. Supported networks are EVM chains, Tron and Stellar. 3. Create the fee with POST /partner-fees. rate is in basis points, so 75 bps is 0.75 percent; flatAmount is a fixed amount per transaction. Keep the returned id — every transaction references it. 4. Apply it in the right place, which depends on the rate type: with a locked rate, pass partnerFeeId on POST /exchange-rates so the fee is inside the quote the customer sees; with a floating rate, pass it on the transaction instead. 5. Read the locked-rate response and show your customer the real numbers. It breaks fees into the Lumx share, the partner share and the total, and gives baseTargetAmount, finalTargetAmount and finalExchangeRate. Display the final values, not the base ones. 6. Verify collection in sandbox: run one POST /transactions/on-ramp with the fee applied, then read GET /partner-fees/{id} and the transaction receipt and reconcile what was charged against what you configured. Constraints: - Do not assume a default-fee flag exists in the API. The prose describes marking a fee as default; the request and response schemas in the spec do not carry that field. Pass partnerFeeId explicitly on every call and report the gap. - The request schema gives the unit for flatAmount only on the off-ramp side, while the response schema and the prose state dollars for both. Treat flatAmount as dollars on both sides and verify with one sandbox transaction before charging. - Do not state a rate limit while listing fees with GET /partner-fees. None is published. Handle 429 TOO_MANY_REQUESTS with exponential backoff. Deliverables: - The fee configuration, with the bps and flat values written down in the code as a decision. - Both application paths wired, locked and floating, with a test each. - A sandbox reconciliation showing configured versus collected. ``` ## How to use 1. Decide the rate with whoever owns pricing before running this. It is the one input the agent cannot infer, and it ends up in every quote. 2. Use a wallet you control on a network you can actually monitor — fees land there and nowhere else. 3. Check the two flagged gaps in the agent's report with Lumx before go-live, especially the missing default-fee field if your design assumed it. ## What the prompt builds | **Endpoint** | **What the agent uses it for** | | :- | :- | | `POST /partner-fees` | Creates the fee | | `GET /partner-fees` | Lists your fees | | `GET /partner-fees/{id}` | Reads a fee to reconcile what was charged | | `POST /exchange-rates` | Applies the fee inside a locked quote | | `POST /transactions/on-ramp` | Applies the fee on a floating-rate transaction | ## Errors to expect These codes come from the [errors catalog](/developer/errors), for the resources this prompt uses. Match on `code`, not on `message`. | **Code** | **Status** | **When it happens** | | :- | :- | :- | | `PARTNER_FEE_NOT_FOUND` | 404 | Partner fee does not exist. | | `INVALID_WALLET_ADDRESS` | 400 | Partner wallet address is invalid for the blockchain. | | `EXCHANGE_RATE_EXPIRED` | 422 | Locked rate has expired. | ## Related resources > [card] [Partner fees](/concepts/partner-fees) > > Fee structure and when fees apply. > [card] [Exchange rates](/concepts/exchange-rates) > > Locked and floating quotes. > [card] [Build marketplace seller payouts](/prompts/build-marketplace-payouts) > > Pay marketplace sellers and take a platform fee on every sale. > [card] [Integrate on-ramps from fiat to stablecoin](/prompts/integrate-onramp) > > Take fiat in and deliver stablecoin to the customer's wallet.