> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pods.finance/llms.txt
> Use this file to discover all available pages before exploring further.

# Foreign USD KYC and ramps

> Sumsub share token, USD unlock, ACH/WIRE deposit instructions, and USD↔USDC money movement

Use this flow when your users are **outside Brazil** and need USD onramp (WIRE/ACH → USDC) or offramp (USDC → USD bank payout) through Avenia.

Pods exposes foreign KYC separately from the Brazilian Pix flow documented in [Set up Pods Ramp](/guides/ramp/setup). Brazilian profiles use `cpf` and `brlaEnabled`; foreign profiles use `taxId` / `taxIdCountry` and `usdEnabled`.

<Info>
  EUR capability is planned but not available yet. Poll `eurEnabled` on status responses only for forward compatibility — it stays `false` today.
</Info>

## Prerequisites

| Value                  | Source | Used for                                          |
| ---------------------- | ------ | ------------------------------------------------- |
| `PODS_API_BASE_URL`    | Pods   | Base URL for API requests                         |
| `PODS_API_KEY`         | Pods   | Server-to-server authentication                   |
| `AVENIA_PARTNER_TOKEN` | Pods   | Sumsub reusable-identity recipient setup          |
| `AVENIA_FOR_CLIENT_ID` | Pods   | `forClientId` when generating Sumsub share tokens |

Send all requests from your backend with `x-api-key`. Never expose the API key in browser or mobile clients.

## End-to-end flow

<Steps>
  <Step title="Complete Sumsub verification">
    The applicant finishes your Sumsub WebSDK level. Confirm `reviewStatus: completed` and `reviewResult.reviewAnswer: GREEN` before generating a share token.
  </Step>

  <Step title="Submit share token to Pods">
    `POST /api/v1/kyc/foreign/sumsub-share-token` with `taxId`, `taxIdCountry`, `shareToken`, `sumsubApplicantId`, `email`, and `walletAddress`.
  </Step>

  <Step title="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`.
  </Step>

  <Step title="Unlock USD">
    `POST /api/v1/kyc/currency-unlock` with `{ "currency": "usd", "kycUserId": "…" }`. Poll status again until `usdEnabled` is `true` (Avenia USD KYC can take one to a few minutes after submit).
  </Step>

  <Step title="Quote and settle onramp">
    `GET /v2/swap/quote` with `originChain=fiat`, `destinationChain` (`ethereum` | `base` | `polygon`), `tokenIn=USD`, USDC `tokenOut`, `amountIn` or `amountOut`, `destinationAddress`, and optional `usdPaymentMethod=ACH` or `WIRE`. With `destinationAddress`, the response includes ACH/WIRE deposit instructions — no EVM tx to sign. Poll `GET /v2/swap/status/{quoteId}` until fulfilled.
  </Step>
</Steps>

## 1. Submit foreign share token

```bash theme={null}
curl -sS -X POST \
  -H "x-api-key: $PODS_API_KEY" \
  -H "Content-Type: application/json" \
  "$PODS_API_BASE_URL/api/v1/kyc/foreign/sumsub-share-token" \
  -d '{
    "email": "user@example.com",
    "shareToken": "SUMSUB_SHARE_TOKEN",
    "sumsubApplicantId": "6a3502bf7fee2fab5377cb1e",
    "taxId": "123456789",
    "taxIdCountry": "USA",
    "walletAddress": "0xb794F5eA0ba39494cE839613fffBA74279579268"
  }'
```

Response:

```json theme={null}
{
  "kycUserId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "provider_pending"
}
```

Pods stores only a salted hash and last four digits of the tax id — never the full value.

## 2. Poll status (Level 1 approved, USD still locked)

```bash theme={null}
curl -sS \
  -H "x-api-key: $PODS_API_KEY" \
  "$PODS_API_BASE_URL/api/v1/kyc/status?kycUserId=550e8400-e29b-41d4-a716-446655440000"
```

Example shape after Level 1 is approved but before currency unlock (live capture pattern):

```json theme={null}
{
  "kycUserId": "a0bfbe64-99fb-4823-902a-7187c67348bb",
  "provider": "bigdatacorp",
  "email": "user@example.com",
  "walletAddress": "0xb0b6234dCbB885409eF23fD5FD5E63eE33b1377f",
  "status": "approved",
  "brlaEnabled": true,
  "usdEnabled": false,
  "eurEnabled": false,
  "rejectReason": null,
  "providerStatus": "COMPLETED",
  "providerResult": "AVENIA_IDENTITY_REUSED",
  "providerMessage": "An approved Avenia identity is available for reuse",
  "livenessStatus": "passed",
  "documentStatus": "passed",
  "birthDate": "1991-03-11",
  "ageAtVerification": 35,
  "aveniaApprovedAt": "2026-07-25T11:51:11.377Z",
  "updatedAt": "2026-07-25T11:51:11.378Z",
  "reason": null
}
```

Wait until `status` is `approved`. For rejections, read the normalized `reason` object (not `providerResult`).

<Warning>
  `status: approved` alone is **not** enough for USD ramps. You must unlock USD and wait until `usdEnabled` is `true`.
</Warning>

## 3. Unlock USD capability

```bash theme={null}
curl -sS -X POST \
  -H "x-api-key: $PODS_API_KEY" \
  -H "Content-Type: application/json" \
  "$PODS_API_BASE_URL/api/v1/kyc/currency-unlock" \
  -d '{
    "currency": "usd",
    "kycUserId": "550e8400-e29b-41d4-a716-446655440000"
  }'
```

The success response is the full KYC status object. Immediately after a successful submit, `usdEnabled` is often still `false` while Avenia processes the USD KYC attempt:

```json theme={null}
{
  "kycUserId": "65a7197f-85d9-4e5f-a12f-702daf0995c7",
  "provider": "bigdatacorp",
  "email": "user@example.com",
  "walletAddress": "0xb0b6234dCbB885409eF23fD5FD5E63eE33b1377f",
  "status": "approved",
  "brlaEnabled": true,
  "usdEnabled": false,
  "eurEnabled": false,
  "providerStatus": "COMPLETED",
  "providerResult": "APPROVED",
  "providerMessage": "An approved Avenia identity is available for reuse",
  "livenessStatus": "passed",
  "documentStatus": "passed",
  "updatedAt": "2026-08-28T22:44:33.094Z"
}
```

Poll `GET /api/v1/kyc/status` until `usdEnabled` is `true`:

```json theme={null}
{
  "kycUserId": "65a7197f-85d9-4e5f-a12f-702daf0995c7",
  "status": "approved",
  "brlaEnabled": true,
  "usdEnabled": true,
  "eurEnabled": false,
  "updatedAt": "2026-08-28T22:49:05.042Z"
}
```

### Currency-unlock errors

| HTTP  | `error.code`                        | When                                                                                     |
| ----- | ----------------------------------- | ---------------------------------------------------------------------------------------- |
| `400` | `AVENIA_CURRENCY_KYC_SUBMIT_FAILED` | Avenia rejected the USD KYC (example: missing identification document on the subaccount) |
| `500` | `INTERNAL_SERVER_ERROR`             | Avenia credentials not configured on the API (`Avenia API key not configured`)           |

Example — missing identification document:

```json theme={null}
{
  "error": {
    "code": "AVENIA_CURRENCY_KYC_SUBMIT_FAILED",
    "message": "missing required document: identification document",
    "details": {
      "provider": "avenia",
      "providerStatus": 400,
      "providerMessage": "missing required document: identification document"
    }
  }
}
```

Documents are collected during Level 1 / Sumsub — `POST /currency-unlock` does not accept document uploads in the body.

## 4. Onramp: USD ACH/WIRE → USDC (Polygon example)

Prerequisite: KYC profile for `destinationAddress` has `usdEnabled: true`.

Pass **exactly one** of `amountIn` or `amountOut`. Both are supported; they answer different product questions:

| Mode               | Query       | Fixes                            | Typical use                                                   |
| ------------------ | ----------- | -------------------------------- | ------------------------------------------------------------- |
| Exact USD send     | `amountIn`  | USD cents the user will wire/ACH | “User pays \$5.00” — USDC out varies after rail fee           |
| Exact USDC receive | `amountOut` | USDC raw (6 decimals)            | “User receives 4 USDC” — Avenia returns the USD gross to send |

| Query parameter               | Required                       | Value                                                                                            |
| ----------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------ |
| `originChain`                 | Yes                            | `fiat`                                                                                           |
| `destinationChain`            | Yes                            | `ethereum`, `base`, or `polygon`                                                                 |
| `tokenIn`                     | Yes                            | `USD`                                                                                            |
| `tokenOut`                    | Yes                            | Native USDC on the destination chain                                                             |
| `amountIn` **or** `amountOut` | Yes (exactly one)              | `amountIn`: USD cents (`500` = USD 5.00). `amountOut`: USDC smallest units (`4000000` = 4 USDC). |
| `destinationAddress`          | Yes (for deposit instructions) | Wallet that receives USDC — must match an unlocked USD KYC profile                               |
| `usdPaymentMethod`            | No                             | `WIRE` (default) or `ACH`                                                                        |

<Info>
  ACH and WIRE charge an Avenia **payment-rail fee** (`feeBreakdown.charges` with `kind: paymentRail`), deducted from the USD sent — not charged on top in the bank app. With `amountIn`, USDC out is net of that fee. With `amountOut`, Pods asks Avenia for an exact USDC receive amount in one quote (`outputAmount`); `tokenIn.amount` / `paymentInstructions.amount` are the USD the user must send (fee included).

  WIRE In Fee is \~USD 10 — a small `amountIn` (e.g. `$5`) can yield `0` USDC. Prefer `amountOut` (or a larger `amountIn`) for WIRE.

  Quote/`Action` deadlines for USD onramps default to **24 hours** (`USD_ONRAMP_QUOTE_EXPIRATION_MINUTES=1440`), not the generic 5-minute crypto quote TTL. When the Avenia ticket is created, Pods extends `quote.deadline` and `Action.expireAt` to at least `paymentInstructions.expiresAt`.
</Info>

### Exact USD send (`amountIn`)

```bash theme={null}
curl -sS -G \
  -H "x-api-key: $PODS_API_KEY" \
  --data-urlencode "originChain=fiat" \
  --data-urlencode "destinationChain=polygon" \
  --data-urlencode "tokenIn=USD" \
  --data-urlencode "tokenOut=0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" \
  --data-urlencode "amountIn=500" \
  --data-urlencode "destinationAddress=0xb0b6234dCbB885409eF23fD5FD5E63eE33b1377f" \
  --data-urlencode "usdPaymentMethod=ACH" \
  "$PODS_API_BASE_URL/v2/swap/quote"
```

Live response shape (ACH, send USD 5.00 → \~3.983876 USDC on Polygon):

```json theme={null}
{
  "quote": {
    "quoteId": "06c35a13-aa53-4de0-ae5b-f81c13f10cc8",
    "originChain": "fiat",
    "destinationChain": "polygon",
    "tokenIn": {
      "symbol": "USD",
      "decimals": 2,
      "amount": "500",
      "amountInUSD": 5
    },
    "tokenOut": {
      "contract": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
      "symbol": "USDC",
      "decimals": 6,
      "amount": "3983876",
      "minAmountOut": "3983876",
      "expectedAmountOut": "3983876"
    },
    "provider": "deframe",
    "status": "pending",
    "destinationAddress": "0xb0b6234dCbB885409eF23fD5FD5E63eE33b1377f",
    "feeBreakdown": {
      "chargeableAmountInRaw": "500",
      "charges": [
        {
          "kind": "paymentRail",
          "label": "ACH fee",
          "amountRaw": "102",
          "decimals": 2,
          "symbol": "USD",
          "asset": "tokenIn",
          "amountInUSD": 1.02
        }
      ],
      "summary": {
        "platformFeesTotalInUSD": 0,
        "paymentRailFeesTotalInUSD": 1.02
      }
    },
    "deadlineDate": "2026-08-28T23:17:08.010Z",
    "chainIdOut": 137
  },
  "chainId": null,
  "transactionData": [],
  "paymentInstructions": {
    "method": "ACH",
    "amount": {
      "currency": "USD",
      "amountRaw": "500",
      "decimals": 2
    },
    "expiresAt": "2026-08-28T23:22:10.106164238Z",
    "usd": {
      "accountHolderName": "BRLA Digital LTDA",
      "bankAccountNumber": "218939188578",
      "bankRoutingNumber": "101019644",
      "accountType": "CHECKING",
      "bankName": "Lead Bank",
      "bankAddress": "1801 Main St., Kansas City, MO 64108",
      "depositMessage": "BRGEGVRDDCX4JE6ZKEBK",
      "paymentRail": "ACH"
    }
  },
  "id": "6a9215c94649656acd52ea13"
}
```

### Exact USDC receive (`amountOut`)

```bash theme={null}
curl -sS -G \
  -H "x-api-key: $PODS_API_KEY" \
  --data-urlencode "originChain=fiat" \
  --data-urlencode "destinationChain=polygon" \
  --data-urlencode "tokenIn=USD" \
  --data-urlencode "tokenOut=0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" \
  --data-urlencode "amountOut=4000000" \
  --data-urlencode "destinationAddress=0xb0b6234dCbB885409eF23fD5FD5E63eE33b1377f" \
  --data-urlencode "usdPaymentMethod=ACH" \
  "$PODS_API_BASE_URL/v2/swap/quote"
```

Live response shape (ACH, receive **exactly** 4 USDC → send USD 5.02):

```json theme={null}
{
  "quote": {
    "quoteId": "afb84845-5347-4d26-9546-4b22eed465f1",
    "originChain": "fiat",
    "destinationChain": "polygon",
    "tokenIn": {
      "symbol": "USD",
      "decimals": 2,
      "amount": "502",
      "amountInUSD": 5.02
    },
    "tokenOut": {
      "contract": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
      "symbol": "USDC",
      "decimals": 6,
      "amount": "4000000",
      "minAmountOut": "4000000",
      "expectedAmountOut": "4000000"
    },
    "provider": "deframe",
    "status": "pending",
    "destinationAddress": "0xb0b6234dCbB885409eF23fD5FD5E63eE33b1377f",
    "feeBreakdown": {
      "chargeableAmountInRaw": "502",
      "charges": [
        {
          "kind": "paymentRail",
          "label": "ACH fee",
          "amountRaw": "102",
          "decimals": 2,
          "symbol": "USD",
          "asset": "tokenIn",
          "amountInUSD": 1.02
        }
      ],
      "summary": {
        "platformFeesTotalInUSD": 0,
        "paymentRailFeesTotalInUSD": 1.02
      }
    },
    "deadlineDate": "2026-08-30T19:09:33.230Z",
    "chainIdOut": 137
  },
  "chainId": null,
  "transactionData": [],
  "paymentInstructions": {
    "method": "ACH",
    "amount": {
      "currency": "USD",
      "amountRaw": "502",
      "decimals": 2
    },
    "expiresAt": "2026-08-30T19:09:33.510425433Z",
    "usd": {
      "accountHolderName": "BRLA Digital LTDA",
      "bankAccountNumber": "218939188578",
      "bankRoutingNumber": "101019644",
      "accountType": "CHECKING",
      "bankName": "Lead Bank",
      "bankAddress": "1801 Main St., Kansas City, MO 64108",
      "depositMessage": "BRGZEPGAWBJTUEWYMBMG",
      "paymentRail": "ACH"
    }
  },
  "id": "6a932e6e8e3c3ec415c10819"
}
```

Same `amountOut=4000000` with `usdPaymentMethod=WIRE` returns exact `tokenOut.amount=4000000` and a larger USD send (live smoke: `tokenIn.amount=1404` / \~USD 14.04, WIRE fee \~USD 10.04).

### How to present deposit instructions

| Field                                              | UI use                                                                      |
| -------------------------------------------------- | --------------------------------------------------------------------------- |
| `paymentInstructions.usd.bankName` / `bankAddress` | Bank receiving the ACH/WIRE                                                 |
| `paymentInstructions.usd.bankRoutingNumber`        | ABA routing                                                                 |
| `paymentInstructions.usd.bankAccountNumber`        | Account number                                                              |
| `paymentInstructions.usd.accountType`              | e.g. `CHECKING`                                                             |
| `paymentInstructions.usd.accountHolderName`        | Beneficiary name                                                            |
| `paymentInstructions.usd.depositMessage`           | **Required memo / reference** — without it, Avenia cannot match the payment |
| `paymentInstructions.amount`                       | Exact USD to send (`amountRaw` / `10^decimals`)                             |
| `paymentInstructions.expiresAt`                    | Instruct the user before this time                                          |

There is no EVM transaction to sign (`transactionData` is `[]`, `chainId` is `null`). Top-level `id` is the **Action** id; `quote.quoteId` is for `GET /v2/swap/status/{quoteId}`.

Also see [Move Money with Pods Ramp](/guides/ramp/quotes) for the shared quote surface and Pix parallels.

## 5. USD beneficiary (offramp resolve-or-create)

Pass bank account fields on `GET /v2/swap/quote` with `originAddress`. Pods lists existing Avenia beneficiaries for the KYC subaccount; if account number + routing match, it reuses that `id`, otherwise it creates one and stores it on the quote (`rawQuote.beneficiaryUsdBankAccountId`, internal).

Required query fields:

| Field                                                                                   | Notes                                       |
| --------------------------------------------------------------------------------------- | ------------------------------------------- |
| `bankAccountNumber`                                                                     | Account number                              |
| `bankRoutingNumber`                                                                     | ABA routing                                 |
| `bankBeneficiaryName`                                                                   | Legal name on the account                   |
| `bankName`                                                                              | Bank name                                   |
| `beneficiaryStreetLine1`                                                                | Address line 1                              |
| `beneficiaryCity` / `beneficiaryState` / `beneficiaryPostalCode` / `beneficiaryCountry` | Address                                     |
| `alias`                                                                                 | Optional; defaults to `bankBeneficiaryName` |
| `beneficiaryStreetLine2` / `description`                                                | Optional                                    |

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).

## 6. USD↔USDC ramps (summary)

Supported USDC networks for v1 follow Avenia's USD→USDC allowlist: `ethereum`, `base`, and `polygon`. When Avenia supports the destination chain, it delivers USDC directly to the user's wallet (no Deframe inventory hop).

| Direction | Chains                                       | Payment rail                                   | Fees                                                                          |
| --------- | -------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------- |
| Onramp    | `originChain=fiat`, `destinationChain=<evm>` | WIRE or ACH (`usdPaymentMethod`, default WIRE) | Avenia pass-through; no Pods/customer swap markup; rail fee in `feeBreakdown` |
| Offramp   | `originChain=<evm>`, `destinationChain=fiat` | WIRE or ACH to registered beneficiary          | Same pass-through model                                                       |

### Token addresses (USDC)

| Network  | Chain id | Address                                      |
| -------- | -------- | -------------------------------------------- |
| Ethereum | `1`      | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` |
| Base     | `8453`   | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
| Polygon  | `137`    | `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` |

Preferred offramp quote (bank fields → resolve-or-create, then executable payload):

```bash theme={null}
curl -sS \
  -H "x-api-key: $PODS_API_KEY" \
  "$PODS_API_BASE_URL/v2/swap/quote?originChain=base&destinationChain=fiat&tokenIn=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&tokenOut=USD&amountIn=1000000&originAddress=0xb794F5eA0ba39494cE839613fffBA74279579268&bankAccountNumber=581984177591&bankRoutingNumber=721738952&bankBeneficiaryName=John%20Smith&bankName=JPMorgan%20Chase%20Bank%20N.A.&beneficiaryStreetLine1=123%20Main%20Street&beneficiaryCity=New%20York&beneficiaryState=NY&beneficiaryPostalCode=10001&beneficiaryCountry=USA&usdPaymentMethod=WIRE"
```

`amountIn` is in USDC smallest units (`1000000` = 1 USDC). Optional `usdPaymentMethod` is `WIRE` (default) or `ACH`.

Track progress with `GET /v2/swap/status/{quoteId}` and `GET /actions/{id}`.

<Warning>
  Offramp and onramp both require `usdEnabled: true` on the KYC profile tied to the wallet (`destinationAddress` on onramp, `originAddress` on offramp). Pods rejects quotes when the wallet has no unlocked USD capability.
</Warning>

## Status fields reference

| Field         | Meaning                                                          |
| ------------- | ---------------------------------------------------------------- |
| `status`      | Normalized Pods KYC state (`approved`, `provider_pending`, etc.) |
| `brlaEnabled` | Brazilian Pix capability                                         |
| `usdEnabled`  | USD WIRE/ACH ramps enabled after currency unlock                 |
| `eurEnabled`  | Reserved; always `false` today                                   |

## API reference

Open the **Ramp** group in the API Reference tab for full request/response schemas:

* `POST /api/v1/kyc/foreign/sumsub-share-token`
* `POST /api/v1/kyc/currency-unlock`
* `GET /api/v1/kyc/status`
* `GET /v2/swap/quote`
* `GET /v2/swap/status/{quoteId}`
* `GET /actions/{id}`

## Captured examples (repo fixtures)

Internal live captures used to author this guide (do not ship secrets from local `.env`):

| Fixture                                 | Path                                                      |
| --------------------------------------- | --------------------------------------------------------- |
| KYC status / unlock errors              | `test/fixtures/kyc/usdCurrencyUnlockStatusCaptures.ts`    |
| ACH onramp quote + deposit instructions | `test/fixtures/bridge/usdAchPolygonOnrampQuoteCapture.ts` |
