> For the complete documentation index, see [llms.txt](https://docs.fonbnk.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fonbnk.com/server-to-server/types.md).

# Types

Every enum and object type the API endpoints refer to, in one place.

### Currencies and channels

{% code overflow="wrap" expandable="true" %}

```typescript
enum CurrencyType {
  FIAT = 'fiat',
  CRYPTO = 'crypto',
  MERCHANT_BALANCE = 'merchant_balance',
}

enum PaymentChannel {
  BANK = 'bank',
  AIRTIME = 'airtime',
  MOBILE_MONEY = 'mobile_money',
  PAYBILL = 'paybill',           // Kenya, payout only
  BUY_GOODS = 'buy_goods',       // Kenya, payout only
  DIGITAL_WALLET = 'digital_wallet',
  MERCHANT_BALANCE = 'merchant_balance',
  CRYPTO = 'crypto',
}

type OrderCurrencyDetails =
  | OrderCryptoDetails
  | OrderFiatDetails
  | OrderMerchantDetails;

type OrderCryptoDetails = {
  network: string;
  asset: string;
  contractAddress?: string; // omitted on a native asset, never null
};

type OrderFiatDetails = {
  countryIsoCode: string;
  carrier?: { code: string; name: string; _id: string }; // the carrier chosen for this order
}

type OrderMerchantDetails = { merchantName: string }
```

{% endcode %}

{% hint style="info" %}
`contractAddress` is **omitted**, not `null`, on a native asset — test for absence rather than comparing against `null`. Note also that `OrderFiatDetails` carries the single `carrier` chosen for the order; the `carriers` **array** of everything a channel supports is a different shape, returned by [Get available currencies](/server-to-server/api-endpoints/get-available-currencies.md).
{% endhint %}

### Orders

{% code overflow="wrap" expandable="true" %}

```typescript
enum OrderStatus {
  DEPOSIT_AWAITING = 'deposit_awaiting',     // waiting for the user to pay
  DEPOSIT_VALIDATING = 'deposit_validating', // payment reported, being checked
  DEPOSIT_SUCCESSFUL = 'deposit_successful', // deposit checked out
  DEPOSIT_INVALID = 'deposit_invalid',       // wrong amount, narration or account
  DEPOSIT_CANCELED = 'deposit_canceled',     // cancelled before payment
  DEPOSIT_EXPIRED = 'deposit_expired',       // not paid in time
  PAYOUT_PENDING = 'payout_pending',         // sending the funds out
  PAYOUT_SUCCESSFUL = 'payout_successful',   // delivered
  PAYOUT_FAILED = 'payout_failed',           // delivery failed; retried via payout_pending
  REFUND_INITIATED = 'refund_initiated',     // refund decided, not started
  REFUND_PENDING = 'refund_pending',
  REFUND_SUCCESSFUL = 'refund_successful',
  REFUND_FAILED = 'refund_failed',           // retried via refund_initiated
}

enum OrderType {
  ON_RAMP = 'on_ramp',
  OFF_RAMP = 'off_ramp',
  SETTLEMENT = 'settlement',                             // merchant balance <-> fiat or crypto
  MERCHANT_BALANCE_DEPOSIT = 'merchant_balance_deposit', // you topping up your own balance
  MERCHANT_BALANCE_WITHDRAWAL = 'merchant_balance_withdrawal',
}

enum FlowType {
  REGULAR = 'regular',
  MERCHANT_BALANCE_CRYPTO = 'merchant_balance_crypto', // your own balance, via crypto
}

enum Source {
  WIDGET = 'widget', // created in the Pay Widget
  API = 'api',       // created through the Merchant API
}

enum OperationType {
  DEPOSIT = 'deposit',
  PAYOUT = 'payout',
  REFUND = 'refund',
}

type OrderStatusChangeLog = {
  oldStatus?: OrderStatus; // absent on a deposit_canceled entry
  newStatus: OrderStatus;
  date: Date;
};
```

{% endcode %}

Which transitions are real, and which statuses are actually final, is on [Order statuses](/server-to-server/order-statuses.md) — fewer are final than the names suggest. The full `Order` object every order endpoint returns is spelled out on [Get order](/server-to-server/api-endpoints/get-order.md).

### Pricing

{% code overflow="wrap" expandable="true" %}

```typescript
type Cashout = {
  exchangeRate: number;           // before fees
  exchangeRateAfterFees: number;  // amountBeforeFees / amountAfterFeesUsd, to 4 dp
  amountBeforeFees: number;
  amountAfterFees: number;
  amountBeforeFeesUsd: number;
  amountAfterFeesUsd: number;
  feeSettings: FeeSetting[];      // the rules that were applied
  chargedFees: ChargedFee[];      // what each rule cost, in the leg's currency
  chargedFeesUsd: ChargedFee[];   // ...and in USD
  totalChargedFees: number;
  totalChargedFeesUsd: number;
  chargedFeesPerRecipient: Partial<Record<FeeRecipient, number>>;
  chargedFeesPerRecipientUsd: Partial<Record<FeeRecipient, number>>;
};

type FeeSetting =
  | {
  id: string;
  recipient: FeeRecipient;
  type: FeeType.FLAT_AMOUNT;
  value: number;
  min: number;
  max: number | 'Infinity'
}
  | {
  id: string;
  recipient: FeeRecipient;
  type: FeeType.PERCENTAGE;
  value: number;      // percent
  min: number;        // amount range this rule applies to
  max: number | 'Infinity';
  minCap?: number;    // floor on the fee itself
  maxCap?: number     // ceiling on the fee itself
};

type ChargedFee = { id: string; type: FeeType; recipient: FeeRecipient; amount: number };

enum FeeRecipient { MERCHANT = 'merchant', PROVIDER = 'provider', PLATFORM = 'platform', BLOCKCHAIN = 'blockchain' }

enum FeeType { PERCENTAGE = 'percentage', FLAT_AMOUNT = 'flat_amount' }
```

{% endcode %}

`chargedFees` with `recipient: "merchant"` is your revenue on the order. `recipient: "blockchain"` is gas, and `recipient: "provider"` is the liquidity provider's cut. The platform's own fee has the id `service_fee`.

### Transfer instructions

{% code overflow="wrap" expandable="true" %}

```typescript
enum TransferType {
  MANUAL = 'manual',
  REDIRECT = 'redirect',
  STK_PUSH = 'stk_push',
  OTP_STK_PUSH = 'otp_stk_push',
  USSD = 'ussd',
}

type TransferInstructions =
  | ManualTransferInstructions
  | RedirectTransferInstructions
  | StkPushTransferInstructions
  | OtpStkPushTransferInstructions
  | UssdTransferInstructions;

type ManualTransferInstructions = {
  type: TransferType.MANUAL;
  instructionsText: string;
  warningText?: string;
  transferDetails: TransferDetail[];
  fieldsToConfirmOrder: RequiredField[];
};

type RedirectTransferInstructions = {
  type: TransferType.REDIRECT;
  paymentUrl: string;
  redirectedToPaymentUrl: boolean;
  intermediateActionButtonText: string;
  instructionsText: string;
  warningText?: string;
  transferDetails: TransferDetail[];
  fieldsToConfirmOrder: RequiredField[];
};

type StkPushTransferInstructions = {
  type: TransferType.STK_PUSH;
  isIntermediateActionAvailable: boolean;
  intermediateActionExecuted: boolean;
  intermediateActionButtonText: string;
  intermediateActionMaxAttempts: number;
  intermediateActionAttempts: number;
  intermediateActionNextAttemptAvailableAt: Date;
  intermediateActionTimeoutMs: number;
  instructionsText: string;
  warningText?: string;
  transferDetails: TransferDetail[];
  fieldsToConfirmOrder: RequiredField[];
};

type OtpStkPushTransferInstructions = {
  type: TransferType.OTP_STK_PUSH;
  otpChannel: 'sms' | 'ussd' | 'email' | 'whatsapp';
  otpAssociativeId?: string;
  intermediateActionRequired: boolean;   // OTP only
  intermediateActionExecuted: boolean;
  isIntermediateActionAvailable: boolean;
  intermediateActionButtonText: string;
  intermediateActionMaxAttempts: number;
  intermediateActionAttempts: number;
  intermediateActionNextAttemptAvailableAt: Date;
  intermediateActionTimeoutMs: number;
  fieldsForIntermediateAction: RequiredField[];
  instructionsText: string;
  warningText?: string;
  transferDetails: TransferDetail[];
  fieldsToConfirmOrder: RequiredField[];
};

type UssdTransferInstructions = {
  type: TransferType.USSD;
  ussdCode: string;
  instructionsText: string;
  warningText?: string;
  transferDetails: TransferDetail[];
  fieldsToConfirmOrder?: RequiredField[];
};

type TransferDetail = { id: TransferDetailId; label: string; description?: string; value?: string };

enum TransferDetailId {
  RECIPIENT_WALLET_ADDRESS = 'recipientWalletAddress',
  SENDER_WALLET_ADDRESS = 'senderWalletAddress',
  AMOUNT_TO_SEND = 'amountToSend',
  CRYPTO_TRANSACTION_REQUEST_ADDITIONAL_DATA = 'cryptoTransactionRequestAdditionalData',
  RECIPIENT_BANK_NAME = 'recipientBankName',
  RECIPIENT_BANK_ACCOUNT_NUMBER = 'recipientBankAccountNumber',
  RECIPIENT_BANK_ACCOUNT_NAME = 'recipientBankAccountName',
  RECIPIENT_BANK_BRANCH_CODE = 'bankBranchCode',
  RECIPIENT_BANK_BANK_SWIFT_CODE = 'bankSwiftCode',
  RECIPIENT_PHONE_NUMBER = 'recipientPhoneNumber',
  BANK_TRANSFER_NARRATION = 'bankTransferNarration',
}
```

{% endcode %}

`otpChannel` tells you how the code reached the user, so you can word your prompt correctly. Do not assume SMS. Render `transferDetails` as it arrives rather than picking known ids — the set grows.

What each transfer type means for your UI is on [Transfer types explanation](/server-to-server/integration-guide/transfer-types-explanation.md).

### Fields

{% code overflow="wrap" expandable="true" %}

```typescript
type RequiredField = {
  key: string;
  type: FieldType;
  label: string;
  required: boolean;
  options?: {
    value: string;
    label: string;
    iconUrl?: string;   // e.g. a bank logo
    featured?: boolean; // show this option first
  }[];
  defaultValue?: string;
};

enum FieldType {
  NUMBER = 'number',
  STRING = 'string',
  DATE = 'date',
  BOOLEAN = 'boolean',
  EMAIL = 'email',
  PHONE = 'phone',
  ENUM = 'enum',
}

// The same values echoed back on an order, labelled and typed for display.
type FormattedUserField = {
  key: string;
  label: string;
  value: string | number | boolean | Date;
  type: FieldType;
};

// Sandbox only. Set on fieldsToCreateOrder to force an outcome.
enum SandboxForcedFlow {
  DEPOSIT_SUCCESS = 'deposit_success',
  DEPOSIT_INVALID = 'deposit_invalid',
  DEPOSIT_UNDERPAYMENT = 'deposit_underpayment', // pays 50%
  DEPOSIT_OVERPAYMENT = 'deposit_overpayment',   // pays 200%
  PAYOUT_SUCCESS = 'payout_success',
  PAYOUT_FAILED = 'payout_failed',
}
```

{% endcode %}

{% hint style="info" %}
For any field with `type: "enum"`, the `options` array on that field is the authoritative list of accepted values — read it rather than hard-coding one. That applies to `bankCode`, `carrierCode`, and to the sandbox-only `depositSandboxForcedFlow` / `payoutSandboxForcedFlow` fields, which do not offer every member of the enum on every leg: a fiat deposit typically offers all four `deposit_*` values, a crypto deposit only `deposit_success` and `deposit_invalid`, and a payout leg only `payout_success` and `payout_failed`.
{% endhint %}

### KYC

{% code overflow="wrap" expandable="true" %}

```typescript
enum KycType { BASIC = 'basic', ADVANCED = 'advanced' }

// Treat this set as open-ended: historical records can carry values outside it,
// so branch on passedKycType rather than on an exact status match.
enum KycStatus { INITIATED = 'initiated', APPROVED = 'approved', REJECTED = 'rejected', INVALID = 'invalid' }

type KycDocument = { _id: string; title: string; value: string; type: KycType; requiredFields: DocumentField[] }

type DocumentField =
  | {
  key: string;
  type: 'number' | 'string' | 'date' | 'boolean' | 'email' | 'phone' | 'smile-identity-images';
  label: string;
  required: boolean;
  defaultValue?: string | number | boolean;
  regexp?: string;
  regexpFlags?: string;
  format?: string
}
  | {
  key: string;
  type: 'enum';
  label: string;
  required: boolean;
  options: { value: string; label: string }[];
  defaultValue?: string;
  regexp?: string;
  regexpFlags?: string;
  format?: string
};

// One KYC rule for a country. Per-order when min/max are set, aggregate when
// maxAmountUsd/maxOrdersCount are set, and possibly both at once.
type KycSetting = {
  operationType: OperationType;
  currencyType: CurrencyType;
  type: KycType;                  // the tier this rule demands
  min?: number;                   // per-order USD floor, inclusive
  max?: number | 'Infinity';      // per-order USD ceiling, exclusive
  maxOrdersCount?: number;        // lifetime successful order count
  maxAmountUsd?: number;          // lifetime successful USD volume
};
```

{% endcode %}

### Limits

Returned by [Get limits](broken://pages/ZVgb29BOl4YDGtkn1scf).

{% code overflow="wrap" expandable="true" %}

```typescript
type LimitRule = {
  id: string;
  criteria?: LimitCriteria; // AND of filters; omitted field is a wildcard
  limit: Limit;
};

type LimitCriteria = {
  side?: LimitSide;
  asset?: LimitAsset;
  paymentChannel?: string;
  currencyCode?: string;
  countryIsoCode?: string;
  merchantId?: string;
};

type Limit =
  | { type: 'per_tx_range'; value: { min?: number; max?: number } }
  | { type: 'window_volume'; accumulator: LimitAccumulator; window: LimitWindow; value: { max: number } }
  | { type: 'window_count';  accumulator: LimitAccumulator; window: LimitWindow; value: { max: number } };

enum LimitSide {
  DEPOSIT = 'deposit', // measured before fees
  PAYOUT = 'payout',   // measured after fees
}

enum LimitAsset {
  FIAT = 'fiat',
  CRYPTO = 'crypto',
  BALANCE = 'balance',   // a merchant balance moving for a user
  TREASURY = 'treasury', // a merchant funding or draining its own balance
}

enum LimitAccumulator { PLATFORM = 'platform', USER = 'user', MERCHANT = 'merchant' }

enum LimitWindow { DAILY = 'daily', WEEKLY = 'weekly', MONTHLY = 'monthly' }
```

{% endcode %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.fonbnk.com/server-to-server/types.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
