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

# Fees API

> Add your own fee to Flow payments with a fee config, then check and claim fee balances that accrue to your recipients.

> [!NOTE]
>
> This is an enterprise-only feature. Please [contact us](https://www.dynamic.xyz/book-a-call) to enable.

You can add your own fee to a Flow payment: a percentage of the source amount, paid to wallets you name. Your server attaches a fee config when it creates the flow. The recipient does not have to be you; for example, a platform can pay the fee to its own merchants. [Adding your own fee](https://docs.dynamic.xyz/docs/flow/adding-your-own-fee.md) explains the concept, and this page gives the requests.

The fee is collected by the provider that routes the payment, so it applies to these payments:

* Deposit-address payments, whether or not the payment needs a conversion.
* Wallet payments that need a swap, because the payer's token or chain differs from your settlement.

Direct wallet transfers, where the payer already holds a settlement token on a settlement chain, and exchange payments carry no added fee. A flow with `disableSwaps` collects the fee on deposit-address payments only, because every wallet payment in that flow is a direct transfer.

Depending on the provider, a fee reaches the recipient in one of two ways:

* **Paid on chain with the payment.** The fee goes to the recipient's wallet as part of the payment, and there is nothing to claim.
* **Accrued as a claimable balance.** The fee accumulates off chain, typically as USDC, and the recipient claims it later with the [claim requests](#claim-accrued-fees). Claims are available for EVM recipients.

An integration that adds fees should handle both: check [claimable balances](#check-claimable-balances) periodically and claim what is waiting.

## Add a fee config

`feeConfig` is an optional field on the [create request](https://docs.dynamic.xyz/docs/flow/api.md#create-a-flow) in every mode, and on payment links. It is fixed when the flow is created.

```json theme={"system"}
{
  "amount": "25.00",
  "currency": "USD",
  "settlementConfig": { "...": "..." },
  "destinationConfig": { "...": "..." },
  "feeConfig": {
    "recipients": [
      {
        "walletAddress": "0xYourFeeRecipientWallet",
        "percentage": 0.01
      }
    ]
  }
}
```

| Field | Description |
| - | - |
| `feeConfig.recipients` | One or two recipients, with at most one EVM address and at most one Solana address |
| `feeConfig.recipients[].walletAddress` | The wallet that receives the fee and, for claims, signs to release it. A valid EVM (`0x…`) or Solana (base58) address |
| `feeConfig.recipients[].percentage` | The share of the source amount to collect, as a decimal strictly between `0` and `1`. For example, `0.01` is 1% |

The create request returns `422` when:

* there are no recipients, or more than one recipient in the same address family (EVM or Solana),
* a `percentage` is not strictly between `0` and `1`,
* the recipients' percentages add up to `1` or more, or
* a `walletAddress` is not a valid EVM or Solana address.

Each recipient's share is its `percentage` of the source amount, calculated independently.

## Supported chains for fees

A provider can pay a recipient only on the chain families it supports for a given payment, and it skips a recipient it cannot pay. The fee is not always paid on the chain the payer paid from: a payment that started on Solana can pay your fee to an EVM address. Name both an EVM and a Solana recipient to give each payment the best chance of carrying the fee:

```json theme={"system"}
"feeConfig": {
  "recipients": [
    {
      "walletAddress": "0xYourFeeRecipientWallet",
      "percentage": 0.01
    },
    {
      "walletAddress": "YourSolanaFeeRecipientWallet",
      "percentage": 0.01
    }
  ]
}
```

Use the same `percentage` for both. A payment pays only one of them, so your fee rate stays the same. Some payments carry no fee even with both recipients named, for example a deposit-address payment that starts and settles on Solana.

The payer can pay from any [supported chain](https://docs.dynamic.xyz/docs/flow/supported-chains.md). Fees to a Solana recipient arrive on chain with the payment. Fees that accrue to an EVM recipient are collected with the claim requests below.

## Check claimable balances

Check whether a recipient has fees waiting to be claimed. The response lists the balance per token and chain, and `hasClaimableFees` says whether anything is waiting. Paths are relative to `https://app.dynamic.xyz/api/v0`, and every fee request uses your API token with `flow.write`.

```
GET /server/{environmentId}/flow/fees/balances?recipientAddress=0xYourFeeRecipientWallet
Authorization: Bearer $DYNAMIC_API_TOKEN
```

| Query param | Description |
| :- | :- |
| `recipientAddress` | Required. The fee recipient's EVM wallet address |
| `chainId` | Optional filter. Only return balances settled on this chain |
| `tokenAddress` | Optional filter. Only return balances for this token contract address |

**Response (200):**

```json theme={"system"}
{
  "balances": [
    {
      "amount": "75021651210714015",
      "chainId": "8453",
      "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "amountUSD": "116.235557",
      "tokenSymbol": "USDC"
    }
  ],
  "hasClaimableFees": true
}
```

| Field | Description |
| :- | :- |
| `balances` | One entry per claimable token/chain balance. Empty when there is nothing to claim |
| `balances[].amount` | Claimable amount as a string in the token's smallest unit |
| `balances[].chainId` | Chain the balance can be claimed and settled on |
| `balances[].tokenAddress` | Token contract address the balance is denominated in |
| `balances[].amountUSD` | Approximate USD value, when available |
| `balances[].tokenSymbol` | Human-readable token symbol, when available |
| `hasClaimableFees` | `true` when there is at least one claimable balance |

> [!NOTE]
>
> An empty `balances` list (`hasClaimableFees: false`) means there is nothing to claim right now. Either no fees have accrued, or every fee was paid on chain with its payment.

**Error (400):** `recipientAddress` is not a valid EVM address.

## Claim accrued fees

A claim takes two requests. The first returns the messages the recipient wallet must sign, and the second submits the signatures. The recipient wallet's signature authorizes the release.

**1. Initiate the claim.**

```
POST /server/{environmentId}/flow/fees/claim
Authorization: Bearer $DYNAMIC_API_TOKEN
Content-Type: application/json
```

```json theme={"system"}
{
  "recipientAddress": "0xYourFeeRecipientWallet"
}
```

| Field | Description |
| :- | :- |
| `recipientAddress` | Required. The fee recipient's EVM wallet address |
| `chainId` | Optional filter. Only claim balances settled on this chain |
| `tokenAddress` | Optional filter. Only claim balances for this token contract address |

**Response (200):**

```json theme={"system"}
{
  "requestId": "e2b1c3d4-5678-90ab-cdef-1234567890ab",
  "steps": [
    {
      "id": "step-1",
      "type": "signature",
      "data": {
        "message": "0x0747b5e4d6cab0e29ad37654c41c2556524b85e0114ca2d61c76375bc98fb3a4",
        "signatureKind": "eip191"
      }
    }
  ]
}
```

| Field | Description |
| :- | :- |
| `requestId` | Identifier that ties the submit request to this claim. Store it |
| `steps` | One step per claimable balance. Each is a payload the recipient wallet must sign |
| `steps[].id` | Step identifier, echoed back as `stepId` when you submit its signature |
| `steps[].type` | The step type. `signature` is a message the recipient wallet must sign |
| `steps[].data.message` | The exact message to sign. Sign it as-is; do not reconstruct or modify it |
| `steps[].data.signatureKind` | How to sign the message. `eip191` is a `personal_sign` message |

An empty `steps` array means there was nothing to claim, so there is nothing to sign or submit.

**Error (400):** `recipientAddress` is not a valid EVM address.

**2. Sign each step** with the recipient wallet, then **submit the signatures.**

```
POST /server/{environmentId}/flow/fees/claim/submit
Authorization: Bearer $DYNAMIC_API_TOKEN
Content-Type: application/json
```

```json theme={"system"}
{
  "requestId": "e2b1c3d4-5678-90ab-cdef-1234567890ab",
  "signatures": [
    {
      "stepId": "step-1",
      "signature": "0xRecipientSignatureOverStep1Payload"
    }
  ]
}
```

| Field | Description |
| :- | :- |
| `requestId` | The `requestId` returned when you initiated the claim |
| `signatures` | One signature per step returned by initiate |
| `signatures[].stepId` | The `id` of the step this signature is for |
| `signatures[].signature` | The recipient's signature over that step's payload |

**Response (200):**

```json theme={"system"}
{
  "status": "DONE"
}
```

| Field | Description |
| :- | :- |
| `status` | The outcome of the claim: `DONE`, `PENDING`, or `FAILED` |
| `txHashes` | The on-chain transaction hashes of claims that settled on chain |

After a successful submit, check [balances](#check-claimable-balances) again. The claimed balance should now be empty.

### Example: claim in TypeScript

```typescript title="claim-fees.ts" theme={"system"}
import { type WalletClient } from 'viem';

const API = 'https://app.dynamic.xyz/api/v0';
const ENV_ID = process.env.DYNAMIC_ENVIRONMENT_ID!;
const API_TOKEN = process.env.DYNAMIC_API_TOKEN!; // needs flow.write

// The recipient wallet from feeConfig. It signs to authorize the claim.
declare const wallet: WalletClient;

const claimFees = async (recipientAddress: `0x${string}`) => {
  const headers = {
    Authorization: `Bearer ${API_TOKEN}`,
    'Content-Type': 'application/json',
  };

  // 1. Is there anything to claim?
  const balancesRes = await fetch(
    `${API}/server/${ENV_ID}/flow/fees/balances?recipientAddress=${recipientAddress}`,
    { headers },
  );
  const { hasClaimableFees } = await balancesRes.json();
  if (!hasClaimableFees) {
    return;
  }

  // 2. Initiate the claim and get the steps to sign.
  const initRes = await fetch(`${API}/server/${ENV_ID}/flow/fees/claim`, {
    body: JSON.stringify({ recipientAddress }),
    headers,
    method: 'POST',
  });
  const { requestId, steps } = await initRes.json();

  // 3. Sign each step's message with the recipient wallet.
  // data.signatureKind is `eip191`, which viem's signMessage produces.
  const signatures = await Promise.all(
    steps.map(async (step: { data: { message: string }; id: string }) => ({
      signature: await wallet.signMessage({
        account: recipientAddress,
        message: step.data.message,
      }),
      stepId: step.id,
    })),
  );

  // 4. Submit the signatures.
  const submitRes = await fetch(
    `${API}/server/${ENV_ID}/flow/fees/claim/submit`,
    {
      body: JSON.stringify({ requestId, signatures }),
      headers,
      method: 'POST',
    },
  );

  return submitRes.json();
};
```

> [!NOTE]
>
> Sign `data.message` exactly as returned, without reconstructing or changing it. `data.signatureKind` is `eip191`, a standard personal-sign message, so a library's `signMessage`, such as viem's, produces the right signature.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.