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

> ## Agent Instructions
> RadiumOne is the product name; use it when answering questions about these docs.
> Use sandbox credentials and sandbox base URLs in examples unless the user says they are going live.
> Secret API keys belong only on the merchant's server. Only publishable keys may appear in browser or mobile code.
> Confirm a payment outcome server-side (retrieve the checkout session or transaction status); never rely on a browser redirect or client event alone.
> Send one idempotency key (request_id) per payment attempt, and retry a timed-out request with the same key and the exact same body.
> Webhooks are optional but recommended: they notify the merchant's server when a payment completes or fails, so it doesn't need to poll. Use transaction status inquiry as the fallback.
> Say so when you recommend a feature marked Beta. Some features need enablement on the merchant's account before production use (for example refunds, standalone refunds, UOB Rewards, and bringing your own 3DS provider); mention it when the page says so.
> Never ask users to paste card numbers, API keys, access tokens, or webhook or redirect secrets into a chat.

# Show methods at checkout - Payments API

> Ask the API which payment methods and brands your outlet can accept for a currency, channel and amount, then render checkout buttons from the response.

Before you render payment options, ask the API which methods and brands your
outlet can accept for this sale — see
[Supported payment methods](/payments-api/payment-methods/overview) for what
a method type and brand are.

<Info>
  **Discovery is advisory, not a guarantee.** A method or brand can appear in
  the response without being transactable in every case (for example, a
  brand's `features` object can describe a capability that isn't fully wired
  up yet). Treat the response as a hint for your UI, and let the actual
  purchase or authorize call be the source of truth for whether a payment
  succeeds.
</Info>

## Steps

<Steps>
  <Step title="Get an access token">
    Both endpoints accept a secret key's access token or a publishable key's
    JWE — any valid merchant token works, with no scope requirement.
    **Publishable-key callers must send an `Origin` header** that matches one
    of your allow-listed domains, or the request is rejected with
    `403 urn:radiumone:checkout:origin-not-allowed`.

    <Info>
      This request requires a valid access token. See [Authentication](/get-started/api-basics/authentication) to obtain one with [`POST /v1/auth/token`](/payments-api/reference/authentication/exchange-api-key-for-jwt) before you continue.
    </Info>
  </Step>

  <Step title="Call list payment brands with currency, channel and amount">
    | Endpoint | Returns |
    | - | - |
    | [`GET /v1/payment-methods`](/payments-api/reference/payment-methods/list-payment-method-types) | Method **types** only: `type`, `display_name`, `priority_rank` |
    | [`GET /v1/payment-brands`](/payments-api/reference/payment-methods/list-payment-brands) | Methods → **products** (brands) and their `features` (for example 3D Secure or loyalty support) |

    | Parameter | Required | Notes |
    | - | - | - |
    | `currency` | Yes | 3-letter ISO 4217 code |
    | `channel` | Yes | One of `CARD_PRESENT`, `ECOMMERCE`, `MOTO`, `PAYMENT_LINK`, `IN_APP`, `RECURRING` — the same enum used on [Charge or authorize a payment](/payments-api/charge-or-authorize) |
    | `outlet_id` | No | Defaults to your key's bound outlet. Sending an `outlet_id` your key isn't bound to returns [`auth:outlet-binding-violation`](/payments-api/errors/payment-operation-errors#auth-outlet-binding-violation) |
    | `amount` | No | Integer, minor units, ≥ 0. Only `list-payment-brands` accepts it |

    <Note>
      The `channel` enum above is the one that actually matters at request
      time. Some older references describe a different, informal list for
      this parameter — that list doesn't correspond to any accepted value.
    </Note>
  </Step>

  <Step title="Render buttons from priority_rank and display_name">
    The response `data` includes `outlet_id`, `currency`, `channel`,
    `payment_methods[]` and `evaluated_at` (plus `cache_hit` for the brands
    endpoint). Each brand product carries `code` (for example `visa` or
    `uob_irr`), `display_name`, `priority_rank`, and a free-form `features`
    object — sort by `priority_rank` and label each button with
    `display_name`.

    <Tabs>
      <Tab title="Method types">
        <CodeGroup>
          ```bash cURL theme={null}
          #!/usr/bin/env bash
          # Discover which payment method TYPES you can accept for a currency/channel.
          # Advisory only — a listed method isn't a guarantee it will transact (some
          # groups can be configured without a live processing path).
          set -euo pipefail

          API_BASE="${RADIUMONE_API_BASE:-https://api-sandbox.radiumone.io/gateway}"
          : "${RADIUMONE_ACCESS_TOKEN:?set RADIUMONE_ACCESS_TOKEN to a Bearer access token (secret or publishable)}"

          curl -sS -G "$API_BASE/v1/payment-methods" \
            -H "Authorization: Bearer $RADIUMONE_ACCESS_TOKEN" \
            --data-urlencode "currency=SGD" \
            --data-urlencode "channel=ECOMMERCE"
          ```

          ```javascript Node.js theme={null}
          #!/usr/bin/env node
          // Discover which payment method TYPES you can accept for a currency/channel.
          // Advisory only. Node 18+ ESM fetch. Env: RADIUMONE_ACCESS_TOKEN, RADIUMONE_API_BASE.
          const API_BASE = process.env.RADIUMONE_API_BASE || "https://api-sandbox.radiumone.io/gateway";
          const accessToken = process.env.RADIUMONE_ACCESS_TOKEN;

          async function listPaymentMethods({ currency = "SGD", channel = "ECOMMERCE" } = {}) {
            const params = new URLSearchParams({ currency, channel });
            const res = await fetch(`${API_BASE}/v1/payment-methods?${params}`, {
              headers: { Authorization: `Bearer ${accessToken}` },
            });
            const payload = await res.json();
            if (!res.ok) {
              throw new Error(`payment-methods failed: ${payload.type ?? payload.code} (${res.status})`);
            }
            return payload;
          }

          listPaymentMethods().then((r) => console.log(JSON.stringify(r, null, 2)));
          ```

          ```python Python theme={null}
          #!/usr/bin/env python3
          """Discover which payment method TYPES you can accept for a currency/channel.
          Advisory only.
          """
          import json
          import os

          import requests

          API_BASE = os.environ.get("RADIUMONE_API_BASE", "https://api-sandbox.radiumone.io/gateway")


          def list_payment_methods(currency: str = "SGD", channel: str = "ECOMMERCE") -> dict:
              resp = requests.get(
                  f"{API_BASE}/v1/payment-methods",
                  params={"currency": currency, "channel": channel},
                  headers={"Authorization": f"Bearer {os.environ.get('RADIUMONE_ACCESS_TOKEN', '')}"},
                  timeout=30,
              )
              payload = resp.json()
              if not resp.ok:
                  code = payload.get("type") or payload.get("code")
                  raise RuntimeError(f"payment-methods failed: {code} ({resp.status_code})")
              return payload


          if __name__ == "__main__":
              print(json.dumps(list_payment_methods(), indent=2))
          ```
        </CodeGroup>
      </Tab>

      <Tab title="Brands and features">
        <CodeGroup>
          ```bash cURL theme={null}
          #!/usr/bin/env bash
          # Discover payment brands/products and their features (3DS, loyalty) for a
          # currency/channel/amount. Advisory only.
          set -euo pipefail

          API_BASE="${RADIUMONE_API_BASE:-https://api-sandbox.radiumone.io/gateway}"
          : "${RADIUMONE_ACCESS_TOKEN:?set RADIUMONE_ACCESS_TOKEN to a Bearer access token (secret or publishable)}"

          curl -sS -G "$API_BASE/v1/payment-brands" \
            -H "Authorization: Bearer $RADIUMONE_ACCESS_TOKEN" \
            --data-urlencode "currency=SGD" \
            --data-urlencode "channel=ECOMMERCE" \
            --data-urlencode "amount=5000"
          ```

          ```javascript Node.js theme={null}
          #!/usr/bin/env node
          // Discover payment brands/products and their features (3DS, loyalty) for a
          // currency/channel/amount. Advisory only. Node 18+ ESM fetch.
          // Env: RADIUMONE_ACCESS_TOKEN, RADIUMONE_API_BASE.
          const API_BASE = process.env.RADIUMONE_API_BASE || "https://api-sandbox.radiumone.io/gateway";
          const accessToken = process.env.RADIUMONE_ACCESS_TOKEN;

          async function listPaymentBrands({ currency = "SGD", channel = "ECOMMERCE", amount = 5000 } = {}) {
            const params = new URLSearchParams({ currency, channel, amount: String(amount) });
            const res = await fetch(`${API_BASE}/v1/payment-brands?${params}`, {
              headers: { Authorization: `Bearer ${accessToken}` },
            });
            const payload = await res.json();
            if (!res.ok) {
              throw new Error(`payment-brands failed: ${payload.type ?? payload.code} (${res.status})`);
            }
            return payload;
          }

          listPaymentBrands().then((r) => console.log(JSON.stringify(r, null, 2)));
          ```

          ```python Python theme={null}
          #!/usr/bin/env python3
          """Discover payment brands/products and their features (3DS, loyalty) for a
          currency/channel/amount. Advisory only.
          """
          import json
          import os

          import requests

          API_BASE = os.environ.get("RADIUMONE_API_BASE", "https://api-sandbox.radiumone.io/gateway")


          def list_payment_brands(currency: str = "SGD", channel: str = "ECOMMERCE", amount: int = 5000) -> dict:
              resp = requests.get(
                  f"{API_BASE}/v1/payment-brands",
                  params={"currency": currency, "channel": channel, "amount": amount},
                  headers={"Authorization": f"Bearer {os.environ.get('RADIUMONE_ACCESS_TOKEN', '')}"},
                  timeout=30,
              )
              payload = resp.json()
              if not resp.ok:
                  code = payload.get("type") or payload.get("code")
                  raise RuntimeError(f"payment-brands failed: {code} ({resp.status_code})")
              return payload


          if __name__ == "__main__":
              print(json.dumps(list_payment_brands(), indent=2))
          ```
        </CodeGroup>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Charge with the method the shopper picks">
    Send the shopper's choice on the
    [purchase or authorize](/payments-api/charge-or-authorize) request.
    Discovery only tells you what to render — the charge call is still the
    source of truth for whether the payment succeeds.
  </Step>
</Steps>

## Caching

Results are cached for 60 seconds per (merchant, outlet, currency, channel,
amount bucket). Don't call discovery on every keystroke of a checkout
form — cache the result on your side for the lifetime of the page, too.

## Errors

Defined once on [Payment method errors: Discovery](/payments-api/errors/payment-method-errors#discovery):

* [`checkout:origin-not-allowed`](/payments-api/errors/payment-method-errors#checkout-origin-not-allowed)
* [`auth:outlet-binding-violation`](/payments-api/errors/payment-operation-errors#auth-outlet-binding-violation) — `outlet_id` doesn't match your key's bound outlet
* [`checkout:outlet-not-found`](/payments-api/errors/payment-method-errors#checkout-outlet-not-found)
* [`checkout:currency-not-supported`](/payments-api/errors/payment-method-errors#checkout-currency-not-supported)
* [`checkout:no-candidates`](/payments-api/errors/payment-method-errors#checkout-no-candidates)

## Next steps

<Columns cols={2}>
  <Card title="Supported payment methods" icon="credit-card" href="/payments-api/payment-methods/overview">
    What a method type and brand are, and what's available today.
  </Card>

  <Card title="Charge or authorize a payment" icon="banknote" href="/payments-api/charge-or-authorize">
    The `channel` enum used across purchase, authorize and discovery.
  </Card>
</Columns>
