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

# Noah virtual accounts and payouts

> Use portal.ramps.noah in the iOS SDK for Noah KYC, payins, payouts, and quotes through the Portal Client API.

The iOS SDK exposes [Noah](/integrations/On-Off-Ramp/noah) virtual accounts and global payouts through `portal.ramps.noah`. Each method issues HTTP requests through `portal.api` to Portal's Noah integration on the [Client API](/apis/quickstart) using your client API key. You do not call Noah's servers directly from the app.

<Note>
  For dashboard setup, signing keys, and supported CAIP-2 networks, see [Noah integration overview](/integrations/On-Off-Ramp/noah). For HTTP shapes and webhooks, see the [Noah workflow guides](/integrations/On-Off-Ramp/noah#workflow-guides) and [Noah Business API / EMM documentation](https://docs.noah.com/).
</Note>

## Prerequisites

* An [initialized `Portal` client](./getting-started) with a wallet and API access.
* Noah enabled for your Portal environment and [configured in the dashboard](/integrations/On-Off-Ramp/noah).
* For payins and payouts, the end user must complete [Noah KYC](/integrations/On-Off-Ramp/noah-kyc) with approved status before those flows succeed.

## Architecture

| Layer             | Role                                                                                                             |
| ----------------- | ---------------------------------------------------------------------------------------------------------------- |
| Your app          | Calls `portal.ramps.noah.*`                                                                                      |
| iOS SDK           | The `Noah` type at `portal.ramps.noah` issues authenticated HTTP requests through `portal.api` to the Portal API |
| Portal API        | `POST/GET …/api/v3/clients/me/integrations/noah/...` with the Portal client API key                              |
| Noah (via Portal) | Hosted KYC, banking rails, settlement                                                                            |

Prefer `portal.ramps.noah` over lower-level APIs. All request and response types are exported from `PortalSwift`.

## Supported networks

Every `network` parameter takes a CAIP-2 chain identifier. Use the constants on `NoahNetwork` rather than string literals so a typo is a compile error instead of a runtime failure.

| Constant                      | CAIP-2 value                              | Noah network          |
| ----------------------------- | ----------------------------------------- | --------------------- |
| `NoahNetwork.ethereum`        | `eip155:1`                                | `Ethereum`            |
| `NoahNetwork.ethereumSepolia` | `eip155:11155111`                         | `EthereumTestSepolia` |
| `NoahNetwork.base`            | `eip155:8453`                             | `Base`                |
| `NoahNetwork.baseSepolia`     | `eip155:84532`                            | `BaseTestSepolia`     |
| `NoahNetwork.polygon`         | `eip155:137`                              | `PolygonPos`          |
| `NoahNetwork.polygonAmoy`     | `eip155:80002`                            | `PolygonTestAmoy`     |
| `NoahNetwork.gnosis`          | `eip155:100`                              | `Gnosis`              |
| `NoahNetwork.gnosisChiado`    | `eip155:10200`                            | `GnosisTestChiado`    |
| `NoahNetwork.solana`          | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | `Solana`              |
| `NoahNetwork.solanaDevnet`    | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | `SolanaDevnet`        |

Any other value is rejected with `Unsupported network for Noah integration`. A non-CAIP-2 string such as `"ethereum"` is rejected earlier still, with `Network must be a "[namespace]:[reference]"`.

<Tip>
  Testnet networks pair with sandbox-only test assets such as `USDC_TEST`.
</Tip>

## Types and responses

Successful Client API responses use an envelope `{ data: T, metadata: NoahResponseMetadata? }`, where `NoahResponseMetadata` is a type alias for `[String: AnyCodable]`. For example, `NoahInitiateKycResponse` is `{ data: { hostedUrl: String } }`.

All nine methods are `async throws`. Failures surface as thrown errors — network problems, TLS issues, or an API error payload — and are handled with `do/catch` like any other async Portal call. See [Error handling](#error-handling).

## initiateKyc

Starts hosted Noah onboarding. Open `data.hostedUrl` in the system browser. Validate **HTTPS** and the **hostname** against the checkout domains Noah documents for your environment (extend the example allowlist accordingly).

```swift theme={null}
import PortalSwift
import UIKit

let portal = try Portal(
  "CLIENT_API_KEY_OR_CLIENT_SESSION_TOKEN",
  withRpcConfig: [
    "eip155:1": "https://api.portalhq.io/rpc/v1/eip155/1",
    "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": "https://api.devnet.solana.com",
  ]
)

let allowedHosts: Set<String> = [
  "checkout.noah.com",
  "checkout.sandbox.noah.com",
  "staging-checkout.noah.com",
]

do {
  let response = try await portal.ramps.noah.initiateKyc(
    request: NoahInitiateKycRequest(
      returnUrl: "https://yourapp.example/noah/return",
      fiatOptions: [NoahFiatOption(fiatCurrencyCode: "USD")],
      customerType: .individual
    )
  )

  if let url = URL(string: response.data.hostedUrl),
     url.scheme == "https",
     let host = url.host,
     allowedHosts.contains(host) {
    await UIApplication.shared.open(url)
  } else {
    // Handle this however your app prefers — surface it, log it, or fail the flow.
    print("Noah returned an unexpected KYC URL: \(response.data.hostedUrl)")
  }
} catch {
  print("initiateKyc failed: \(error)")
}
```

**Signature**

```swift theme={null}
public func initiateKyc(request: NoahInitiateKycRequest) async throws -> NoahInitiateKycResponse
```

| Parameter              | Type                    | Required | Description                                             |
| ---------------------- | ----------------------- | -------- | ------------------------------------------------------- |
| `request.returnUrl`    | `String`                | Yes      | HTTPS URL where Noah returns the user after onboarding. |
| `request.fiatOptions`  | `[NoahFiatOption]?`     | No       | Fiat currencies to present in onboarding.               |
| `request.customerType` | `NoahCustomerType?`     | No       | Onboarding flow variant — `.individual` or `.business`. |
| `request.metadata`     | `[String: AnyCodable]?` | No       | Opaque metadata forwarded per API rules.                |
| `request.form`         | `[String: AnyCodable]?` | No       | Optional prefill payload for hosted forms.              |

<Note>
  The `returnUrl` must be an HTTPS URL. Custom app schemes (for example `myapp://callback`) are not supported. For mobile applications, the recommended pattern is to use an HTTPS bridge page that redirects to a deep link after KYC completion.
</Note>

**Returns** — `NoahInitiateKycResponse`: `{ data: { hostedUrl: String } }`.

<Warning>
  This endpoint is **idempotent**. If a Noah customer record already exists for the client, the previously stored `hostedUrl` is returned regardless of KYC status (`Pending`, `Submitted`, `Approved`, `Declined`). Calling it a second time does not restart onboarding and does not mint a fresh URL.
</Warning>

<Note>
  This call only starts onboarding; KYC outcome and status changes arrive asynchronously via Noah **`Customer`** webhooks. See [Noah webhooks](/integrations/On-Off-Ramp/noah-webhooks).
</Note>

See also: [Noah KYC guide](/integrations/On-Off-Ramp/noah-kyc), [Noah hosted flows](https://docs.noah.com/).

## initiatePayin

Creates a fiat-to-stablecoin payin and returns bank instructions and a `payinId`. Use a [supported network](#supported-networks) and the user's wallet address as `destinationAddress`.

```swift theme={null}
let response = try await portal.ramps.noah.initiatePayin(
  request: NoahInitiatePayinRequest(
    fiatCurrency: "USD",
    cryptoCurrency: "USDC_TEST",
    network: NoahNetwork.solanaDevnet,
    destinationAddress: "SoLAddr1111111111111111111111111111111111111"
  )
)

print(response.data.payinId)
print(response.data.bankDetails.accountNumber)
```

**Signature**

```swift theme={null}
public func initiatePayin(request: NoahInitiatePayinRequest) async throws -> NoahInitiatePayinResponse
```

| Parameter                    | Type                         | Required | Description                                                                   |
| ---------------------------- | ---------------------------- | -------- | ----------------------------------------------------------------------------- |
| `request.fiatCurrency`       | `String`                     | Yes      | Fiat currency code (for example `USD`).                                       |
| `request.cryptoCurrency`     | `String`                     | Yes      | Noah crypto asset code (for example stablecoin test symbols in sandbox).      |
| `request.network`            | `String`                     | Yes      | CAIP-2 chain identifier. Use a [`NoahNetwork` constant](#supported-networks). |
| `request.destinationAddress` | `String`                     | Yes      | Address that receives crypto after settlement.                                |
| `request.businessFees`       | `[String: NoahBusinessFee]?` | No       | Custom business fee overrides keyed by payment method type.                   |

**Returns** — `NoahInitiatePayinResponse`: `{ data: { payinId: String, bankDetails: NoahBankDetails } }`.

`NoahBankDetails` includes:

* `paymentMethodId` — payment method identifier
* `paymentMethodType` — payment rail type, for example `BankSepa` or `IdentifierPix`
* `accountNumber` — bank account number
* `cryptoCurrency` — crypto currency for this payin
* `network` — network identifier
* `fee` — fee breakdown (`NoahFeeDetails`: `fiatCurrencyCode`, `totalFeePct`, `totalFeeBase`, `totalFeeMin`)
* `accountHolderName` — optional account holder name
* `bankCode` — optional bank routing or sort code
* `bankName` — optional bank name
* `bankAddress` — optional `NoahBankAddress` with `street`, `street2`, `city`, `postCode`, `state`, `country`
* `reference` — optional payment reference
* `relatedPaymentMethods` — optional `[NoahBankToAddressRelatedPaymentMethod]`

<Note>
  Payin lifecycle updates are asynchronous; track them with Noah **`FiatDeposit`** and **`Transaction`** webhooks, not by polling this SDK response. See [Noah webhooks](/integrations/On-Off-Ramp/noah-webhooks).
</Note>

See also: [Payins](/integrations/On-Off-Ramp/noah-payins), [FiatDeposit webhooks](https://docs.noah.com/api-concepts/webhooks/fiat-deposits/).

## simulatePayin

Simulates a fiat deposit against a payment method so you can exercise the payin flow end to end without moving real money.

```swift theme={null}
let response = try await portal.ramps.noah.simulatePayin(
  request: NoahSimulatePayinRequest(
    paymentMethodId: "pm-1",
    fiatAmount: "10",
    fiatCurrency: "USD"
  )
)

print(response.data.fiatDepositId)
```

**Signature**

```swift theme={null}
public func simulatePayin(request: NoahSimulatePayinRequest) async throws -> NoahSimulatePayinResponse
```

| Parameter                 | Type     | Required | Description                          |
| ------------------------- | -------- | -------- | ------------------------------------ |
| `request.paymentMethodId` | `String` | Yes      | Payment method identifier from Noah. |
| `request.fiatAmount`      | `String` | Yes      | Fiat amount as a decimal string.     |
| `request.fiatCurrency`    | `String` | Yes      | Fiat currency code.                  |

**Returns** — `NoahSimulatePayinResponse`: `{ data: { fiatDepositId: String, reference: String? } }`.

<Warning>
  This endpoint is **sandbox-only**. Calling it against a production environment fails with a `400` client error — `Simulate fiat deposit is only available in sandbox environments`. Guard the call behind your own environment check rather than shipping it in a production code path.
</Warning>

## getPayoutCountries

Lists countries available for fiat payouts, keyed by country code with the supported fiat currency codes for each.

```swift theme={null}
let response = try await portal.ramps.noah.getPayoutCountries()

for (country, currencies) in response.data.countries {
  print("\(country): \(currencies.joined(separator: ", "))")
}
```

**Signature**

```swift theme={null}
public func getPayoutCountries() async throws -> NoahGetPayoutCountriesResponse
```

**Returns** — `NoahGetPayoutCountriesResponse`: `{ data: { countries: [String: [String]] } }`.

## getPayoutChannels

Returns payout rails for a given crypto asset. Only `cryptoCurrency` is required; `country` and `fiatCurrency` narrow the results, and `fiatAmount` refines channel availability.

```swift theme={null}
let response = try await portal.ramps.noah.getPayoutChannels(
  request: NoahGetPayoutChannelsRequest(
    cryptoCurrency: "USDC_TEST",
    country: "US",
    fiatCurrency: "USD",
    fiatAmount: "10",
    pageSize: 10
  )
)

for channel in response.data.items {
  print(channel.id, channel.paymentMethodType, channel.rate)
}
```

**Signature**

```swift theme={null}
public func getPayoutChannels(request: NoahGetPayoutChannelsRequest) async throws -> NoahGetPayoutChannelsResponse
```

| Parameter                 | Type      | Required | Description                                                |
| ------------------------- | --------- | -------- | ---------------------------------------------------------- |
| `request.cryptoCurrency`  | `String`  | Yes      | Crypto asset code for the payout leg.                      |
| `request.country`         | `String?` | No       | ISO country code (for example `US`).                       |
| `request.fiatCurrency`    | `String?` | No       | Fiat currency for the payout.                              |
| `request.fiatAmount`      | `String?` | No       | Amount string used for filtering or quotes.                |
| `request.paymentMethodId` | `String?` | No       | Filter to channels compatible with a saved payment method. |
| `request.pageSize`        | `Int?`    | No       | Page size between 1 and 100, validated server-side.        |
| `request.pageToken`       | `String?` | No       | Pagination token returned from a previous response.        |

**Returns** — `NoahGetPayoutChannelsResponse`: `{ data: { items: [NoahChannel], pageToken: String? } }`.

Each `NoahChannel` includes:

* `id` — channel identifier
* `paymentMethodCategory` — broad grouping such as `Bank`, `Card`, or `Identifier`
* `paymentMethodType` — payment rail type, for example `BankSepa` or `IdentifierPix`
* `fiatCurrency` — fiat currency code
* `country` — ISO country code
* `limits` — `NoahChannelLimits` with `minLimit` and optional `maxLimit`
* `rate` — exchange rate as a string
* `processingSeconds` — estimated settlement time
* `calculated` — optional `NoahChannelCalculated` with `totalFee`
* `paymentMethods` — optional `[NoahChannelPaymentMethodDisplay]`, only populated when a customer id was supplied
* `processingTier` — optional settlement speed tier such as `Standard` or `Priority`
* `formSchema` — optional inline JSON Schema for the channel's payout form
* `formMetadata` — optional `NoahFormMetadata` with `contentHash`
* `issuer` — optional issuer identifier

<Tip>
  When a channel already carries a `formSchema`, use it directly instead of calling `getPayoutChannelForm` — it saves a round-trip.
</Tip>

See also: [Payouts](/integrations/On-Off-Ramp/noah-payouts).

## getPayoutChannelForm

Loads the dynamic form schema for a channel so you can collect recipient fields before requesting a quote. The `channelId` is percent-encoded as a single path segment.

```swift theme={null}
let response = try await portal.ramps.noah.getPayoutChannelForm(channelId: "ch-1")

// Render form fields from `response.data.formSchema` per Noah's schema
print(response.data.formMetadata?.contentHash ?? "no schema metadata")
```

**Signature**

```swift theme={null}
public func getPayoutChannelForm(channelId: String) async throws -> NoahGetPayoutChannelFormResponse
```

| Parameter   | Type     | Required | Description                                  |
| ----------- | -------- | -------- | -------------------------------------------- |
| `channelId` | `String` | Yes      | Channel identifier from `getPayoutChannels`. |

**Returns** — `NoahGetPayoutChannelFormResponse`: `{ data: { formSchema: [String: AnyCodable]?, formMetadata: NoahFormMetadata? } }`.

## getPayoutQuote

Requests fees and crypto amount estimates for a payout. Include `form` when the channel requires recipient data.

```swift theme={null}
let quote = try await portal.ramps.noah.getPayoutQuote(
  request: NoahGetPayoutQuoteRequest(
    channelId: "ch-1",
    cryptoCurrency: "USDC_TEST",
    fiatAmount: "10"
  )
)

print(
  quote.data.payoutId,
  quote.data.formSessionId,
  quote.data.cryptoAmountEstimate,
  quote.data.cryptoAuthorizedAmount,
  quote.data.totalFee
)
```

**Signature**

```swift theme={null}
public func getPayoutQuote(request: NoahGetPayoutQuoteRequest) async throws -> NoahGetPayoutQuoteResponse
```

| Parameter                 | Type                    | Required                              | Description                                                                                                        |
| ------------------------- | ----------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `request.channelId`       | `String`                | Yes                                   | Payout channel id.                                                                                                 |
| `request.cryptoCurrency`  | `String`                | Yes                                   | Crypto asset for the quote.                                                                                        |
| `request.fiatAmount`      | `String`                | One of `fiatAmount` or `cryptoAmount` | Fiat amount to receive, as a string.                                                                               |
| `request.cryptoAmount`    | `String`                | One of `fiatAmount` or `cryptoAmount` | Crypto amount to sell, as a string.                                                                                |
| `request.quoted`          | `Bool?`                 | No                                    | Request a signed, rate-locked quote. When `true`, the response includes a `quote` with `signedQuote` and `expiry`. |
| `request.form`            | `[String: AnyCodable]?` | No                                    | Recipient fields from the channel form.                                                                            |
| `request.fiatCurrency`    | `String?`               | No                                    | Fiat currency override when needed.                                                                                |
| `request.paymentMethodId` | `String?`               | No                                    | Payment method hint when applicable.                                                                               |
| `request.formSessionId`   | `String?`               | No                                    | Existing form session to continue, for multi-step forms.                                                           |
| `request.businessFee`     | `NoahBusinessFee?`      | No                                    | Custom business fee override for this quote.                                                                       |

`fiatAmount` and `cryptoAmount` are mutually exclusive, and `NoahGetPayoutQuoteRequest` enforces that in the type system rather than at runtime: there are two initializers, one taking `fiatAmount` and one taking `cryptoAmount`. Choosing an initializer chooses the denomination, and passing both or neither will not compile.

```swift theme={null}
// Quote a fiat amount to receive
NoahGetPayoutQuoteRequest(channelId: "ch-1", cryptoCurrency: "USDC_TEST", fiatAmount: "10")

// Quote a crypto amount to sell
NoahGetPayoutQuoteRequest(channelId: "ch-1", cryptoCurrency: "USDC_TEST", cryptoAmount: "10.5")
```

**Returns** — `NoahGetPayoutQuoteResponse`: includes `payoutId`, `totalFee`, `cryptoAmountEstimate`, `cryptoAuthorizedAmount`, `formSessionId`, and optional `cryptoCurrency`, `fiatCurrency`, `fiatAmount`, `rate`, `breakdown`, `quote` (`{ signedQuote, expiry }`), and `nextStep`.

Each `breakdown` entry is a `NoahTransactionBreakdownItem` with a `type` of `ChannelFee`, `BusinessFee`, or `Remaining`, plus an `amount`.

<Note>
  Set `quoted: true` if you intend to submit the payout with a `NoahQuotedOnchainDepositSourceTriggerInput` trigger — that variant requires the `signedQuote` returned here.
</Note>

## initiatePayout

Executes a payout after quoting. Returns the destination address and the on-chain deposit conditions your transfer must satisfy.

```swift theme={null}
let formatter = ISO8601DateFormatter()
let expiry = formatter.string(from: Date().addingTimeInterval(24 * 60 * 60))

// Use a stable nonce per payout attempt and reuse it on retries (max 36 characters)
let nonce = String(UUID().uuidString.prefix(36))

let response = try await portal.ramps.noah.initiatePayout(
  request: NoahInitiatePayoutRequest(
    payoutId: quote.data.payoutId,
    sourceAddress: "SoLAddr1111111111111111111111111111111111111",
    expiry: expiry,
    nonce: nonce,
    network: NoahNetwork.solanaDevnet
  )
)

print(response.data.destinationAddress ?? "no destination address")
print(response.data.conditions ?? [])
```

**Signature**

```swift theme={null}
public func initiatePayout(request: NoahInitiatePayoutRequest) async throws -> NoahInitiatePayoutResponse
```

| Parameter               | Type                               | Required | Description                                                                                                                 |
| ----------------------- | ---------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `request.payoutId`      | `String`                           | Yes      | Identifier from `getPayoutQuote`.                                                                                           |
| `request.sourceAddress` | `String`                           | Yes      | Address funding the crypto leg.                                                                                             |
| `request.expiry`        | `String`                           | Yes      | ISO-8601 expiry for the deposit authorization.                                                                              |
| `request.nonce`         | `String`                           | Yes      | Stable nonce for this payout attempt; **reuse on retry** so repeated calls stay idempotent. Must be 36 characters or fewer. |
| `request.network`       | `String`                           | Yes      | CAIP-2 network for the deposit leg. Use a [`NoahNetwork` constant](#supported-networks).                                    |
| `request.trigger`       | `NoahOnchainDepositSourceTrigger?` | No       | Explicit on-chain deposit trigger. See [Deposit source triggers](#deposit-source-triggers).                                 |
| `request.businessFee`   | `NoahBusinessFee?`                 | No       | Custom business fee override for this payout.                                                                               |

**Returns** — `NoahInitiatePayoutResponse`: `{ data: { destinationAddress: String?, conditions: [NoahDepositSourceTriggerCondition]?, ruleId: String? } }`.

`destinationAddress` is optional because it is derived from `conditions[0].destinationAddress` and falls back to `nil` when that shape is missing. `ruleId` is returned for permanent and quoted triggers.

### Deposit source triggers

The payout must be authorized by a trigger. Either supply one explicitly, or rely on a saved payment method attached to the quote via `paymentMethodId` — when `trigger` is omitted and the saved method is a single on-chain deposit source, a default trigger is synthesised from the `sourceAddress`, `expiry`, and `nonce` you passed.

`NoahOnchainDepositSourceTrigger` is an enum with three cases:

```swift theme={null}
public enum NoahOnchainDepositSourceTrigger: Codable {
  case single(NoahSingleOnchainDepositSourceTriggerInput)
  case permanent(NoahPermanentOnchainDepositSourceTriggerInput)
  case quoted(NoahQuotedOnchainDepositSourceTriggerInput)
}
```

| Case         | Use it for                                                                                                              |
| ------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `.single`    | One payout matching specific amount conditions on a network.                                                            |
| `.permanent` | A source that stays active across multiple deposits until expiry. Set `networkAgnostic` to match any supported network. |
| `.quoted`    | A rate-locked payout. Requires the `signedQuote` from a `getPayoutQuote` call made with `quoted: true`.                 |

The wire format is discriminated by a `Type` field, so the enum encodes its wrapped value **transparently** — no extra nesting is added to the JSON. Each trigger input defaults its `type` property to the correct discriminator, so you do not need to set it:

```swift theme={null}
let trigger = NoahOnchainDepositSourceTrigger.single(
  NoahSingleOnchainDepositSourceTriggerInput(
    conditions: [
      NoahSingleOnchainDepositSourceTriggerCondition(
        amountConditions: [
          NoahSingleOnchainDepositSourceTriggerAmountCondition(
            comparisonOperator: .eq,
            value: "10.5"
          )
        ],
        network: NoahNetwork.solanaDevnet
      )
    ],
    sourceAddress: "SoLAddr1111111111111111111111111111111111111",
    expiry: expiry,
    nonce: nonce
  )
)
```

Trigger inputs serialize their fields in PascalCase (`Type`, `Conditions`, `SourceAddress`, `Expiry`, `Nonce`) to match the Noah API wire format.

<Note>
  After this call returns `destinationAddress` and `conditions`, submit the on-chain transfer to satisfy them. You can do this with any wallet — including Portal's own [send method](./send-tokens) (`portal.sendAsset(...)`) on the same `Portal` instance, which builds, signs, and broadcasts in one call.
</Note>

<Note>
  This call initiates the payout flow; completion and failures are reported asynchronously via Noah **`Transaction`** webhooks. See [Noah webhooks](/integrations/On-Off-Ramp/noah-webhooks).
</Note>

See also: [Transaction events](https://docs.noah.com/api-concepts/webhooks/transactions/), [automated payout recipes](https://docs.noah.com/recipes/payout/automated-payouts).

## getPaymentMethods

Returns payment methods available to the customer, including a pagination token when more results are available.

```swift theme={null}
let response = try await portal.ramps.noah.getPaymentMethods(
  request: NoahGetPaymentMethodsRequest(
    pageSize: 10,
    capability: .payoutTo
  )
)

print(response.data.paymentMethods.count, response.data.pageToken ?? "no more pages")
```

Call the no-argument overload to use server-side defaults:

```swift theme={null}
let response = try await portal.ramps.noah.getPaymentMethods()
```

**Signature**

```swift theme={null}
public func getPaymentMethods(request: NoahGetPaymentMethodsRequest) async throws -> NoahGetPaymentMethodsResponse
```

| Parameter            | Type                           | Required | Description                                                                                                                                                                       |
| -------------------- | ------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `request.pageSize`   | `Int?`                         | No       | Maximum number of payment methods to return. Must be an integer between 1 and 100 — validated server-side, and out-of-range values are rejected with a `400` rather than clamped. |
| `request.pageToken`  | `String?`                      | No       | Cursor from a previous response's `data.pageToken`.                                                                                                                               |
| `request.capability` | `NoahPaymentMethodCapability?` | No       | Filter by capability — `.payoutFrom`, `.payinTo`, or `.payoutTo`.                                                                                                                 |

**Returns** — `NoahGetPaymentMethodsResponse`: `{ data: { paymentMethods: [NoahPaymentMethod], pageToken: String? } }`.

Each `NoahPaymentMethod` includes `id`, `paymentMethodCategory`, `country`, and `displayDetails`, plus optional `customerId`, `capabilities`, `accountHolderDetails`, and `issuerDetails`.

## Testing and mocking

`Ramps.noah` is a `var` typed as `NoahProtocol`, so you can substitute your own conformer in tests:

```swift theme={null}
final class MockNoah: NoahProtocol {
  func initiateKyc(request: NoahInitiateKycRequest) async throws -> NoahInitiateKycResponse {
    NoahInitiateKycResponse(data: NoahInitiateKycData(hostedUrl: "https://checkout.sandbox.noah.com/session"))
  }

  // … implement the remaining methods
}

portal.ramps.noah = MockNoah()
```

`Ramps.init(api:)` is internal, so replace the `noah` property rather than the `Ramps` instance.

`Noah.init(api: PortalNoahApiProtocol)` and `PortalNoahApi.init(apiKey:apiHost:requests:)` are both public, so you can also stay on the real domain type and swap the transport layer instead:

```swift theme={null}
portal.ramps.noah = Noah(api: MockNoahApi())
```

## Error handling

All Noah methods are async throwing functions. Wrap calls in a `do/catch` block. Log or surface errors without printing full API responses in production if they might contain sensitive identifiers. Retry only idempotent reads unless your product team confirms otherwise.

The following errors may be thrown:

| Error                                           | Description                                                                                                                         |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `PortalRequestsError.unauthorized`              | Authentication failed (401). Verify your Portal client is properly initialized and that Noah is enabled for your environment.       |
| `PortalRequestsError.clientError`               | Client error (4xx). The request was invalid, or the operation is not permitted — for example calling `simulatePayin` in production. |
| `PortalRequestsError.internalServerError`       | Server error (5xx). A server-side issue occurred.                                                                                   |
| `PortalRequestsError.couldNotParseHttpResponse` | The response could not be parsed.                                                                                                   |
| `URLError.badURL`                               | The request URL could not be built.                                                                                                 |
| `PortalApiError.unableToEncodeData`             | A `channelId` passed to `getPayoutChannelForm` could not be percent-encoded.                                                        |

```swift theme={null}
do {
  let response = try await portal.ramps.noah.getPayoutCountries()
  print(response.data.countries)
} catch PortalRequestsError.unauthorized {
  print("Check your Portal client API key and Noah configuration")
} catch {
  print("getPayoutCountries failed: \(error)")
}
```

## Related documentation

* iOS SDK reference — [`initiateKyc`](../reference/noahinitiatekyc), [`initiatePayin`](../reference/noahinitiatepayin), [`simulatePayin`](../reference/noahsimulatepayin), [`getPayoutCountries`](../reference/noahgetpayoutcountries), [`getPayoutChannels`](../reference/noahgetpayoutchannels), [`getPayoutChannelForm`](../reference/noahgetpayoutchannelform), [`getPayoutQuote`](../reference/noahgetpayoutquote), [`initiatePayout`](../reference/noahinitiatepayout), [`getPaymentMethods`](../reference/noahgetpaymentmethods)
* [Noah integration overview](/integrations/On-Off-Ramp/noah)
* [KYC](/integrations/On-Off-Ramp/noah-kyc), [Payins](/integrations/On-Off-Ramp/noah-payins), [Payouts](/integrations/On-Off-Ramp/noah-payouts), [Webhooks](/integrations/On-Off-Ramp/noah-webhooks)
* [Noah docs — API concepts](https://docs.noah.com/api-concepts/transactions/)
* [Noah docs — authentication & signing](https://docs.noah.com/api-concepts/authentication/signing/)
