# Reconcile Stripe Payouts Against Charges

Yes. An agent matches last month's Stripe payouts to their balance transactions and writes a reconciliation with the lines that do not tie out.

[Run this on Rush](https://cloud.getrush.ai/run/reconcile-payouts/stripe/).

## What the agent does

There is no repo in resources. Stripe is connected as an MCP server: POST /me/connections with type mcp, name stripe, url https://mcp.stripe.com/ and auth mode oauth. The response is 202 with authorize_url. The key's creator opens it while signed in to the console, and the OAuth screen is where they pick the live account or a sandbox and the permissions. GET /me/connections then shows status connected. The session names connections: ["stripe"]. A key run gets only connections it names. The catalog description is payments and billing: customers, charges, subscriptions and balances, through that server.

The agent uses stripe_api_read, the tool that performs GET methods, and not stripe_api_write. The methods it needs are the ones Stripe documents for this server: List all payouts, Retrieve a payout, List all balance transactions, Retrieve a balance transaction. It lists payouts whose arrival falls in the previous calendar month, then lists the balance transactions that belong to each payout. Charges, refunds and fees show up as those transactions, which is why the prompt does not also walk the Charges list unless a payout's transactions do not sum.

payout-reconciliation.csv has one row per balance transaction: payout id, type, gross, fee, net. reconciliation.md totals each payout from those rows and compares the total to the payout amount List all payouts returned. A difference is listed with the payout id and the two numbers. Fees and refunds are called out in the note because they are transaction types in the same list, not a separate export. The agent does not create a refund, a payout or a transfer. stripe_api_write exists, and Stripe asks a person to confirm some writes, but this prompt never calls it.

If the account is a Connect platform, the default MCP session reads the platform account tied to the OAuth grant. A connected account needs Stripe-Account on the MCP server, which this catalog connection does not set. The report says which account get_stripe_account_info returned so a reviewer can see the scope. Test-mode and live-mode are different grants; the note names which one the connection used.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Use the Stripe connection. Call stripe_api_read only: list last month's payouts and the balance transactions in each (List all payouts, List all balance transactions). Do not call stripe_api_write. Write payout-reconciliation.csv with one row per balance transaction (payout id, type, gross, fee, net) and reconciliation.md that totals each payout, checks it against the payout amount, and lists any difference, including fees and refunds. Name the account the reads ran against. Read only."
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

Two artifacts come back: payout-reconciliation.csv and reconciliation.md. Each has a name, a mime type and a url on api.prix.dev. The url needs no key and lasts until url_expires_at, about an hour; read the session again for a new one. GET /o/{org}/sessions/{id}/artifacts/{name} with the key downloads either file. The webhook body will not hold the CSV, because result is included only up to 16 KiB.

The CSV is the line detail a close can import. The markdown is the check: one section per payout with the summed net, the payout amount, and a difference line only where they disagree. Fees and refunds are subtotals, not hidden inside charges. No sample totals are filled in when the connection returns an empty month; the note says the list was empty.

A reviewer ties one payout by hand before trusting the rest: pick a payout id, sum the net column for that id in the CSV, and compare it to the amount in the markdown and to the payout in the Stripe Dashboard. The session transcript has the stripe_api_read calls, so a missing payout is a page of results that stopped, which is visible, rather than a row the file dropped. Nothing is posted back to Stripe.

## Run it on every month close

On the first weekday of the month, your close job POSTs this body to https://api.prix.dev/o/{org}/sessions. Use an Idempotency-Key of the month (2026-09, for example) so a retry returns the same run until that run fails or is cancelled. A completed run keeps its key, so the next month needs a new key. No resources field: Stripe is the connection, not a GitHub repo.

Give the API key a webhook_url from an admin session. Rush sends run.completed when the turn finishes. Verify the signature with the whsec_ secret, then download both artifacts from the session. If the handler is down, the same webhook-id is retried three more times, 2, 4 and 6 minutes later, and then dropped; read session_url to catch up. A key's max_concurrent (default 4) is the cap on how many accounts you can reconcile at once.

## Why run it on Rush?

A payout export and a balance-transaction export from the Dashboard are two downloads that drift if someone issues a refund while you are in the spreadsheet. The agent reads both through stripe_api_read in one session, writes the CSV from those reads, and the transcript keeps the pages it fetched. The check against the payout amount is in the markdown, next to the lines, rather than in a formula only one laptop has.

Close week is many accounts and one person. Each account is its own session and its own sandbox, started together up to the key's concurrency limit. The sandbox parks after the turn, is free while parked, and stays for 7 idle days, so a bookkeeper can send a follow-up on the same session (POST /o/{org}/sessions/{id}/messages) asking for one payout id to be expanded, and the earlier reads are still there.

## Frequently asked questions

### Will it refund, pay out, or move money?

No. The prompt allows stripe_api_read and forbids stripe_api_write. Stripe also requires a person to confirm some writes, such as refunds, before they run. This job never asks for that. The output is a CSV and a note.

### Does it see live charges or only a test sandbox?

It sees the account the OAuth grant selected. Stripe's consent screen lets you grant a live account, a sandbox, or both, with permissions per environment. The report names the account get_stripe_account_info returned. A test-mode grant does not read live payouts.

### What about Connect connected accounts?

The catalog connection operates on the account of the OAuth session or key. Stripe's docs say a connected account is a Stripe-Account header on the MCP client, and that OAuth is not how you act for connected accounts. This job reconciles the account the connection is signed into. Say so in the prompt if you expected a connected account and the account id is the platform.

### The CSV is bigger than the webhook body. Where is it?

On the session, as artifacts. data.result in the webhook is omitted past 16 KiB. session_url is still there. Read the session with the key and use each artifact's url, or GET the artifact by name.
