agent-stripe

Read-only Stripe CLI for AI agents. Use for any read-only Stripe question — customers, charges, payment intents, refunds, disputes, balance, payouts, subscriptions, invoices, products, prices, events, and Connect (connected accounts, application fees, and reading a connected account's data via --stripe-account). This skill is read-only — it cannot create, modify, or delete anything in Stripe.

simonperryman/agent-stripe5 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: agent-stripe
description: Read-only Stripe CLI for AI agents. Use for any read-only Stripe question — customers, charges, payment intents, refunds, disputes, balance, payouts, subscriptions, invoices, products, prices, events, and Connect (connected accounts, application fees, and reading a connected account's data via --stripe-account). This skill is read-only — it cannot create, modify, or delete anything in Stripe.
license: MIT
---

# agent-stripe

`agent-stripe` is a read-only CLI wrapper around the Stripe API, designed for AI agents. It is **read-only**: the HTTP transport rejects non-GET requests at the chokepoint in `internal/stripe/readonly.go`, so no command can write, even by accident. Every response is a JSON envelope with `mode`, `account`, `apiVersion`, and `data` — predictable shape, no prose.

## When to invoke

Reach for `agent-stripe` whenever the question is read-only Stripe:

- "what charges did customer X have this week"
- "why didn't this subscription renew"
- "find the payment for invoice in_…"
- "what's in our test-mode events feed since yesterday"
- "what fields are on a Stripe Subscription"
- "is this dispute still open"
- "why can't this connected account take payments / receive payouts"
- "where is the charge for merchant acct_… — I can't find it"

Skip it when the user wants to *do* something to Stripe (create, refund, cancel, etc.). This skill cannot help with writes — direct them to the Stripe Dashboard or the Stripe CLI.

## Command surface

```
account     add | remove | list | test | set-default     # local key management
customer    get | list | search                          # cus_…
charge      get | list | search                          # ch_…
payment-intent  get | list | search                      # pi_…
refund      get | list                                   # re_…
dispute     get | list                                   # dp_…
balance     get | transaction <txn_…> | transactions      # ledger entries
payout      get | list                                   # po_…
event       get | list [--related <id>]                  # evt_…
subscription get | list | search                         # sub_…
invoice     get | list | search                          # in_…
product     get | list | search                          # prod_…
price       get | list | search                          # price_…
coupon      get | list                                   # discount definition
promotion-code  get | list [--code LAUNCH20]             # promo_…
test-clock  get | list                                   # clock_… (test mode only)
connected-account get | list | capabilities | persons | external-accounts   # acct_…
application-fee get | list | refunds                     # fee_…
resource    describe <name> [--depth N]                  # shape discovery, no API call
```

Global flags: `--account <alias>` · `--stripe-account <acct_…>` · `--live` · `--full` · `--expand <fields|paths>` · `--expand-stripe <paths>` · `--raw` · `--api-version <date>` · `--stream` · `--timeout <dur>`.

`agent-stripe <command> usage` is the source of truth for flags — don't trust this file for flag-level detail; read the CLI's own help.

## The read-only guarantee

Every Stripe API call goes through `internal/stripe/readonly.go`, which wraps `http.RoundTripper` and returns `ErrReadOnly` on any non-GET request before it leaves the process. There is no `--force` to bypass it. Commands that look like they could write (`account add`, `account remove`) only touch local keychain state, never Stripe.

This guarantee is the reason you should reach for `agent-stripe` early on Stripe questions: there's no detection-evasion or oops-I-deleted-a-customer risk.

## Common workflows

### "Why did this charge fail?"

```
agent-stripe charge get ch_… --full --expand-stripe customer
```

`outcome.seller_message` and `failure_message` are the high-signal fields. `--full` skips truncation so the messages aren't cut.

### Reconcile a charge to an invoice

```
agent-stripe charge get ch_…
# → invoice id in response
agent-stripe invoice get in_… --expand-stripe customer,subscription
```

### "What happened to this object?"

`event list --related <id>` is the audit trail for any Stripe object:

```
agent-stripe event list --related sub_… --limit 50
```

Under `--stream`, `event list --related` emits a final trailing line
`{"_truncated":bool,"scanned":N,"matched":M}` after the records — it's the
only command whose stream has more than `header + records`. Skip lines without
a top-level `id` to filter it out when parsing.

### Connect: which account does the object live on?

`agent-stripe` can read connected accounts. Two different flags, easily confused:

- `--account <alias>` — which **credential** to authenticate with (a local keychain alias).
- `--stripe-account acct_…` — whose **books** that credential reads (the `Stripe-Account` header).

The distinction that matters most:

| Flow | Object lives on | Flag |
|---|---|---|
| Direct charge | connected account | `--stripe-account acct_…` |
| Destination charge | platform, with `transfer_data.destination` | none |
| Separate charges and transfers | charge on platform, `tr_…` to the account, joined by `transfer_group` | none for the charge |

**Do not conclude an object doesn't exist because the platform can't see it.**
A direct charge is invisible from the platform account entirely. If a `ch_…`,
`pi_…`, or its events come back empty, try the connected account before
reporting "not found".

```
agent-stripe connected-account list --limit 10           # find the acct_ id
agent-stripe connected-account capabilities acct_…       # why it can't charge/pay out
agent-stripe --stripe-account acct_… balance get         # why no payout
agent-stripe --stripe-account acct_… charge get ch_…     # the direct charge itself
agent-stripe --stripe-account acct_… account test        # is the account reachable at all?
```

Responses read this way echo `"stripeAccount": "acct_…"` in the envelope, so you
can verify the scope of what you just read rather than re-deriving it.

`on_behalf_of` is **not** the same thing as `--stripe-account`: it's a field set
when the object was created, which this CLI only ever reads.

`connected-account list` and `application-fee` are platform-scoped by nature —
they enumerate *your* connected accounts and *your* revenue, so `--stripe-account`
does nothing there.

### "No such coupon" on a connected account

Coupons and promotion codes are **per-account** and do not inherit from the
platform. A coupon created on the platform and referenced from a connected
account's subscription fails with `No such coupon`, and the diagnosis is just
reading it from both sides:

```
agent-stripe coupon get LAUNCH20                             # platform
agent-stripe --stripe-account acct_… coupon get LAUNCH20     # the account
```

A hit on one and a 404 on the other is the answer — each account needs its own.
To find a code by the string a customer typed, `promotion-code list --code
LAUNCH20` (there is no Search API for either resource).

### "This ledger row / this bank line item"

```
agent-stripe balance transaction txn_…                 # one row, no re-listing a payout
agent-stripe balance transactions --source ch_…        # what rows did this object produce?
agent-stripe payout list --arrival-date-gt … --arrival-date-lt …
```

Reconcile payouts on **arrival date**, not `created` — `created` is when Stripe
opened the payout, `arrival_date` is when the money lands, and a bank statement
matches the latter.

### A test clock that looks stuck

Two causes look identical from outside: the clock never advanced, or it advanced
and the webhook never fired. The clock itself separates them.

```
agent-stripe test-clock get clock_…
```

`status: advancing` means objects have not caught up yet; `ready` means the
advance finished and the problem is downstream; `internal_failure` means the
clock is dead. Compare `frozen_time` against the subscription's
`current_period_end` before concluding anything is wrong. Advancing a clock is a
write — out of scope here, use the Stripe CLI or Dashboard.

### Find customers by email

```
agent-stripe customer search --query 'email:"alice@example.com"'
```

Search is eventually consistent — there's a ~1-minute lag between creating an object and being able to search for it. Use `list` when strict consistency matters.

### Bulk export to NDJSON

```
agent-stripe charge list --created-gt 1735689600 --stream > charges.ndjson
```

`--stream` emits one header line then one record per line; pagination happens automatically until exhausted or `--limit` is hit. Pipes cleanly: `agent-stripe ... --stream | head -5` won't hang.

### Discover a resource's shape without spending an API call

```
agent-stripe resource describe subscription --depth 3
```

Returns the field tree (reflected from the SDK) and the curated `expandPaths` — useful before you decide what to `--expand-stripe`.

Caveat: `describe` reflects over the SDK's structs, so it shows one version's shape and only the fields that version models. If a field you expect is missing from a real response, that is the reason — see below.

### "A field I expect isn't in the output"

Responses are normally marshalled through SDK structs pinned to one Stripe API version. Any field that version does not model is dropped silently — no error, indistinguishable from Stripe not sending it. `--raw` emits Stripe's JSON instead:

```
agent-stripe invoice get in_… --raw
```

To see what a consumer on an older version receives — webhook endpoints pin their own version, often years behind — name it. `--api-version` implies `--raw`:

```
agent-stripe webhook-endpoint list                       # read each endpoint's api_version
agent-stripe --api-version 2022-11-15 invoice get in_…   # what that endpoint's payload looks like
```

The envelope's `apiVersion` reports the version actually requested, and raw responses carry `"raw": true`, so the two modes are never confusable in a transcript.

## Installation

```bash
brew install simonperryman/tap/agent-stripe   # once the tap is published
# or
go install github.com/simonperryman/agent-stripe/cmd/agent-stripe@latest
```

First-time setup:

```
agent-stripe account add default --form --default
```

`--form` opens an OS dialog so the secret key never enters the agent transcript. The key is stored in the OS keychain (macOS Keychain in v1).

More AI & ML skills

← All AI & ML skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY