cpf and brlaEnabled; foreign profiles use taxId / taxIdCountry and usdEnabled.
EUR capability is planned but not available yet. Poll
eurEnabled on status responses only for forward compatibility — it stays false today.Prerequisites
You need to understand these integration values:
Send all requests from your backend with
x-api-key. Never expose the API key in browser or mobile clients.
End-to-end flow
1
Complete Sumsub verification
The applicant finishes your Sumsub WebSDK level. Confirm
reviewStatus: completed and reviewResult.reviewAnswer: GREEN before generating a share token.2
Submit share token to Pods
POST /api/v1/kyc/foreign/sumsub-share-token with taxId, taxIdCountry, shareToken, sumsubApplicantId, email, and walletAddress.3
Poll KYC status
GET /api/v1/kyc/status?kycUserId=… until status is approved. Level 1 approval does not enable USD ramps yet — usdEnabled stays false. Public Pix (BRL) and public USD are separate identities — a user typically has one or the other.4
Unlock USD
Default path:
POST /api/v1/kyc/currency-unlock with { "currency": "usd", "kycUserId": "…" }. Poll until usdEnabled is true. For UnblockPay, first provision an individual with POST /api/v1/kyc/sessions/:kycUserId/submit and rampProvider=unblockpay using taxId + taxIdCountry (and the home address). Finish UnblockPay KYC via hosted verificationLink or POST /api/v1/kyc/sessions/:kycUserId/documents — see UnblockPay KYC. Then POST /api/v1/kyc/currency-unlock with rampProvider=unblockpay refreshes rails and, when the bank fields in ramps.unblockpay.unlock.requiredBodyFields are sent, registers a wire External Account for offramp. That path never sets top-level usdEnabled. When the UnblockPay nest is non-terminal, GET /api/v1/kyc/status live-refreshes UnblockPay and may trigger /check after document uploads. capabilities.USD is the source of truth after review — LIGHT create alone is not enough.5
Quote and settle onramp
See Wire to USDC (onramp) for quote examples and deposit instructions.
GET /v2/swap/quote with originChain=fiat, destinationChain (base | polygon), tokenIn=USD, USDC tokenOut, amountIn or amountOut, and destinationAddress. With destinationAddress, the response includes the WIRE deposit instructions — no EVM tx to sign. Poll GET /v2/swap/status/{quoteId} until fulfilled. A default provider handles USD WIRE unless you pin one; pass rampProvider=unblockpay to route USD WIRE through UnblockPay on base or polygon. Requesting a chain outside base/polygon returns RAMP_PROVIDER_NOT_ELIGIBLE. UnblockPay onramp is ready when ramps.unblockpay.capabilities.USD is true.6
Quote and settle offramp
See USDC to Wire (offramp) for the full quote examples and execution details.
GET /v2/swap/quote with originChain (base | polygon), destinationChain=fiat, USDC tokenIn, tokenOut=USD, amountIn or amountOut, originAddress, and the USD bank fields. Sign every item in transactionData using that item’s chainId. Poll status until fulfilled. UnblockPay offramp additionally requires ramps.unblockpay.unlock.offrampReady.1. Submit foreign share token
approved, retries with the same customer, applicant, tax ID, and an already linked wallet return the original kycUserId and status: approved without creating or changing a profile or importing another token. Legacy approvals, including copied approvals and profiles without import metadata, follow the same rule. The request still requires a share token, but Pods does not validate or consume it on this approved retry. Pods trusts the authenticated integration’s identifiers; your backend must authenticate the end user and authorize their use of that KYC profile.
Changing the applicant while claiming a tax ID already approved for your customer returns 409 KYC_VERIFIED_BINDING_REQUIRED without creating a profile. Changing an existing profile’s wallet returns 409 SUMSUB_WALLET_MISMATCH; changing its tax ID returns 409 SUMSUB_TAX_ID_MISMATCH. An approved profile without a stored identity hash cannot be matched and returns 409 KYC_VERIFIED_BINDING_REQUIRED. A duplicate-tax-ID rejection does not establish approval for the current applicant.
2. Poll status (Level 1 approved, USD still locked)
status is approved. For rejections, read the normalized reason object (not providerResult).
3. Unlock USD capability
usdEnabled is often still false while the provider processes the USD KYC attempt:
GET /api/v1/kyc/status until usdEnabled is true:
Currency-unlock errors
Example — missing identification document:
POST /currency-unlock does not accept document uploads in the body.
UnblockPay: register the wire External Account
UnblockPay pays USD wire offramps only to a bank account registered beforehand (an External Account). Send the bank fields onPOST /api/v1/kyc/currency-unlock with rampProvider=unblockpay to register it:
- US beneficiary address only. UnblockPay’s wire provider rejects any other country, so Pods refuses it up front with
UNBLOCKPAY_WIRE_BENEFICIARY_COUNTRY_UNSUPPORTED. SendbeneficiaryCountry=USA(USis also accepted). The address does not need to match the KYC address. isThirdParty(optional, defaultfalse) tells UnblockPay whether the bank account belongs to someone other than the KYC holder:- Leave it out, or send
false, when the account is the KYC holder’s own. That is the common case: the user withdraws to their own US account. - Send
trueonly whenbankBeneficiaryNameis a different person or company, for example a family member or the user’s employer. - Send the real value: it is a compliance declaration to the provider. It does not block payouts, and it is fixed once the External Account exists.
- Leave it out, or send
- Approval. UnblockPay reviews each External Account. Offramp quotes return
UNBLOCKPAY_WIRE_EXTERNAL_ACCOUNT_NOT_APPROVEDwhile the account is notapproved. - One account per KYC profile. A second unlock with bank fields keeps the account already registered. Offramp quotes must send the same
bankAccountNumberandbankRoutingNumber, or they fail withUSD_WIRE_ACCOUNT_MISMATCH.
ramps.unblockpay.unlock.offrampReady becomes true once the External Account is registered.
4. USD beneficiary
Offramp resolve-or-create
Pass bank account fields onGET /v2/swap/quote with originAddress. Pods lists existing payout beneficiaries for the KYC subaccount; if account number + routing match, it reuses that id, otherwise it creates one and stores it on the quote. The id is internal and is not returned in the quote response.
Required query fields:
Inspect is not exposed as a separate KYC API — beneficiaries are created or reused automatically on offramp quote from the flat bank fields (Pix-style destination).
5. USD↔USDC ramps (summary)
Supported USDC networks for v1 arebase and polygon. The provider delivers USDC directly to the user’s wallet (no Pods inventory hop).
For full quotes, deposit instructions, and execution examples, see the Move Money guide.
Token addresses (USDC)
Pass exactly one of
amountIn or amountOut — same contract as the onramp, inverted:
With
amountOut, Pods requests the price once (outputAmount); tokenIn.amount is the USDC to transfer (Out Fee included). The rail is WIRE (usdPaymentMethod defaults to WIRE).
Example (quotes only, nothing signed):
Prefer
amountOut when the product promise is “receive $N”. A small amountIn will not cover the WIRE Out Fee (~10 USDC), so use amountOut or an amountIn above ~10 USDC.
Preferred offramp quote (bank fields → resolve-or-create, then executable payload). Use ASCII bankBeneficiaryName (example John Smith, not accented names):
amountOut=400 with amountIn=15000000 (15 USDC) — large enough to cover the ~10 USDC WIRE Out Fee.
Track progress with GET /v2/swap/status/{quoteId} and GET /actions/{id}.
Money movement and quotes
Onramp/offramp quote examples, deposit instructions, and executabletransactionData live in the Move Money guide: Move Money with Pods Ramp. Use that guide for GET /v2/swap/quote, GET /v2/swap/status/{quoteId}, and execution details.
Status fields reference
API reference
Open the Ramp group in the API Reference tab for full request/response schemas:POST /api/v1/kyc/foreign/sumsub-share-tokenPOST /api/v1/kyc/currency-unlockGET /api/v1/kyc/statusGET /v2/swap/quoteGET /v2/swap/status/{quoteId}GET /actions/{id}
Captured examples
Internal live captures used to author this guide (do not ship secrets from local.env):