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

# White-Label API

> Embed the Meld White-Label API for buy, sell, and transfer crypto end-to-end through Portal Client API endpoints.

## Overview

Meld's [White-Label API](https://docs.meld.io/docs/whitelabel-api-guide) is an iframe-based experience for buy, sell, and transfer crypto sessions powered by an aggregated network of providers. Portal exposes thin proxy endpoints under `/clients/me/integrations/meld/*`.

The minimum end-to-end sequence is:

1. **Discover** supported countries, currencies, and payment methods.
2. **Quote** the conversion to confirm pricing and pick a service provider.
3. **Create a widget session** — Meld returns a `widgetUrl` you embed in an iframe or load via redirect.
4. **Track transaction status** by session or transaction ID (or via Meld webhooks configured in Meld Dashboard).

The widget itself collects user identity and drives KYC inside the iframe. You can optionally [pre-create a Meld customer](#optional-pre-create-a-meld-customer) for your Portal client to speed up KYC or to track sessions against a persistent customer record — but it is not required.

For the widget UX itself — iframe sizing, redirect handling, supported lock fields — see [Meld's Provider UI Launch Implementation guide](https://docs.meld.io/docs/whitelabel-api-guide#provider-ui-launch-implementation).

## Prerequisites

* The Meld integration is enabled for the target Portal environment. See [Meld integration overview](/integrations/On-Off-Ramp/meld).
* You hold a valid Portal Client API session token. See the [Client API quickstart](/apis/quickstart).

All examples below use `https://api.portalhq.io`. For sandbox/dev Portal environments, swap the host accordingly.

## Step 1: Discovery

Call the discovery endpoints to populate dropdowns and validate selections before opening the widget. All discovery endpoints are `GET` and require no request body.

### Countries

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/discovery/countries \
  --header 'Authorization: Bearer [token]'
```

Example response (truncated):

```json theme={null}
{
  "data": [
    {
      "countryCode": "US",
      "name": "United States",
      "flagImageUrl": "https://images.meld.io/flags/us.svg",
      "regions": [
        { "regionCode": "CA", "name": "California" },
        { "regionCode": "NY", "name": "New York" }
      ]
    }
  ]
}
```

### Fiat currencies

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/discovery/fiat-currencies \
  --header 'Authorization: Bearer [token]'
```

### Crypto currencies

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/discovery/crypto-currencies \
  --header 'Authorization: Bearer [token]'
```

<Note>
  Meld doesn't accept a separate chain parameter — each token has a distinct `currencyCode` per chain (a "Meld Code"). See [Meld's cryptocurrency coverage](https://www.meld.io/coverage/cryptocurrencies) for the full list.
</Note>

### Payment methods

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/discovery/payment-methods \
  --header 'Authorization: Bearer [token]'
```

Other discovery endpoints follow the same shape:

* `GET /discovery/service-providers` — available providers and their categories/logos.
* `GET /discovery/defaults` — default fiat currency and payment methods per country.
* `GET /discovery/buy-limits`, `GET /discovery/sell-limits` — min/max/default amounts.
* `GET /discovery/kyc-limits` — daily/weekly/monthly limits per KYC tier.

<Tip>
  Cache discovery responses client-side. Countries, currencies, and payment methods change rarely and re-querying on every page load adds unnecessary latency.
</Tip>

## Step 2: Get a quote

Call `POST /retail/quote` to fetch live pricing across providers before opening the widget. The response includes one `MeldQuote` per available provider, sorted by Meld's recommendation logic.

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/retail/quote \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --data '{
    "countryCode": "US",
    "sourceCurrencyCode": "USD",
    "destinationCurrencyCode": "USDC",
    "sourceAmount": 100
  }'
```

| Field                     | Type      | Required | Description                                                            |
| ------------------------- | --------- | -------- | ---------------------------------------------------------------------- |
| `countryCode`             | string    | Yes      | ISO-3166-1 alpha-2 country code (for example `US`).                    |
| `sourceCurrencyCode`      | string    | Yes      | Source currency (fiat for buy, crypto for sell).                       |
| `destinationCurrencyCode` | string    | Yes      | Destination currency.                                                  |
| `sourceAmount`            | number    | Yes      | Amount of source currency to convert.                                  |
| `walletAddress`           | string    | No       | Destination wallet address (informs provider eligibility).             |
| `paymentMethodType`       | string    | No       | Filter to a specific payment method (for example `CREDIT_DEBIT_CARD`). |
| `serviceProviders`        | string\[] | No       | Restrict the quote to specific providers.                              |
| `subdivision`             | string    | No       | State/region code, when required by the country.                       |

<Note>
  Whichever side carries crypto — `destinationCurrencyCode` on a buy, `sourceCurrencyCode` on a sell, or both on a transfer — uses a [Meld Code](https://www.meld.io/coverage/cryptocurrencies) that selects both the asset and the chain. Pass the same code into the widget session in Step 3 so the quoted price matches the actual settlement chain.
</Note>

Example response (truncated):

```json theme={null}
{
  "data": {
    "quotes": [
      {
        "transactionType": "CRYPTO_PURCHASE",
        "sourceAmount": 100,
        "sourceCurrencyCode": "USD",
        "destinationAmount": 99.42,
        "destinationCurrencyCode": "USDC",
        "exchangeRate": 0.9942,
        "transactionFee": 0.5,
        "totalFee": 0.58,
        "paymentMethodType": "CREDIT_DEBIT_CARD",
        "serviceProvider": "TRANSAK"
      }
    ]
  }
}
```

Pick a `serviceProvider` from the response — you'll pass it into the widget session in the next step.

## Step 3: Create a widget session

Call `POST /retail/widget` with the chosen `serviceProvider` and the same currency/amount fields used for quoting. Meld returns a `widgetUrl` you embed in an iframe (or load via redirect).

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/retail/widget \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --data '{
    "sessionType": "BUY",
    "sessionData": {
      "countryCode": "US",
      "sourceAmount": "100",
      "sourceCurrencyCode": "USD",
      "destinationCurrencyCode": "USDC",
      "serviceProvider": "TRANSAK",
      "walletAddress": "0xc118ad00663be7d0360b56a4e9fbb20530bf4693",
      "redirectUrl": "https://yourapp.example/meld/return"
    }
  }'
```

| Field                                 | Type                            | Required | Description                                                                                   |
| ------------------------------------- | ------------------------------- | -------- | --------------------------------------------------------------------------------------------- |
| `sessionType`                         | `"BUY" \| "SELL" \| "TRANSFER"` | Yes      | The flow direction.                                                                           |
| `sessionData.countryCode`             | string                          | Yes      | ISO-3166-1 alpha-2 country code.                                                              |
| `sessionData.sourceAmount`            | string                          | Yes      | Source amount (string-encoded decimal).                                                       |
| `sessionData.sourceCurrencyCode`      | string                          | Yes      | Source currency.                                                                              |
| `sessionData.destinationCurrencyCode` | string                          | Yes      | Destination currency.                                                                         |
| `sessionData.serviceProvider`         | string                          | Yes      | Provider chosen from the quote response.                                                      |
| `sessionData.walletAddress`           | string                          | No       | Destination wallet address. Pre-fills the widget.                                             |
| `sessionData.walletTag`               | string                          | No       | Memo / destination tag for chains that require it.                                            |
| `sessionData.paymentMethodType`       | string                          | No       | Pre-selects the payment method.                                                               |
| `sessionData.lockFields`              | string\[]                       | No       | Field names the user cannot modify (for example `["walletAddress"]`). See Meld's docs.        |
| `sessionData.redirectUrl`             | string                          | No       | URL Meld redirects to when the user finishes the flow.                                        |
| `externalSessionId`                   | string                          | No       | Your reference for this session (recorded on Meld's transaction).                             |
| `customerId`                          | string                          | No       | Meld `customerId` if you have [pre-created a customer](#optional-pre-create-a-meld-customer). |
| `bypassKyc`                           | boolean                         | No       | Skip KYC where allowed by the provider/jurisdiction. Use cautiously.                          |

Example response:

```json theme={null}
{
  "data": {
    "id": "ses_01HZZZZ",
    "token": "eyJhbGciOi...",
    "customerId": "cust_01HXXXX",
    "externalCustomerId": "cmnescm57000a14ebp8oeqj4k--cmnescm5h000c14ebofkcqdkr--cmnesxy85000346cmwyknl1v7",
    "externalSessionId": "",
    "widgetUrl": "https://meldcrypto.com/?token=eyJhbGciOi..."
  }
}
```

<Warning>
  Load `widgetUrl` in an iframe or full-page redirect from your app — never echo the URL into a context the end user can copy and reuse, since it embeds a single-use session token. Refer to [Meld's Provider UI Launch Implementation guide](https://docs.meld.io/docs/whitelabel-api-guide#provider-ui-launch-implementation) for iframe sizing guidance and supported `lockFields` values.
</Warning>

## Step 4: Track transaction status

The widget drives the user through payment, KYC (when required), and settlement. To inspect the resulting transaction from your backend, look it up by widget session ID:

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/retail/transactions/sessions/ses_01HZZZZ \
  --header 'Authorization: Bearer [token]'
```

Or by Meld transaction ID:

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/retail/transactions/tx_01HAAAA \
  --header 'Authorization: Bearer [token]'
```

Or list all transactions for the current client:

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/retail/transactions \
  --header 'Authorization: Bearer [token]'
```

Example response:

```json theme={null}
{
  "data": {
    "transaction": {
      "id": "tx_01HAAAA",
      "sessionId": "ses_01HZZZZ",
      "status": "SETTLED",
      "transactionType": "CRYPTO_PURCHASE",
      "serviceProvider": "TRANSAK",
      "sourceAmount": 100,
      "sourceCurrencyCode": "USD",
      "destinationAmount": 99.42,
      "destinationCurrencyCode": "USDC",
      "destinationWalletAddress": "0xc118ad00663be7d0360b56a4e9fbb20530bf4693",
      "createdAt": "2026-05-07T12:00:00Z",
      "updatedAt": "2026-05-07T12:08:42Z"
    }
  }
}
```

<Note>
  Prefer Meld webhooks over polling for transaction lifecycle updates. Configure them in [Meld Dashboard](https://dashboard.meld.io/) — see [Webhooks](/integrations/On-Off-Ramp/meld-webhooks).
</Note>

## Optional: Pre-create a Meld customer

The widget collects user identity inside the iframe, so you do not need a pre-existing Meld customer record to launch a session. Pre-creating one is useful when you want to:

* Pre-fill identifying fields to speed up KYC
* Track Meld history for a Portal client outside of widget sessions
* Associate multiple sessions with a persistent Meld customer record

Call `POST /customers` once per Portal client. Portal derives an identifier from your custodian + environment + client identity, so you don't pass it yourself. Meld returns this value as `externalId` on customer objects and as `externalCustomerId` on quote, widget session, and transaction objects — both fields hold the same value.

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/customers \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --data '{
    "name": { "firstName": "Jane", "lastName": "Doe" },
    "email": "jane@example.com",
    "phone": "+15551234567",
    "dateOfBirth": "1990-01-15",
    "type": "INDIVIDUAL"
  }'
```

Example response:

```json theme={null}
{
  "data": {
    "id": "cust_01HXXXX",
    "accountId": "acct_01HYYYY",
    "externalId": "cmnescm57000a14ebp8oeqj4k--cmnescm5h000c14ebofkcqdkr--cmnesxy85000346cmwyknl1v7",
    "name": { "firstName": "Jane", "lastName": "Doe" },
    "email": "jane@example.com",
    "type": "INDIVIDUAL",
    "status": "ACTIVE"
  }
}
```

<Note>
  All body fields are optional, but providing identifying data up front helps Meld's KYC providers complete checks faster. Required fields vary by service provider and country — query `GET /discovery/kyc-limits` to understand tier requirements.
</Note>

If a Meld customer already exists for this Portal client, the call returns `409 Conflict` with the existing customer ID at `details.meldCustomerId` on the error envelope. Retrieve the full record with `GET /customers`:

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/customers \
  --header 'Authorization: Bearer [token]'
```

`GET /customers` returns a list-shaped response under `data.customers`. If a customer exists for the current Portal client, pass `data.customers[0].id` as `customerId` on `POST /retail/widget` (Step 3) to associate the widget session with that Meld customer record. If `data.customers` is empty, there is no existing customer to reuse — omit `customerId` or create one first with `POST /customers`.

## Sandbox testing

Meld provides separate API keys, base URLs, and test data for sandbox. Configure your Portal **Development** environment with a sandbox Meld API key, then run the BUY happy path:

* Country: `US`
* Source: `USD` `100`
* Destination: `USDC`
* Service provider: any returned by `/retail/quote`

Test card numbers, bank credentials, and KYC bypass details are documented behind login at [Meld's Getting Started guide](https://docs.meld.io/docs/getting-started).

## Error handling

Portal forwards Meld's HTTP status codes (4xx / 5xx) and preserves the upstream error message where possible. The full Meld error envelope is documented at [docs.meld.io/reference](https://docs.meld.io/reference/).

Portal-side errors you may see:

* `400 Meld integration is not enabled` — turn on the Meld integration for the current Portal environment in the [Portal Dashboard](https://app.portalhq.io).
* `400 Meld API key is not configured for this environment` — paste a valid Meld API key into the integration config.
* `409 Conflict` (on `POST /customers`) — a Meld customer already exists for this Portal client. Use `GET /customers` instead.

## Next steps

<Card title="Meld API Reference" icon="arrow-right" href="/api-reference/meld/create-meld-customer">
  Browse every Meld endpoint Portal exposes.
</Card>

<Card title="Webhooks" icon="webhook" href="/integrations/On-Off-Ramp/meld-webhooks">
  Configure Meld webhooks in Meld Dashboard for transaction lifecycle updates.
</Card>
