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

# AgentPay MCP Integration Guide

> Connect your AI agent to the AgentPay MCP server, pay customers' BBPS bills from their Cashfree wallet, test in sandbox and fix common issues.

This guide shows you how to connect your AI agent to the AgentPay MCP (Model Context Protocol) server and pay a customer's bill from their Cashfree AgentPay wallet. It also covers testing in sandbox and going live.

> [!NOTE]
>
> **Before you start, make sure you have:**
>
> * AgentPay access and a client API key for sandbox (for testing) or production (for live payments). To get both, contact the Cashfree sales team. They set up AgentPay for you and share your API keys. You don't need a Cashfree merchant account.
> * An AI agent or MCP client that supports the Streamable HTTP transport and custom request headers, such as Claude Code, Cursor, or a custom agent built with an MCP SDK.

## Connect your agent

Sandbox and production work the same way. Only the server URL and your API key change:

| Environment | MCP server URL | API key to use |
| - | - | - |
| Sandbox (testing) | `https://sandbox.cashfree.com/agentpay/mcp` | Your sandbox key |
| Production (live) | `https://mcp.cashfree.com/agentpay/mcp` | Your production key |

> [!WARNING]
>
> Always pair the sandbox key with the sandbox URL, and the production key with the production URL. A mismatch fails with HTTP 401.

Choose your client and add the server. The examples use the sandbox URL:

> [tab] Claude Code
>
> Run this command in your terminal:
>
> ```bash theme={"dark"}
> claude mcp add --transport http agentpay https://sandbox.cashfree.com/agentpay/mcp \
>   --header "Authorization: Bearer <CASHFREE_CLIENT_API_KEY>"
> ```
>
> Add `-s user` to make AgentPay available in all your projects. Run `claude mcp list` to check it. The output lists `agentpay` as connected.

> [tab] Cursor
>
> To add the server in Cursor:
>
> 1. Go to **Settings > Tools & Integrations**.
> 2. Select **Add Custom MCP**. Cursor opens your `mcp.json` file.
> 3. Add this entry and save the file:
>
>    ```json theme={"dark"}
>    {
>      "mcpServers": {
>        "agentpay": {
>          "type": "http",
>          "url": "https://sandbox.cashfree.com/agentpay/mcp",
>          "headers": {
>            "Authorization": "Bearer <CASHFREE_CLIENT_API_KEY>"
>          }
>        }
>      }
>    }
>    ```
>
> AgentPay appears in the list of MCP servers with its tools enabled.

> [tab] Custom agent
>
> Use any MCP client SDK with the Streamable HTTP transport. This example uses the TypeScript SDK:
>
> ```typescript theme={"dark"}
> // Requires: @modelcontextprotocol/sdk >= 1.10.0
> // Run as an ES module (for example, set "type": "module" in package.json) to use top-level await.
> import { Client } from "@modelcontextprotocol/sdk/client/index.js";
> import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
>
> const transport = new StreamableHTTPClientTransport(
>   new URL("https://sandbox.cashfree.com/agentpay/mcp"),
>   { requestInit: { headers: { Authorization: `Bearer ${process.env.CASHFREE_CLIENT_API_KEY}` } } },
> );
>
> const client = new Client({ name: "my-agent", version: "1.0.0" });
> await client.connect(transport);
>
> const { tools } = await client.listTools();          // 17 tools
> const result = await client.callTool({ name: "list_biller_categories", arguments: {} });
> console.log(result.content[0].text);                 // JSON string
> ```
>
> Give `tools` to your LLM as its tool definitions, and send its tool calls back through `client.callTool`. Also add the server's `instructions` (returned when you connect) to your system prompt. They tell the model how to talk to the customer. For a summary, see [Instructions the server gives your agent](https://www.cashfree.com/docs/tools-ai/agentpay-mcp/tool-reference.md#instructions-the-server-gives-your-agent).

> [tab] curl (for debugging)
>
> Send a request directly to check your key:
>
> ```bash theme={"dark"}
> curl -s -X POST https://sandbox.cashfree.com/agentpay/mcp \
>   -H "Content-Type: application/json" \
>   -H "Accept: application/json, text/event-stream" \
>   -H "Authorization: Bearer <CASHFREE_CLIENT_API_KEY>" \
>   -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_biller_categories","arguments":{}}}'
> ```
>
> A list of biller categories in the response means your key works and the server can reach Cashfree.

## Supported bill categories

The wallet can pay most bill categories, including Electricity, Water, Gas, LPG, Mobile Postpaid, Broadband, Landline, DTH, Cable TV, Insurance, Fastag, Education, Municipal Taxes, Rental, Subscription, Hospital, Donation and Clubs.

> [!WARNING]
>
> The wallet **can't pay Credit Card or Loan Repayment bills**. Don't offer billers in these categories. If a customer asks, tell them to pay that bill another way.

Some billers also don't accept wallet payments. `wallet_pay` refuses these with `wallet_not_supported_by_biller`.

## Choose a payment flow

Each biller in `list_billers` tells you whether your agent fetches the bill, validates the customer's details, or pays directly. Check `fetch_requirement` and `support_bill_validation`, then follow the matching flow:

| What `list_billers` returns | Flow | Where the amount comes from |
| - | - | - |
| `fetch_requirement` is `MANDATORY`, or `OPTIONAL` with `support_bill_validation` not `MANDATORY` | **Fetch the bill.** Follow [Pay a bill, step by step](#pay-a-bill-step-by-step). | The fetched bill |
| `fetch_requirement` is `NOT_SUPPORTED` with `support_bill_validation` `MANDATORY` or `OPTIONAL`, or `fetch_requirement` is `OPTIONAL` with `support_bill_validation` `MANDATORY` | **Validate first.** See [Validate-first billers](#validate-first-billers). | The customer |
| Both are `NOT_SUPPORTED` | **Pay directly**, with no fetch. See [Direct payments such as donations](#direct-payments-such-as-donations). | The customer |

## Pay a bill, step by step

Here is the whole journey at a glance for a biller whose bill your agent fetches. The consent step happens only the first time a customer pays:

```mermaid theme={"dark"}
flowchart LR
    A[1. Find the biller] --> B[2. Fetch the bill]
    B --> C{3. Confirm with<br/>the customer}
    C --> D[4. Get consent<br/>first time only]
    D --> E[5. Check the<br/>wallet balance]
    E --> F[6. Debit the wallet]
    F --> G[7. Pay the biller]
    G --> H[8. Share the receipt]
```

The following steps continue the BESCOM example from the [AgentPay MCP overview](https://www.cashfree.com/docs/tools-ai/agentpay-mcp/overview.md) and show the tool calls your agent makes at each stage.

**1. Find the biller**

Identify which biller the customer means. Use `list_biller_categories` to map words like "electricity" to a category, then search for the biller by name with `list_billers`:

```json Arguments theme={"dark"}
{ "biller_category_name": ["Electricity"], "biller_name": "BESCOM" }
```

The response gives you the `biller_id`, the details the customer must provide (in `biller_customer_params`), and which flow to follow (`fetch_requirement` and `support_bill_validation`). Here, BESCOM needs a Customer ID and the bill must be fetched.

> [!TIP]
>
> Always pass `biller_name`. Some categories have more than 600 billers. If several billers match, ask the customer which one is theirs rather than guessing.

**2. Fetch the bill**

Ask the customer only for the details the biller needs, plus their mobile number. Then request the bill with `request_bill_fetch`:

```json Arguments theme={"dark"}
{
  "biller_id": "BESCOM000KAR01",
  "customer_mobile": "9999999999",
  "input_params": [ { "param_name": "Customer ID / Account ID", "param_value": "1234567890" } ]
}
```

You get back a `ref_id`. The bill takes a moment to arrive, so call `poll_bill_fetch` with the `ref_id` every 2 to 3 seconds until `response_code` is `000`. Both the status and the bill are inside `data`, in `bill_fetch_response` and `biller_response`:

```json Response (excerpt) theme={"dark"}
{
  "status": "OK",
  "data": {
    "bill_fetch_response": { "ref_id": "4fd9fb282bf846a685a4301d15563031223", "response_code": "000", "response_reason": "Successful" },
    "biller_response": {
      "customer_name": "Rahul Sharma",
      "amount": "350.00",
      "cust_conv_fee": "0.00",
      "due_date": "2026-10-25",
      "bill_date": "2026-10-01",
      "bill_number": "INV-889201",
      "bill_period": "SEP-2026"
    }
  }
}
```

Save the `ref_id`. A `ref_id` is valid for 15 minutes, so if more time passes before you debit the wallet, fetch the bill again.

**3. Confirm with the customer**

Show the customer the name, amount and due date, and wait for a clear "yes, pay it". No money moves until they agree. Do this for every bill, even after the customer has approved your agent.

**4. Get the customer's consent (first time only)**

If you don't yet hold a token for this customer, call `create_mandate_session` with their mobile number. You get a `consent_link` to send them, ideally in the same message as the bill.

The customer opens the link, logs in with a one-time password (OTP), and approves a mandate for your agent. Meanwhile, call `get_mandate_session` with the `session_id` until the status is `CONFIRMED`. That response contains:

* An `access_token`, which lets you use the customer's wallet. It lasts about an hour.
* A `refresh_token`, which gets you a new `access_token` later without asking the customer again.
* The `terms` the customer agreed to, such as `max_amount_per_month` and `max_debits_per_month`.

Store the tokens and terms securely on your side, against this customer.

> [!TIP]
>
> In sandbox, the OTP on the consent page is always `111000`.

**5. Check the wallet balance**

Call `get_wallet_balance` with the `access_token`. Go ahead only when `eligibility` is `true`, `blocked` is `false`, `wallet_exists` is `true`, and `balance` covers the bill.

If the balance doesn't cover the bill, call `topup_wallet` to create a top-up and send the customer the `payment_link`. Call `get_topup_status` until the order is `PAID`, then check the balance again.

**6. Debit the wallet**

Call `wallet_pay` to take the money from the wallet. Use a new, unique `order_id` and pass the bill's `ref_id` as `reference_id`:

```json Arguments theme={"dark"}
{
  "order_id": "AP-9999999999-1761120000",
  "reference_id": "4fd9fb282bf846a685a4301d15563031223",
  "order_amount": 350,
  "access_token": "<ACCESS_TOKEN>"
}
```

The response says `ACCEPTED`, which isn't the final result yet. Call `get_pay_status` with the same `order_id` until `txstatus` is `SUCCESS`, and save the `transaction_id` it returns.

**7. Pay the biller**

Now tell the biller the bill is paid. Call `request_bill_payment` with the `transaction_id` from step 6 and the `ref_id` from step 2:

```json Arguments theme={"dark"}
{
  "pg_reference_id": "5114917215931",
  "bill_fetch_ref_id": "4fd9fb282bf846a685a4301d15563031223",
  "biller_id": "BESCOM000KAR01",
  "customer_mobile": "9999999999"
}
```

You don't send the bill details. The server reads the customer details, bill and amount back from the fetch, so they always match what the biller sent.

You get back a `transaction_ref_id`. Call `poll_bill_payment` with it and the `ref_id` until the status is `SUCCESS` or `FAILURE`.

**8. Share the receipt**

On `SUCCESS`, tell the customer the bill is paid and give them the `utr` (unique transaction reference). This is their proof of payment.

If the status is `FAILURE`, the wallet has already been debited, so don't charge it again. Refund the customer with `refund_payment` instead (see [Refunds](#refunds)).

## Flow variations

The step-by-step flow covers a first-time customer paying a fetched bill. The following variations change only some of the steps.

### Returning customers

Once a customer has approved your agent and you hold their tokens, skip the consent step entirely:

```text theme={"dark"}
Find the biller → Fetch the bill → Confirm with the customer → Check the wallet balance → Debit the wallet → Pay the biller → Share the receipt
```

If the `access_token` has expired, call `refresh_access_token` with the `refresh_token` first. Send a new consent link only if the refresh fails, which happens when the customer's consent expires or is revoked.

### Validate-first billers

Some billers can't return a bill, but they can check that the customer's details are valid. For these billers, the flow changes in the following places:

1. Call `request_bill_fetch` as usual. Cashfree validates the customer's details instead of fetching a bill, and the response has `flow` set to `VALIDATE_AND_PAY`.
2. Poll with `poll_bill_fetch` until `response_code` is `000`. The response confirms the details are valid but has no `biller_response`, so there's no bill amount.
3. Ask the customer how much to pay, and read it back to confirm.
4. In `wallet_pay`, pass the `ref_id` as `reference_id` and the customer's amount as `order_amount`.
5. In `request_bill_payment`, pass `bill_fetch_ref_id` as usual, plus `customer_params` (the same `param_name` and `param_value` pairs you sent to `request_bill_fetch`) and `amount` (the same amount as the debit).

The rest of the flow doesn't change.

### Direct payments such as donations

Some billers have no bill to fetch or validate, and donations are the most common example. The customer chooses the amount. For these billers, the flow changes in the following places:

1. Skip `request_bill_fetch` and `poll_bill_fetch`. Ask the customer for the details in `biller_customer_params` and the amount, and read them back to confirm.
2. In `wallet_pay`, send `biller_id` instead of `reference_id`.
3. In `request_bill_payment`, leave out `bill_fetch_ref_id`. Send `customer_params` (the biller's `biller_customer_params`, as `param_name` and `param_value` pairs) and `amount` (the same amount as the debit).
4. In `poll_bill_payment`, leave out `bill_fetch_ref_id`.

The amount must stay within the biller's limits for wallet payments.

## Handle failures

When a tool fails, the result has `isError: true` and a message that explains what went wrong, for example `Cashfree API error 400 for POST /pg/agentpay/wallet/pay: {...}`. For the full result format, see [Technical details](https://www.cashfree.com/docs/tools-ai/agentpay-mcp/tool-reference.md#technical-details). Return that message to your agent unchanged so it can recover. Tell the customer only that the agent couldn't fetch or pay the bill, and offer to try again.

### Recover without charging twice

The most important question after a failure is whether money has already left the wallet. Use this table to decide what to do:

| Where it failed | Has money moved? | What to do |
| - | - | - |
| Finding the biller, fetching the bill, consent or balance check | No | Retry freely. Fetch the bill again if the `ref_id` is more than 15 minutes old. |
| `wallet_pay` | Maybe | Check `get_pay_status` with the same `order_id` first. Retry with a new `order_id` only if the first debit definitely didn't happen. |
| `get_pay_status` shows `INITIATED` or `PENDING` | Maybe | Keep polling. Don't debit again or pay the biller until the status is final. |
| `request_bill_payment` | Yes | Fix the request and retry with the same `pg_reference_id`. Don't debit again. If the bill payment can't go through, refund the debit once it's 15 minutes old. |
| `poll_bill_payment` shows `FAILURE` | Yes | Don't debit again. Refund with `refund_payment`. |

### Refunds

If a bill payment fails after the wallet debit succeeds, return the money to the wallet:

1. Call `refund_payment` with the `order_id` of the debit, a unique `refund_id` you create, and the `refund_amount`.
2. Call `get_refund_status` until `refund_status` changes from `PENDING` to `SUCCESS`.

When `refund_status` is `SUCCESS`, the money is back in the wallet.

You can refund a debit whose `txstatus` is `SUCCESS` in either of these cases:

* The bill payment failed.
* The bill payment never reached the biller, and the debit is at least 15 minutes old. This covers a `ref_id` that expired between the debit and the bill payment.

You can't refund a bill that was paid, or a debit whose bill payment is still in progress. You can't refund more than you debited. Refunds still work after the customer's consent expires or is revoked, and the `access_token` is optional for them.

### Troubleshooting

Expand a problem to see the likely cause and fix.

#### Connection issues

> [accordion] HTTP 401 with authentication\_error when connecting
>
> The `Authorization: Bearer` header is missing or malformed, so the Cashfree gateway rejects the request before it reaches the server. Add the header to your MCP client configuration.

> [accordion] Cashfree API error 401
>
> Your API key is wrong or revoked, or you used a sandbox key with the production URL (or the other way round). Make sure the key matches the server URL. If it still fails, raise a support request through the [Merchant Support form](https://www.cashfree.com/contact-us/).

> [accordion] The server won't connect
>
> The URL is wrong, or your client has cached an old configuration. Check the URL and reconnect. In Claude Code, use `/mcp`.

#### Payment issues

> [accordion] list\_billers returns a very long or truncated list
>
> You didn't pass `biller_name`. Add it to narrow the search.

> [accordion] The bill fetch failed or expired
>
> A `ref_id` is valid for 15 minutes. Call `request_bill_fetch` again to get a fresh one.

> [accordion] request\_bill\_fetch says the biller doesn't support bill fetch
>
> This is a direct-payment biller, such as a donation. Skip the fetch and follow [Direct payments such as donations](#direct-payments-such-as-donations).

> [accordion] Wallet tools return 401 or 403
>
> The `access_token` is missing, wrong or expired. Call `refresh_access_token`. If that fails, send the customer a new consent link.

> [accordion] The wallet balance is too low
>
> Call `topup_wallet`, send the customer the `payment_link`, and wait until `get_topup_status` shows `PAID`.

> [accordion] The wallet is not eligible, is blocked or doesn't exist
>
> Read the `reason` field in the `get_wallet_balance` response. Usually, the fix is to start a new consent session.

> [accordion] wallet\_pay returns monthly\_debit\_limit\_exceeded
>
> The customer has reached their monthly limit and no money moved. Tell them, and suggest they pay another way or wait until next month.

> [accordion] wallet\_pay returns agent\_pay\_bill\_fetch\_required
>
> You sent `biller_id` for a biller that supports bill fetch or validation. Call `request_bill_fetch` and pay with its `ref_id` as `reference_id`.

> [accordion] wallet\_pay returns invalid\_order\_amount
>
> The amount breaks the biller's rules. For a fetched bill, it must follow the biller's `payment_amount_exactness`, for example the exact bill amount. Every amount must also stay within the biller's limits for wallet payments and have at most 2 decimal places.

> [accordion] wallet\_pay returns wallet\_not\_supported\_by\_biller
>
> This biller doesn't accept wallet payments. Tell the customer to pay this bill another way.

> [accordion] wallet\_pay returns order\_id\_conflict
>
> You reused an `order_id` for a different payment. Check `get_pay_status` for the first order before you try again with a new `order_id`. Every `order_id` must be unique across all your customers.

> [accordion] request\_bill\_payment says customer\_params and amount are required
>
> The bill was validated rather than fetched, or it's a direct payment. Send `customer_params` and `amount`, as described in [Flow variations](#flow-variations).

> [accordion] refund\_payment returns bill\_already\_paid or bill\_payment\_not\_terminal
>
> `bill_already_paid` means the biller received the payment, so you can't refund it. `bill_payment_not_terminal` means the bill payment is still in progress. Poll `poll_bill_payment` until it's final, then refund only if it failed.

> [accordion] Amounts are 100 times too big or too small
>
> You treated rupees as paise. All amounts in AgentPay are in rupees.

> [accordion] An ID isn't found or doesn't match
>
> A JSON parser rounded a long number. Keep all IDs as strings from start to finish.

## Security

Two kinds of credentials protect AgentPay: your API key, which identifies your business, and the customer tokens, which let your agent pay from a customer's wallet.

### Protect your API key

Your MCP client sends your API key as a Bearer token on every request. The server forwards it to Cashfree and never stores it. It handles each request separately, so you see only your own customers and payments. The server masks the key as `***` in any error message. Keep the key in your MCP client configuration or an environment variable, never in prompts or chat messages.

### Protect customer tokens

The tokens from the consent step let your agent pay on the customer's behalf. Follow these practices to keep them safe:

* **Never ask the customer for a token.** Customers only know their bill and mobile number. Your agent gets tokens from the consent flow, not from the customer.
* **Store tokens securely on your side**, encrypted and linked to the customer. The MCP server doesn't keep them, so you pass the `access_token` on every wallet call.
* **Reuse them.** The mandate is a standing permission, so don't send a new consent link for every bill.
* **Stay within the limits.** Track how much you've debited this month. Cashfree refuses debits that go over `max_amount_per_month` or `max_debits_per_month`.
* **Refresh when expired.** Use `refresh_access_token`. Calling `get_mandate_session` again doesn't extend the token.
* **Revoke when the customer asks.** If the customer wants your agent to stop paying from their wallet, call `revoke_consent`. Every token for that consent stops working. Payments already in progress still complete, and refunds remain possible.

## Test in sandbox

Before you go live, run the full flow in sandbox. Use these test values:

| Setting | Sandbox value |
| - | - |
| Server URL | `https://sandbox.cashfree.com/agentpay/mcp` |
| API key | Your sandbox client API key |
| Biller | Any `ACTIVE` Electricity biller, such as `BESCOM000KAR01`. For direct payments, use a Donation biller. |
| Bill details (`param_value`) | Any value that matches the biller's `regex`, for example `5552306001` for `BESCOM000KAR01` |
| Mobile number | Any 10-digit number |
| Consent OTP | `111000` |
| `order_id` | A new unique value for every debit, for example `AP-<mobile>-<timestamp>` |

To test, complete the following checks:

1. **First-time payment.** Follow every step in [Pay a bill, step by step](#pay-a-bill-step-by-step). It passes when `poll_bill_payment` returns `SUCCESS` with a `utr`.
2. **Returning customer.** Pay a second bill with the same `access_token`, without a new consent link.
3. **Direct payment.** Pay a Donation biller without fetching a bill.
4. **Validate-first biller.** If you plan to support these billers, pay one with an amount the customer gives.
5. **Revoke consent.** Call `revoke_consent`, then check that wallet tools reject the old `access_token`.
6. **Error handling.** Check that your agent responds sensibly to each of the following cases:
   * A missing `Authorization` header returns HTTP 401 with `"type": "authentication_error"`.
   * Reusing an `order_id` for a different payment returns `order_id_conflict`.
   * Going over `max_debits_per_month` returns `monthly_debit_limit_exceeded`.
   * Paying a Credit Card or Loan Repayment bill is refused.
   * A wrong `access_token` returns 401 or 403.

## Go live

When every sandbox check passes, complete the following steps to go live:

1. Replace your sandbox API key with your production API key.
2. Change the server URL to `https://mcp.cashfree.com/agentpay/mcp`.
3. Remove any sandbox-only values from your agent, such as the test OTP `111000`. In production, customers receive a real OTP on their mobile number.
4. Pay one small real bill end to end and check that the customer gets a `utr`.

Nothing else changes between sandbox and production.

## Get help

Raise a support request through the [Merchant Support form](https://www.cashfree.com/contact-us/). Include the `order_id`, `transaction_id`, `transaction_ref_id` and the time of the payment you're asking about.

Related topics

<ul>
  <li>[AgentPay MCP Tool Reference](https://www.cashfree.com/docs/tools-ai/agentpay-mcp/tool-reference.md)</li>
  <li>[Cashfree MCP Server](https://www.cashfree.com/docs/tools-ai/mcp-server.md)</li>
</ul>


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