# Company Types Source: https://docs.lumx.io/additional-information/company-types Business categories collected on the companyType field during KYB onboarding When you onboard a business customer, Lumx collects a `companyType` value that classifies the entity for AML risk profiling. It describes the regulated category the business falls under (MSB, 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 | **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). | | **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. | | **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. | | **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. | | **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. | Several of the values above trigger Enhanced Due Diligence rather than rejection — see [High-risk business activities](/compliance/prohibited-activities#high-risk-business-activities). The categories that map directly to [Prohibited business activities](/compliance/prohibited-activities#prohibited-business-activities) — including `BANKING_CORRESPONDENT`, `GOVERNMENT_AGENCY_US`, `GOVERNMENT_AGENCY_FOREIGN`, and `CHARITY_NGO_NON_PROFIT` — 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. ## Related resources Full KYB onboarding flow. Categories Lumx does not service. 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. | 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. Uploads are capped at 50 MB. Accepted file formats: JPG, PNG, and PDF. ## Related resources KYC and KYB requirements per customer type. Step-by-step KYC integration. Step-by-step KYB integration. 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 enum values each field accepts. 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). ## Common to all customers ### Jurisdictions Regions where transactions are expected to occur. Array. Submit every region that applies. | **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. | This field is separate from the customer's country of residence/incorporation. For the country-level Supported / EDD / Restricted classification, see [Supported countries](/compliance/supported-countries). ### Involved activities Self-disclosure of involvement with high-risk activities. Array. Must include `NONE` if none apply. | **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. | Disclosing any value other than `NONE` does not pre-approve the activity. See [Prohibited business activities](/compliance/prohibited-activities) for the categories Lumx does not service. ### Transaction counterparties Who the customer expects to transact with. 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). | This drives which destination [holder relationships](/concepts/destinations#holder-relationships) Lumx expects to see on payouts. ### Source of funds 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. | — | When more than one source applies, pick the primary one. Compliance may request supporting evidence regardless. See [Document Types](/additional-information/document-types). ## Individuals only ### Professional situation | **Code** | **Description** | | :-------------- | :-------------------------------- | | `EMPLOYEE` | Salaried employee. | | `SELF_EMPLOYED` | Self-employed or sole proprietor. | | `UNEMPLOYED` | Currently unemployed. | | `RETIRED` | Retired. | | `OTHER` | Doesn't fit the categories above. | The free-text `professionalOccupation` field accompanies this to capture the specific role (e.g., "Software Engineer"). ## Businesses only Businesses also disclose their company type. See [Company Types](/additional-information/company-types) for the accepted values. ## Related resources Business categorization for `companyType` on KYB. Jurisdiction tiers and EDD scope. Activity categories Lumx does not service. 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. | `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 Full transaction lifecycle and request schemas. Holder relationships that pair with each purpose code. Worked example of purpose codes across an inter-company flow. 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 | | SWIFT | 🌐 International | Customer | 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 | 2:00 PM CET/CEST (≥ €100k only) | Less than €100k: Instant; ≥ €100k: T+1 | \< €100k: 24/7/365; ≥ €100k: business days | | 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 | 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. | ## Related resources Supported currencies, rails, and countries. Transaction lifecycle and statuses. Subscribe to status changes for transactions. 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. `passport`, `national_id`, and `other` are valid fallback options for every country except the United States. Use the country-specific identifier when available. 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 Data requirements and verification statuses per customer type. Jurisdiction tiers and EDD scope. Self-disclosure enums collected at onboarding. 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. # 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. # 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. # 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 ### 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. Subscribe to the new `account.*` events on the dashboard's webhook settings to receive the new lifecycle notifications. ### 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" } ``` ### 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. ### 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" } ``` ### 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. ### 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" } ``` ### 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" } ``` ### 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"] } ``` ### 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`. 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). ### Bank accounts renamed to destinations 📦 **Deprecated:** 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`. 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. ### `TEMPORARY_REJECTION` renamed to `RFI` ✏️ **Deprecated:** 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. Update your subscriptions on the [dashboard](https://dashboard.lumx.io) to start receiving the new `customer.rfi` event. ### 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. ### 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. 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" ] } ``` ### 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. 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** 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. ### 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:** 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. ### 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). ### 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. ### Multiple roles for associated parties 🎭 **Deprecated:** 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" } ``` ### 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. ### 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. ### 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:** 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`. ### 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:** 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. ### 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. ### 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 and verification can't proceed. 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, expected transaction volume, and the countries and counterparties involved. 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. 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 | 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 Successful verification flow: `NOT_STARTED` → `UNDER_VERIFICATION` → `APPROVED` When submitted information is invalid, the customer can resubmit corrected documents: `NOT_STARTED` → `UNDER_VERIFICATION` → `RFI` → `UNDER_VERIFICATION` → `APPROVED` 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 How customers accept Lumx's ToS before verification. Jurisdiction tiers and where onboarding is restricted. 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 Industries and activities Lumx doesn't support, including unlicensed money services. How marketplaces and platforms collect revenue without nesting. Reference flow for onboarding every seller as a customer. 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) 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 Jurisdiction tiers and where Enhanced Due Diligence applies. KYC and KYB requirements for individuals and businesses. Visibility rules and why pass-through structures are restricted. 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. Screenshots are not accepted unless explicitly pre-approved by Lumx compliance. ## Related resources Data requirements and verification statuses. Documentation accepted to verify source of funds and wealth. How Lumx notifies you when documents are needed. 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. 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). 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. If you don't respond within 24 hours, the transaction may be refunded to the sender. An API-driven flow for transaction RFIs is coming. Until then, resolution stays on email/Slack. ## Related resources Data requirements and verification statuses. Documentation accepted to verify source of funds and wealth. Full list of customer and account events. 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. Lumx may request additional supporting documentation if more clarification is needed during review. ## Related resources How Lumx notifies you when documents are needed. KYC and KYB requirements per customer type. Enum reference for the `type` field on document uploads. 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 | \*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 | 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 | 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 Payment rails, currencies, and stablecoin pairs by country. KYC and KYB requirements per customer type. Default thresholds and how EDD affects limits. 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. 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. 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 Full list of Lumx's legal documents and policies. Data requirements for KYC/KYB after acceptance. End-to-end onboarding flow for individual customers. End-to-end onboarding flow for business customers. # Transaction Limits Source: https://docs.lumx.io/compliance/transaction-limits Understand transaction thresholds 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 | **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. The response includes `used` and `remaining` for both `daily` and `monthly` limits, alongside the `max` per-transaction limit. ```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 theme={null} { "transactionLimits": { "single": { "max": "7500.00" }, "daily": { "max": "15000.00", "used": "2300.00", "remaining": "12700.00" }, "monthly": { "max": "15000.00", "used": "2300.00", "remaining": "12700.00" } } } ``` ## 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. 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). ### 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. 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. If an account moves to `RFI`, you'll be notified of the specific information needed. Provide the requested details to continue verification. 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`. | | USD | Up to 1 business day | Banking partner reviews before the account moves to `ACTIVE`. | | EUR | 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 The legal entity that owns the account. Move funds in and out of accounts. External payout targets for outbound fiat. 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. | 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. 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 Route payouts to a destination. The legal entity that owns the destination. Why holder relationships have to be disclosed and accurate. 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 Apply exchange rates when moving funds between currencies. 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. 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. Test fee collection in sandbox before going live. ## Related resources Where partner fees are applied and collected. 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: `AWAITING_FUNDS` → `TRANSFERRING_FIAT` → `TRADING` → `TRANSFERRING_STABLECOIN` → `SUCCESS` `TRANSFERRING_STABLECOIN` → `TRADING` → `TRANSFERRING_FIAT` → `SUCCESS` `TRANSFERRING_STABLECOIN` → `SUCCESS` 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 Approved customers are required to run transactions. Floating and locked rates that drive conversion pricing. Where off-ramp funds land. 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" } ] } ] } ``` 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 Customer entities and verification lifecycle. Virtual fiat accounts for incoming payments. Payout destinations and holder relationships. 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`. | 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. | ### 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. | | `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 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 | 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 Run the following command in your terminal: ```bash theme={null} claude mcp add --transport http lumx-docs https://docs.lumx.io/mcp ``` Add the following to your `.cursor/mcp.json` file: ```json theme={null} { "mcpServers": { "lumx-docs": { "url": "https://docs.lumx.io/mcp" } } } ``` Add the following to your `.vscode/mcp.json` file: ```json theme={null} { "servers": { "lumx-docs": { "type": "http", "url": "https://docs.lumx.io/mcp" } } } ``` 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. 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. 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 | 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 | | 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 | | Event type | Triggered when | | :--------------------------------- | :--------------------------------- | | `transfer.transferring_stablecoin` | Stablecoin transfer is in progress | | `transfer.success` | Transfer completed successfully | | `transfer.failed` | Transfer failed | | 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 | | 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 | | 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 | | 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 ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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", "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" } } ``` ```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", "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" } } ``` ```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", "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" } } ``` ```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", "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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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" } } ``` ```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. Your webhook signing secret starts with `whsec_`. You must strip this prefix and base64-decode the remainder before using it as the HMAC key. 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. ```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)) ); } ``` ```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)) ); } ``` ```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 ) ``` ```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). 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. Sign up at the [Dashboard](https://dashboard.lumx.io). From there you can manage projects, generate API keys, and monitor transaction activity. New accounts start with a Sandbox environment for testing. To access Production, [schedule a call with our team](https://cal.com/caiobrbs/discovery). 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. Store your API keys securely. Never commit them to version control or expose them in client-side code. 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 | Less than €100k: Instant; Other: 1 business day | | SWIFT (EUR) Q3/2026 | 🇪🇺 Europe | 1-5 business days | | SPEI | 🇲🇽 Mexico | Instant | | ACH Q3/2026 | 🇨🇴 Colombia | 1 business day | | Bre-B Q3/2026 | 🇨🇴 Colombia | Instant | | Transfers 3.0 Q3/2026 | 🇦🇷 Argentina | Instant | | Instant Payments Q3/2026 | 🇬🇧 United Kingdom | 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** | | :---------------------------------- | :----------- | :----------------------- | :----------------------- | :----------------------- | :----------------------- | | Ethereum | USDC | Supported | Supported | Supported | Supported | | Ethereum | USDT | Supported | Supported | Supported | Supported | | Polygon | USDC | Supported | Supported | Supported | Supported | | Polygon | USDT | Supported | Supported | Supported | Supported | | Base | USDC | Supported | Supported | - | Supported | | Tron | USDT | Supported | Supported | Supported | Supported | | Stellar On-ramp only | USDC | Supported | Supported | Supported | 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 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. Individual and business entities, with KYC/KYB built in. Multi-chain custody for stablecoin balances and payouts. Local-currency virtual accounts for fiat on and off ramps. Payout endpoints for fiat and stablecoin transfers. Floating and locked rates for fiat to stablecoin conversions. Add fees to transactions to monetize your integration. On-ramp, off-ramp, and transfer flows with full lifecycle. ## Get started Generate an API key and start sending requests. Create your first customer, accept payments, and send money globally. KYC/KYB requirements, transaction limits, and supported countries. 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, and USD accounts. 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 | 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. A customer can have only **one active USD rule** at a time. USD deposits 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" ``` The response has the same shape as the create response. To list every rule for a customer, send `GET /autoconversion-rules?customerId={customerId}`. ## 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 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. This endpoint only exists in sandbox. Calling it in production returns `404`. ## Related resources Virtual accounts that receive the deposits rules convert. Subscribe to conversion lifecycle events. Earn revenue on every conversion a rule executes. 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.). 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: 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. Returns the Lumx customer record including verification.link. 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. UI to build - Customer onboarding screen: form that calls POST /api/customers, then 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, 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 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. 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. 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). 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. ``` 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. Returns the Lumx customer record including verification.link. 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. UI to build - Customer onboarding screen: form that calls POST /api/customers, then 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, 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 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. 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`. 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). 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 ``` 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. 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. Returns the Lumx customer record including verification.link. 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. UI to build - Customer onboarding screen: form that calls POST /api/customers, then 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, 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 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. 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. 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). 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 ``` 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. Returns the Lumx customer record including verification.link. 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. UI to build (only if the project already has a frontend) - Customer onboarding screen: form that calls POST /api/customers, then 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 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. 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`. 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). 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 | 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. 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. Returns the Lumx customer record including verification.link. 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. UI to build - Customer onboarding screen: form that calls POST /api/customers, then 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, 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 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. 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. 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). 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 AI-built app — customer onboarding, on-ramp deposits, off-ramps, transfers, and webhooks — by pasting a prompt into your builder of choice. Lumx ships three surfaces that fit any AI workflow: **copy-paste prompts** tailored per builder, a **docs MCP server** for inline schema lookup, and the **REST API** itself. ## Pick your builder * [**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. * [**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. ## The three integration surfaces Each builder page ships a prompt that scaffolds the integration end-to-end: server routes for customer onboarding, on-ramp deposits, and transaction lookup, plus a starter UI. Paste it 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. ``` 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. For everything else — CI scripts, edge functions, server jobs, backends an AI tool doesn't touch — call the Lumx REST API directly. Every builder integration 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. 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. 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: 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. Returns the Lumx customer record including verification.link. 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. UI to build - Customer onboarding screen: form that calls POST /api/customers, then 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, 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 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. 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. 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). 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. 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: 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. Returns the Lumx customer record including verification.link. 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. UI to build (App Router pages) - /onboarding: form that submits to the customer server action, then 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, 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 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. 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. 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). 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). 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. 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 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). The request body is optional. If you don't need to redirect the customer after acceptance, you can send the request without a body. 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" }, "monthlyVolumeInUSD": "50000.00", "transactionVolumeInUSD": "10000.00", "jurisdictions": ["BRA", "USA", "EU"], "involvedActivities": ["NONE"], "transactionCounterparties": ["SELF"], "companyType": "BLOCKCHAIN_SOFTWARE_COMPANY", "annualRevenue": "1000000.00", "monthlyTransactionVolume": "100000.00", "complianceAndAML": "We have a dedicated compliance team and use third-party AML screening tools.", "isRegulatedActivity": false, "regulatedActivityDetails": "", "website": "https://example.com", "sourceOfFunds": "COMPANY", "servicesProvided": "We provide blockchain-based payment infrastructure for B2B cross-border transactions." }' ``` All fields are required. Here's a summary of the business-specific fields: | Field | Description | | :------------------------- | :----------------------------------------------------------------------- | | `companyType` | Type of company (e.g., `BLOCKCHAIN_SOFTWARE_COMPANY`, `PAYMENT_GATEWAY`) | | `annualRevenue` | Company's annual revenue in USD | | `monthlyTransactionVolume` | Expected monthly transaction volume | | `complianceAndAML` | Description of the company's compliance and AML policies | | `isRegulatedActivity` | Whether the company is involved in regulated activities | | `regulatedActivityDetails` | Details if `isRegulatedActivity` is `true` | | `website` | Company website URL | | `sourceOfFunds` | Origin of funds (e.g., `COMPANY`, `COMPANY_CAPITAL`, `INVESTMENT`) | | `servicesProvided` | Description of the services or products the company offers | For all accepted values, see the [API Reference](/api-reference/customers/send-additional-information). 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. Add the company's UBOs, shareholders, and representatives using `POST /customers/{id}/associated-parties`. Each associated party can have one or more roles. 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. ```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 }' ``` ```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, "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" } ``` ```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 }' ``` ```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 }' ``` ```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" } }' ``` Representatives do not require `ownershipPercentage`. ```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 }' ``` When an associated party has multiple roles, include all applicable roles in the `roles` array. If UBO or SHAREHOLDER is included, `ownershipPercentage` is required. 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: 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" ``` 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" } ``` 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. 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. 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 }' ``` This creates the following structure: ``` Your Customer (Business) └── Investment Fund ABC (Business Shareholder): 25% └── John Smith (Individual Shareholder): 50% ``` 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. 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. Resubmit any corrected documents/information for the affected associated parties before starting the retry. This option applies only to BUSINESS customers. ## Related resources Receive real-time status updates. Understand post-verification limits. 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. ```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", "link": "https://in.sumsub.com/websdk/p/sbx_aA00bB11cC33dD44" }, "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 | ```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", "link": "https://in.sumsub.com/websdk/p/sbx_aA00bB11cC33dD44" }, "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, 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"] }' ``` 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), the verification `level` (`STANDARD`), and a `link` for the verification flow. * `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 single, daily, and monthly limits. 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 Understand customer entities and verification workflows. Required data for KYC/KYB verification processes. Understand post-verification transaction limits. ## What's next After creating a customer, complete the verification process: Complete identity verification for individual customers. 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. 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. ## 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 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). The request body is optional. If you don't need to redirect the customer after acceptance, you can send the request without a body. 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", "middleName": "", "lastName": "Default", "address": { "country": "BRA", "line1": "Rua das Flores, 123", "city": "São Paulo", "state": "SP", "postalCode": "01234-567" }, "monthlyVolumeInUSD": "5000", "transactionVolumeInUSD": "1000", "jurisdictions": ["BRA", "USA", "OTHERS"], "involvedActivities": ["NONE"], "transactionCounterparties": ["SELF", "MERCHANTS_SUPPLIERS"], "professionalSituation": "EMPLOYEE", "professionalOccupation": "Software Engineer", "sourceOfFunds": "EMPLOYMENT" }' ``` 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` | | `monthlyVolumeInUSD` | Expected monthly transaction volume in USD | | `transactionVolumeInUSD` | Expected volume per transaction in USD | | `jurisdictions` | Regions where transactions will occur (e.g., `BRA`, `USA`, `EU`) | | `involvedActivities` | Activities involved (e.g., `NONE`, `GAMBLING`) | | `transactionCounterparties` | Who the customer transacts with (e.g., `SELF`, `MERCHANTS_SUPPLIERS`) | | `professionalSituation` | One of: `EMPLOYEE`, `SELF_EMPLOYED`, `UNEMPLOYED`, `RETIRED`, `OTHER` | | `professionalOccupation` | Free-text description of occupation | | `sourceOfFunds` | Origin of funds (e.g., `EMPLOYMENT`, `SAVINGS`, `COMPANY`) | For all accepted values, see the [API Reference](/api-reference/customers/send-additional-information). 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) 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. The customer must complete a liveness check by accessing the `verification.link` returned in the customer response. This link opens a guided selfie flow. You can retrieve the link at any time by reading the customer: ```bash theme={null} curl https://api-sandbox.lumx.io/customers/{id} \ -H "Authorization: Bearer YOUR_API_KEY" ``` For individual customers, verification starts automatically after the liveness check is completed. You do not need to call a separate endpoint to start verification. 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. 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. 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 Receive real-time status updates. Understand post-verification limits. 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. 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). 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. 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). 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" } ``` 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", "middleName": "", "lastName": "Default", "address": { "country": "BRA", "line1": "Rua das Flores, 123", "city": "São Paulo", "state": "SP", "postalCode": "01234-567" }, "monthlyVolumeInUSD": "5000", "transactionVolumeInUSD": "1000", "jurisdictions": ["BRA", "USA", "OTHERS"], "involvedActivities": ["NONE"], "transactionCounterparties": ["SELF", "MERCHANTS_SUPPLIERS"], "professionalSituation": "EMPLOYEE", "professionalOccupation": "Software Engineer", "sourceOfFunds": "EMPLOYMENT" }' ``` For the accepted values of each field, see [Individual Verification (KYC)](/guides/individual-verification#verification-flow) and the [API Reference](/api-reference/customers/send-additional-information). 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 Register individual and business customers. The standard verification flow without reuse. Full compliance requirements. 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 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. 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)) 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. 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`. ```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 '{ "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", "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" } ``` 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" ``` 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. 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 Default limits per verification level and the Dashboard flow. Receive real-time status updates. Every accepted document type code. 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"] }' ``` ```json Response theme={null} { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "type": "INDIVIDUAL", "verification": { "status": "NOT_STARTED", "level": "STANDARD", "link": "https://in.sumsub.com/websdk/p/sbx_aA00bB11cC33dD44" } } ``` 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" } ``` Then share the `verification.link` returned at customer creation 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, or an IBAN. Share those details with the depositor; when the matching amount lands, Lumx converts the fiat to stablecoin and credits the customer's wallet. 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). ```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" }' ``` ```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" }' ``` ```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" }' ``` ```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" }' ``` ```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`). 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. 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 Understand virtual fiat accounts and their lifecycle. Supported currencies, rails and countries. Subscribe to account and customer events. 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. ```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" }' ``` ```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" }' ``` 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" }' ``` 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. 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. ```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" } } }' ``` ```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" } } }' ``` ```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" } } }' ``` ```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" } } }' ``` ```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 Configure platform fees on on-ramp and off-ramp transactions. On-ramp lifecycle, purpose codes and rails. Lock FX rates for stablecoin-priced catalogs. 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" }' ``` 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. ```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" } } }' ``` ```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" } } }' ``` ```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" } } }' ``` ```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" } } }' ``` ```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" } } }' ``` ```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. 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 Fund the employer's wallet via local fiat rails. Holder relationships and verification. Off-ramp lifecycle and purpose codes. 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. 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" }' ``` 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. ```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" } } }' ``` ```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" } } }' ``` ```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" } } }' ``` ```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 Provision the sender's funding account. Lock FX rates for predictable payouts. Holder relationships and rail-specific identifiers. 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"] }' ``` 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" }' ``` 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" }' ``` 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 Provision multi-currency virtual accounts. Lock FX rates for predictable conversions. Holder relationships for inter-company moves. Transfer, off-ramp lifecycle and purpose codes.