Skip to main content

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.

Overview

Meld’s White-Label API 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 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.

Prerequisites

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

curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/discovery/countries \
  --header 'Authorization: Bearer [token]'
Example response (truncated):
{
  "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

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

Crypto currencies

curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/discovery/crypto-currencies \
  --header 'Authorization: Bearer [token]'
Meld doesn’t accept a separate chain parameter — each token has a distinct currencyCode per chain (a “Meld Code”). See Meld’s cryptocurrency coverage for the full list.

Payment methods

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.
Cache discovery responses client-side. Countries, currencies, and payment methods change rarely and re-querying on every page load adds unnecessary latency.

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.
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
  }'
FieldTypeRequiredDescription
countryCodestringYesISO-3166-1 alpha-2 country code (for example US).
sourceCurrencyCodestringYesSource currency (fiat for buy, crypto for sell).
destinationCurrencyCodestringYesDestination currency.
sourceAmountnumberYesAmount of source currency to convert.
walletAddressstringNoDestination wallet address (informs provider eligibility).
paymentMethodTypestringNoFilter to a specific payment method (for example CREDIT_DEBIT_CARD).
serviceProvidersstring[]NoRestrict the quote to specific providers.
subdivisionstringNoState/region code, when required by the country.
Whichever side carries crypto — destinationCurrencyCode on a buy, sourceCurrencyCode on a sell, or both on a transfer — uses a Meld Code 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.
Example response (truncated):
{
  "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).
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"
    }
  }'
FieldTypeRequiredDescription
sessionType"BUY" | "SELL" | "TRANSFER"YesThe flow direction.
sessionData.countryCodestringYesISO-3166-1 alpha-2 country code.
sessionData.sourceAmountstringYesSource amount (string-encoded decimal).
sessionData.sourceCurrencyCodestringYesSource currency.
sessionData.destinationCurrencyCodestringYesDestination currency.
sessionData.serviceProviderstringYesProvider chosen from the quote response.
sessionData.walletAddressstringNoDestination wallet address. Pre-fills the widget.
sessionData.walletTagstringNoMemo / destination tag for chains that require it.
sessionData.paymentMethodTypestringNoPre-selects the payment method.
sessionData.lockFieldsstring[]NoField names the user cannot modify (for example ["walletAddress"]). See Meld’s docs.
sessionData.redirectUrlstringNoURL Meld redirects to when the user finishes the flow.
externalSessionIdstringNoYour reference for this session (recorded on Meld’s transaction).
customerIdstringNoMeld customerId if you have pre-created a customer.
bypassKycbooleanNoSkip KYC where allowed by the provider/jurisdiction. Use cautiously.
Example response:
{
  "data": {
    "id": "ses_01HZZZZ",
    "token": "eyJhbGciOi...",
    "customerId": "cust_01HXXXX",
    "externalCustomerId": "cmnescm57000a14ebp8oeqj4k--cmnescm5h000c14ebofkcqdkr--cmnesxy85000346cmwyknl1v7",
    "externalSessionId": "",
    "widgetUrl": "https://meldcrypto.com/?token=eyJhbGciOi..."
  }
}
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 for iframe sizing guidance and supported lockFields values.

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:
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:
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:
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/meld/retail/transactions \
  --header 'Authorization: Bearer [token]'
Example response:
{
  "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"
    }
  }
}
Prefer Meld webhooks over polling for transaction lifecycle updates. Configure them in Meld Dashboard — see Webhooks.

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.
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:
{
  "data": {
    "id": "cust_01HXXXX",
    "accountId": "acct_01HYYYY",
    "externalId": "cmnescm57000a14ebp8oeqj4k--cmnescm5h000c14ebofkcqdkr--cmnesxy85000346cmwyknl1v7",
    "name": { "firstName": "Jane", "lastName": "Doe" },
    "email": "jane@example.com",
    "type": "INDIVIDUAL",
    "status": "ACTIVE"
  }
}
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.
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:
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.

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

Meld API Reference

Browse every Meld endpoint Portal exposes.

Webhooks

Configure Meld webhooks in Meld Dashboard for transaction lifecycle updates.