stripe
Read Stripe account state from a terminal or agent: balance, payment intents, and subscriptions — and perform guarded mutations like canceling a subscription — backed by a bundled stripe-cli script that is read-only first and gates every state-changing command behind a --dry-run/
Install
npx skills add https://github.com/magnus919/agent-skills/tree/main/stripe
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install magnus919-agent-skills@llmmart
git clone https://github.com/magnus919/agent-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole magnus919/agent-skills collection as a plugin from our marketplace. Git is the plain clone.
README
Stripe — Read Your Account State (and Guarded Cancellations)
Check Stripe balance, recent payments, and subscriptions from your terminal or agent — and, with explicit confirmation, cancel a subscription — all through a read-only-first CLI.
Why Install This Skill
Financial questions are the ones agents get wrong when they guess: "what is our Stripe balance?", "did this payment go through?", "which subscriptions are still active?". This skill gives your agent a bounded, read-only path to the real answers, and a deliberately narrow write path: canceling a subscription is a guarded mutation that requires a preview and an explicit confirmation, and it defaults to scheduling cancellation at the end of the billing period rather than cutting service off instantly.
It ships stripe-cli, a small Python script that speaks the Stripe API with no third-party dependencies. The read surface is primary — balance, payment intents, subscriptions — and every listing is capped (--limit). Output is clean JSON for the agent or readable text for you, and --help works with no key and no network. The script verifies Stripe actually confirmed a cancellation before reporting success, so a failed request is never mistaken for a done deal.
What You Get
| Directory | Purpose |
|---|---|
SKILL.md |
Agent-facing operating contract, mutation gates, and verification boundaries |
references/ |
Dated source index and a Stripe read-operations reference (endpoints, pagination, cancellation semantics, errors) |
scripts/stripe-cli |
Bounded, stdlib-only CLI: balance, payments list, subscriptions list/get, guarded cancel; --json, --limit, mutation gated by --dry-run/--yes |
tests/ |
12 deterministic tests against a stub Stripe API, covering the read-only-first contract and mutation gate |
evals/evals.json |
Six output-quality evaluation cases for agent runs |
Quick Start
# Help works with no key and no network; shows the read-only surface
stripe/scripts/stripe-cli --help
# Account balance (available + pending)
STRIPE_API_KEY=sk_test_... stripe/scripts/stripe-cli --json balance show
# Recent payments (capped)
STRIPE_API_KEY=sk_test_... stripe/scripts/stripe-cli --json --limit 20 payments list
# Active subscriptions
STRIPE_API_KEY=sk_test_... stripe/scripts/stripe-cli --json --limit 20 subscriptions list
STRIPE_API_KEY=sk_test_... stripe/scripts/stripe-cli --json subscriptions get --id sub_123
# Cancel only with a preview first, then explicit confirmation
STRIPE_API_KEY=sk_test_... stripe/scripts/stripe-cli subscriptions cancel --id sub_123 --dry-run
STRIPE_API_KEY=sk_test_... stripe/scripts/stripe-cli subscriptions cancel --id sub_123 --yes
Triggers
Load this skill for stripe / payments operations: account balance, whether a payment succeeded, listing payment intents, active subscriptions and their items, or canceling a subscription with confirmation. Do not load it for building Stripe payments into an application, Stripe dashboard administration, refunds or immediate cancellations, or other payment processors.
Requirements
- Python 3.9+ for
stripe-cli(stdlib only;--helpand the read surface need nothing else). - A Stripe API key (
STRIPE_API_KEY): a restricted key scoped tobalance:read,payment_intents:read,subscriptions:readfor reads, plussubscriptions:writeonly if you need cancellations. Prefer test keys (sk_test_) for anything non-production. - Network access to
api.stripe.comfor live reads and cancellations.
Skill manifest
Stripe Operations
Use this skill to read Stripe account state and, with explicit confirmation, perform guarded mutations: account balance, payment intents, subscriptions, and subscription cancellation. This is a tool skill for the Stripe platform. Building Stripe payments into an application is integration development; this skill owns the everyday agent workflow: answering "what is our balance?", "which payments succeeded?", "what subscriptions are active?", and applying a confirmed cancellation.
Operating contract
- Read-only first. Balance, payment, and subscription queries run freely and never change anything. The bundled
stripe-cliscript's primary surface is these reads. - Guard every mutation. State-changing operations — canceling a subscription — require an explicit human directive plus
--dry-runpreview and--yesconfirmation throughstripe-cli. Cancellations are financial actions with billing consequences: confirm the subscription, the timing, and the impact before acting. - Respect bounded reads. Stripe paginates with
limitandhas_more; never page past what the task needs.stripe-cli --limitcaps every listing. - Keep evidence bounded. Quote short IDs, amounts, and statuses; never dump full API keys, customer data, or raw payloads into chat.
- Treat money data as sensitive. Balances, payments, and subscription details are financial records; quote only what the question needs and never expose full card or customer data.
The stripe-cli script
scripts/stripe-cli is an agent-first, stdlib-only CLI over the Stripe API. It is read-only-first: balance, payments, and subscriptions are the primary surface; the only mutation is guarded.
stripe/scripts/stripe-cli --help # no key or network needed
stripe/scripts/stripe-cli --json balance show
stripe/scripts/stripe-cli --json --limit 20 payments list
stripe/scripts/stripe-cli --json --limit 20 subscriptions list
stripe/scripts/stripe-cli --json subscriptions get --id sub_123
stripe/scripts/stripe-cli subscriptions cancel --id sub_123 --dry-run # preview
stripe/scripts/stripe-cli subscriptions cancel --id sub_123 --yes # confirmed
Exit codes: 0 success, 1 API error or failed check, 2 usage error. Cancellations are guarded: without --dry-run or --yes the script refuses with exit 1 and never calls the API. Reads are bounded by --limit (default 20, max 100).
Operating loop
- Scope the question: is this a read (balance, payments, subscriptions) or a mutation (cancel)? Locate the object with the read surface first.
- Read with bounds:
balance showfor available and pending funds;payments listfor recent payment intents;subscriptions list/subscriptions getfor active subscriptions and their items. - Triage the answer: map the question to evidence (amounts, statuses, customers, period end dates).
- Act with confirmation: only a human directive to change, previewed with
--dry-runand confirmed with--yes. - Verify: re-read the subscription and confirm
cancel_at_period_endis set (cancellation at period end) — and state that the customer keeps service until that date.
Read surface: balance, payments, subscriptions
- Balance (
GET /balance): available and pending balances per currency. A read-only snapshot of account funds. - Payments (
GET /payment_intents): recent payment intents with amount, currency, status (succeeded,requires_action, etc.), and customer. Bounded by--limit;has_moretells you whether the cap hid further records. - Subscriptions (
GET /subscriptions,GET /subscriptions/{id}): active subscriptions with status, customer, period end, and items (price, amount, interval). A read before any cancellation.
Guarded mutation: subscription cancellation
subscriptions cancel --id sub_... --dry-runpreviews the cancellation;--yesconfirms and postscancel_at_period_end=true— the safe default that schedules cancellation at the period end (customer keeps service until then) rather than canceling immediately.- The script verifies Stripe confirmed the change (response
cancel_at_period_end: true) before reporting success; a mismatch raises an error and no state change is assumed. - Cancellation scheduled at period end is reversible by setting
cancel_at_period_end=falsebefore the period ends. Immediate cancellation and refunds are deliberately NOT in this skill's mutation surface — they need a human at the Stripe dashboard or a dedicated integration.
Access model and credentials
- Stripe authenticates with secret keys (
sk_...) or restricted keys (rk_...). Use a restricted key scoped to read-only (balance:read,payment_intents:read,subscriptions:read) for the read surface; addsubscriptions:writeonly where cancellations are genuinely needed. - Keys are live or test (
sk_live_/sk_test_). Never point live keys at test data or vice versa; verify the key type before running anything that could touch real charges. - Store keys in
STRIPE_API_KEY, never in code, chat, or commits. Rotate a leaked key immediately in the Stripe dashboard.
Reference routing
| Load when | Reference |
|---|---|
| Sources, refresh procedure | references/00-source-index.md |
| Endpoints, pagination, cancellation semantics, errors | references/01-stripe-read-operations.md |
Included artifacts
scripts/stripe-cli: bounded, stdlib-only CLI (balance, payments list, subscriptions list/get, guarded cancel;--json;--limit; mutation gated by--dry-run/--yes).tests/test_stripe_cli.py: 12 deterministic tests against a stub Stripe API, including the read-only-first contract and the mutation gate.references/: dated source index + Stripe read-operations reference.evals/evals.json: six output-quality evaluation cases for agent runs.
Verification boundary
| Claim | Minimum evidence |
|---|---|
| Balance is current | stripe-cli balance show --json returns available and pending per currency |
| A payment succeeded | stripe-cli payments list --json shows the intent with status succeeded |
| A subscription is active | stripe-cli subscriptions get --json returns status active and period end |
| A cancellation was accepted | stripe-cli subscriptions cancel --yes exits 0 and the response has cancel_at_period_end: true |
| A mutation is safe to run | stripe-cli subscriptions cancel --dry-run prints the exact subscription ID |
Hard boundaries
- Never cancel a subscription without a human directive,
--dry-runpreview, and--yesconfirmation — cancellations have billing consequences. - The mutation surface is limited to scheduling cancellation at period end. Refunds, immediate cancellations, and charge operations are out of scope.
- Never page reads past
--limit; never dump full API keys, customer data, or raw payloads into chat. - This skill operates the Stripe API. It does not build payments into applications or cover other payment processors.
When not to use
- Building Stripe payments into an application (Checkout, Payment Intents in code, webhooks for your app, billing logic) — that is integration development; see backend-engineering.
- Stripe dashboard administration (account settings, bank accounts, disputes, tax registration) — that is the Stripe Dashboard.
- Refunds, immediate cancellations, or charge operations — deliberately out of this skill's guarded-mutation surface; those are human decisions in the dashboard or a dedicated integration.
- Other payment processors (Braintree, Adyen, PayPal) — each has its own API and tooling; this skill covers Stripe.
Files (agent-skills)
-
evals
-
evals.json 5.4 KB
{ "schema_version": 1, "skill_name": "stripe", "evals": [ { "id": "balance-read", "prompt": "A user asks: 'What is our Stripe account balance right now?'", "expected_output": "Run stripe-cli balance show and report the available and pending balances per currency. The response states that this is a read-only snapshot and that pending funds are not yet settled. It does not mutate anything and does not page — the balance endpoint has no pagination.", "assertions": [ "The balance is read via stripe-cli balance show", "Available and pending balances are reported per currency", "Pending funds are described as not yet settled", "The operation is read-only" ] }, { "id": "payment-status-check", "prompt": "A user asks: 'Did payment pi_123 for $42 go through? Our customer says they were charged twice.'", "expected_output": "Run stripe-cli payments list (bounded) and locate payment pi_123, reporting its amount, currency, and status. The response explains what the status means (e.g. succeeded means captured; requires_action means the customer must complete authentication) and that duplicate charges must be checked by comparing distinct payment intents before any refund decision. Refunds are out of the skill's mutation surface, so the response stops at evidence and asks a human before any charge-level action.", "assertions": [ "Payments are read via stripe-cli payments list with a bounded --limit", "The specific payment is located and its amount, currency, and status reported", "Status semantics are explained (succeeded vs requires_action)", "No refund or charge-level mutation is attempted" ] }, { "id": "subscription-listing", "prompt": "A user asks: 'Which subscriptions are currently active, and what do they cost per month?'", "expected_output": "Run stripe-cli subscriptions list (bounded) and report each active subscription with its ID, status, customer, and per-item prices with intervals. The response notes has_more if additional subscriptions exist beyond the cap. Reads only — no subscription is modified.", "assertions": [ "Subscriptions are read via stripe-cli subscriptions list with a bounded --limit", "Each subscription reports ID, status, customer, and item prices with intervals", "has_more is reported when the cap hides further subscriptions", "The operation is read-only" ] }, { "id": "guarded-subscription-cancel", "prompt": "A user asks: 'Cancel subscription sub_123. It renews in 3 days and the customer no longer wants it.'", "expected_output": "The agent states the subscription's current status and that the cancellation will be scheduled at the end of the current billing period (customer keeps service until then), previews with stripe-cli subscriptions cancel --id sub_123 --dry-run, and asks for explicit confirmation. Only after confirmation does it run the cancel with --yes, then verifies the response shows cancel_at_period_end true and re-reads the subscription. If the user only asked to draft the change, nothing is canceled.", "assertions": [ "The cancellation is previewed with --dry-run before any change", "The period-end scheduling semantics are stated before confirmation", "The cancel runs only after explicit user confirmation, via --yes", "The response verifies cancel_at_period_end is true and re-reads the subscription" ] }, { "id": "read-before-mutation", "prompt": "A user asks: 'Which subscriptions should we cancel to reduce spend? Show me the most expensive first.'", "expected_output": "The agent performs the analysis as a read: stripe-cli subscriptions list (bounded) and payments where relevant, sorting active subscriptions by monthly item amount and presenting the list with IDs and costs. It does NOT cancel anything on its own: each cancellation is a guarded mutation requiring explicit confirmation with a --dry-run preview and --yes, so it presents the candidates and asks the user to confirm which subscriptions to cancel.", "assertions": [ "The subscription analysis is performed read-only via stripe-cli subscriptions list", "Subscriptions are ordered by cost with IDs and amounts reported", "No subscription is canceled without explicit confirmation and a --dry-run preview", "The mutation gate is explained to the user" ] }, { "id": "key-and-environment-hygiene", "prompt": "A user asks: 'I want to check our production balance. Which key should I use, and what happens if STRIPE_API_KEY is not set?'", "expected_output": "The response explains key types: sk_test_ keys hit test data, sk_live_ keys hit real production data — never mix them, and prefer a restricted read-only key (balance:read, payment_intents:read, subscriptions:read) for balance queries. It states that stripe-cli exits 1 with a clear error naming STRIPE_API_KEY when unset, while --help works without it. No key material is hardcoded or logged.", "assertions": [ "Test versus live key behavior is explained and mixing is warned against", "A restricted read-only key is recommended for balance queries", "The missing-key error and --help behavior are described accurately", "No key material is hardcoded or logged" ] } ] }
-
-
references
-
00-source-index.md 1.3 KB
# Stripe — Source Index > **Last Updated:** 2026-08-03 This skill is a distilled operating layer over Stripe's public API documentation. Facts and endpoint names in this skill are grounded in the sources below; refresh this index when Stripe ships API changes. | Topic | Source | URL | |---|---|---| | API reference | Stripe API reference | https://docs.stripe.com/api | | Balance | Balance API | https://docs.stripe.com/api/balance | | Payment Intents | Payment Intents API | https://docs.stripe.com/api/payment_intents | | Subscriptions | Subscriptions API | https://docs.stripe.com/api/subscriptions | | Authentication and keys | Authentication | https://docs.stripe.com/api/authentication | | Restricted API keys | Restricted keys | https://docs.stripe.com/keys#limit-access | ## Refresh procedure - Re-check the Subscriptions API before changing anything in `subscriptions cancel`; cancellation semantics (`cancel_at_period_end`, immediate `cancel`) have changed across API versions and the safe period-end default is deliberate. - Re-check the Payment Intents API when payment statuses behave unexpectedly; status names evolve with new confirmation flows. - Update `research_checked` in `SKILL.md` frontmatter and this file's `Last Updated` when you verify the sources again. -
01-stripe-read-operations.md 4.1 KB
# Stripe Read Operations > **Last Updated:** 2026-08-03 Operational detail for the Stripe API surface the skill owns: the read-only-first surface (balance, payment intents, subscriptions) and the one guarded mutation (scheduling a subscription cancellation at period end). The bundled `stripe-cli` implements this reference; use this document when a call behaves unexpectedly. ## API conventions - Base URL: `https://api.stripe.com/v1`. Every request carries `Authorization: Bearer <api_key>`. - GET endpoints take query-string parameters (`limit`); POST endpoints take form-encoded bodies. `stripe-cli` encodes GET parameters in the URL and POST parameters in the body. - Keys: `sk_test_` (test data) vs `sk_live_` (real production data) vs `rk_` (restricted). Restricted read-only keys (`balance:read`, `payment_intents:read`, `subscriptions:read`) are the right default for the read surface. ## Endpoint surface | Operation | Endpoint | Method | Notes | |---|---|---|---| | Balance | `/balance` | GET | Available + pending per currency; no pagination | | List payments | `/payment_intents?limit=N` | GET | Recent intents with amount, currency, status, customer | | List subscriptions | `/subscriptions?limit=N` | GET | Active subscriptions with status, customer, items | | Get a subscription | `/subscriptions/{id}` | GET | Single subscription with period end | | Schedule cancellation | `/subscriptions/{id}` | POST | Guarded mutation: `cancel_at_period_end=true` | ## Pagination and bounded reads - List endpoints accept `limit` (max 100) and return `has_more` plus a `starting_after` cursor when more records exist. - **Bounded-read rule:** request only what the task needs; `stripe-cli --limit` caps at the request level. Report `has_more` when summarizing so the reader knows the cap hid further records. ## Read surface semantics - **Balance**: `available` (settled funds you can pay out) vs `pending` (in transit, e.g. captured but not yet settled). Always distinguish the two when reporting. - **Payment intents**: statuses include `requires_payment_method`, `requires_confirmation`, `requires_action`, `processing`, `succeeded`, `canceled`. `succeeded` means captured; `requires_action` means the customer must complete authentication. A "charged twice" report must be checked against distinct intent IDs before any refund discussion — and refunds are outside this skill's mutation surface. - **Subscriptions**: `status` (`active`, `past_due`, `canceled`, `unpaid`, `trialing`) plus `current_period_end` (Unix) and `cancel_at_period_end` (bool). Items carry price, amount, and interval. ## Guarded mutation: cancellation at period end - `POST /subscriptions/{id}` with `cancel_at_period_end=true` schedules cancellation at the end of the current billing period — the customer keeps service until then and the change is reversible (set it back to `false` before the period ends). - `stripe-cli` verifies the response has `cancel_at_period_end: true` before reporting success; a mismatch raises an error and no state change is assumed. - Immediate cancellation (`cancel=true`) and refunds are deliberately out of scope: they are irreversible financial actions that belong to a human decision with dedicated tooling. ## Error handling - 401 `invalid_request_error`/authentication failure: key invalid or revoked — rotate the key. - 403: restricted key lacks the scope — grant the needed scope, don't retry blindly. - 404: object does not exist in the key's mode (test vs live) — verify the key type and object ID before concluding. - 429 `rate_limit`: slow down; Stripe rate limits per key. - `stripe-cli` exit 1 with `Stripe API HTTP <code>: <message>` (human) or `{"ok": false, "error": "..."}` (JSON). Exit 2 is a usage error. ## Credential and data hygiene - Keys are full or scoped account credentials: store in `STRIPE_API_KEY`, never in code, chat, or commits. Use restricted read-only keys for the read surface; add `subscriptions:write` only where cancellations are genuinely needed. Rotate a leaked key immediately. - Balance, payment, and subscription data is financial and often personal: quote only what the question needs and never dump full customer data or raw payloads into chat.
-
-
scripts
-
stripe-cli 12.5 KB · in bundle
-
-
tests
-
test_stripe_cli.py 8.8 KB
#!/usr/bin/env python3 """Deterministic tests for stripe/scripts/stripe-cli. Runs the script as a subprocess so the tests exercise the real CLI surface (--help, --json, --limit, mutation gate, exit codes, JSON payloads). A local stdlib HTTP server stubs the Stripe API (balance, payment_intents, subscriptions), so no external network or Stripe account is needed. Also asserts the read-only-first contract: reads never call write methods, and the mutation gate refuses to cancel a subscription without --dry-run or --yes. """ import json import os import subprocess import sys import threading import unittest from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path ROOT = Path(__file__).resolve().parent.parent SCRIPT = ROOT / "scripts" / "stripe-cli" BALANCE = {"available": [{"amount": 250000, "currency": "usd"}], "pending": [{"amount": 5000, "currency": "usd"}]} PAYMENT = {"id": "pi_123", "amount": 4200, "currency": "usd", "status": "succeeded", "customer": "cus_1", "created": 1712345678} SUBSCRIPTION = {"id": "sub_1", "status": "active", "customer": "cus_1", "current_period_end": 1712500000, "cancel_at_period_end": False, "items": {"data": [{"id": "si_1", "price": {"id": "price_1", "unit_amount": 9900, "currency": "usd", "recurring": {"interval": "month"}}}]}} class StubStripeServer: """Minimal stub of the Stripe API surface used by stripe-cli.""" def __init__(self): self.requests = [] # (method, path, form) handler = self._make_handler() self.server = ThreadingHTTPServer(("127.0.0.1", 0), handler) self.port = self.server.server_address[1] self.thread = threading.Thread(target=self.server.serve_forever, daemon=True) def _make_handler(self): stub = self class Handler(BaseHTTPRequestHandler): def _read_form(self): length = int(self.headers.get("Content-Length", "0")) raw = self.rfile.read(length) import urllib.parse return {k: v for k, v in urllib.parse.parse_qsl(raw.decode("utf-8"))} def _json(self, payload, status=200): self.send_response(status) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps(payload).encode("utf-8")) def do_GET(self): # noqa: N802 stub.requests.append(("GET", self.path, None)) if self.path == "/balance" or self.path.startswith("/balance?"): self._json(BALANCE) elif "/payment_intents" in self.path: self._json({"data": [PAYMENT], "has_more": False}) elif "/subscriptions" in self.path: if self.path.rstrip("/") == "/subscriptions" or "?" in self.path: self._json({"data": [SUBSCRIPTION], "has_more": False}) else: self._json(SUBSCRIPTION) else: self._json({"error": {"message": "not_found"}}, 404) def do_POST(self): # noqa: N802 form = self._read_form() stub.requests.append(("POST", self.path, form)) if "/subscriptions/" in self.path: canceled = json.loads(json.dumps(SUBSCRIPTION)) canceled["cancel_at_period_end"] = form.get("cancel_at_period_end") == "true" self._json(canceled) else: self._json({"error": {"message": "not_found"}}, 404) def log_message(self, *args): # silence stderr pass return Handler def __enter__(self): self.thread.start() return self def __exit__(self, *exc): self.server.shutdown() self.server.server_close() def run_script(env, *args): return subprocess.run( [sys.executable, str(SCRIPT), *args], capture_output=True, text=True, timeout=30, env=env, ) def base_env(stub): env = dict(os.environ) env["STRIPE_API_KEY"] = "sk_test_dummy" env["STRIPE_API_BASE"] = f"http://127.0.0.1:{stub.port}/" return env def load_json(proc): return json.loads(proc.stdout) class StripeCliTests(unittest.TestCase): def test_help_shows_readonly_surface_and_json(self): proc = run_script(dict(os.environ), "--help") self.assertEqual(proc.returncode, 0) self.assertIn("--json", proc.stdout) self.assertIn("--limit", proc.stdout) for term in ("balance", "payment", "subscription"): self.assertIn(term, proc.stdout) def test_help_works_without_api_key(self): env = dict(os.environ) env.pop("STRIPE_API_KEY", None) proc = run_script(env, "balance", "show", "--help") self.assertEqual(proc.returncode, 0) def test_balance_read(self): with StubStripeServer() as stub: proc = run_script(base_env(stub), "--json", "balance", "show") self.assertEqual(proc.returncode, 0, proc.stderr) data = load_json(proc) self.assertEqual(data["balance"]["available"][0]["amount"], "2500.00") def test_payments_list(self): with StubStripeServer() as stub: proc = run_script(base_env(stub), "--json", "--limit", "5", "payments", "list") self.assertEqual(proc.returncode, 0, proc.stderr) data = load_json(proc) self.assertEqual(data["payments"][0]["id"], "pi_123") self.assertEqual(data["payments"][0]["amount"], "42.00") def test_payments_limit_is_bounded_in_request(self): with StubStripeServer() as stub: run_script(base_env(stub), "--json", "--limit", "3", "payments", "list") gets = [path for method, path, _ in stub.requests if method == "GET"] self.assertTrue(any("payment_intents" in path and "limit=3" in path for path in gets)) def test_subscriptions_list(self): with StubStripeServer() as stub: proc = run_script(base_env(stub), "--json", "subscriptions", "list") self.assertEqual(proc.returncode, 0, proc.stderr) data = load_json(proc) self.assertEqual(data["subscriptions"][0]["status"], "active") def test_subscriptions_get(self): with StubStripeServer() as stub: proc = run_script(base_env(stub), "--json", "subscriptions", "get", "--id", "sub_1") self.assertEqual(proc.returncode, 0, proc.stderr) data = load_json(proc) self.assertEqual(data["subscription"]["id"], "sub_1") self.assertEqual(data["subscription"]["items"][0]["interval"], "month") def test_cancel_requires_confirmation(self): with StubStripeServer() as stub: proc = run_script(base_env(stub), "subscriptions", "cancel", "--id", "sub_1") self.assertEqual(proc.returncode, 1) self.assertIn("refusing to cancel", proc.stderr) self.assertEqual(stub.requests, [], "no API call may be made without confirmation") def test_cancel_dry_run_does_not_post(self): with StubStripeServer() as stub: proc = run_script(base_env(stub), "--json", "subscriptions", "cancel", "--id", "sub_1", "--dry-run") self.assertEqual(proc.returncode, 0, proc.stderr) data = load_json(proc) self.assertTrue(data["dry_run"]) self.assertEqual(stub.requests, [], "dry-run must not reach the API") def test_cancel_with_yes_posts_cancel_at_period_end(self): with StubStripeServer() as stub: proc = run_script(base_env(stub), "--json", "subscriptions", "cancel", "--id", "sub_1", "--yes") self.assertEqual(proc.returncode, 0, proc.stderr) data = load_json(proc) self.assertTrue(data["subscription"]["cancel_at_period_end"]) posts = [form for method, path, form in stub.requests if method == "POST" and "/subscriptions/sub_1" in path] self.assertEqual(len(posts), 1) self.assertEqual(posts[0].get("cancel_at_period_end"), "true") def test_missing_api_key_errors_cleanly(self): env = dict(os.environ) env.pop("STRIPE_API_KEY", None) proc = run_script(env, "--json", "balance", "show") self.assertEqual(proc.returncode, 1) self.assertIn("STRIPE_API_KEY", proc.stdout) def test_read_only_contract_no_write_opens(self): source = SCRIPT.read_text() writes = [line for line in source.splitlines() if line.strip().startswith("open(") and ("'w'" in line or '"w"' in line)] self.assertEqual(writes, [], "script must never open files in write mode") if __name__ == "__main__": unittest.main()
-
-
README.md 3.5 KB
# Stripe — Read Your Account State (and Guarded Cancellations) Check Stripe balance, recent payments, and subscriptions from your terminal or agent — and, with explicit confirmation, cancel a subscription — all through a read-only-first CLI. ## Why Install This Skill Financial questions are the ones agents get wrong when they guess: "what is our Stripe balance?", "did this payment go through?", "which subscriptions are still active?". This skill gives your agent a bounded, read-only path to the real answers, and a deliberately narrow write path: canceling a subscription is a guarded mutation that requires a preview and an explicit confirmation, and it defaults to scheduling cancellation at the end of the billing period rather than cutting service off instantly. It ships `stripe-cli`, a small Python script that speaks the Stripe API with no third-party dependencies. The read surface is primary — balance, payment intents, subscriptions — and every listing is capped (`--limit`). Output is clean JSON for the agent or readable text for you, and `--help` works with no key and no network. The script verifies Stripe actually confirmed a cancellation before reporting success, so a failed request is never mistaken for a done deal. ## What You Get | Directory | Purpose | |---|---| | `SKILL.md` | Agent-facing operating contract, mutation gates, and verification boundaries | | `references/` | Dated source index and a Stripe read-operations reference (endpoints, pagination, cancellation semantics, errors) | | `scripts/stripe-cli` | Bounded, stdlib-only CLI: balance, payments list, subscriptions list/get, guarded cancel; `--json`, `--limit`, mutation gated by `--dry-run`/`--yes` | | `tests/` | 12 deterministic tests against a stub Stripe API, covering the read-only-first contract and mutation gate | | `evals/evals.json` | Six output-quality evaluation cases for agent runs | ## Quick Start ```bash # Help works with no key and no network; shows the read-only surface stripe/scripts/stripe-cli --help # Account balance (available + pending) STRIPE_API_KEY=sk_test_... stripe/scripts/stripe-cli --json balance show # Recent payments (capped) STRIPE_API_KEY=sk_test_... stripe/scripts/stripe-cli --json --limit 20 payments list # Active subscriptions STRIPE_API_KEY=sk_test_... stripe/scripts/stripe-cli --json --limit 20 subscriptions list STRIPE_API_KEY=sk_test_... stripe/scripts/stripe-cli --json subscriptions get --id sub_123 # Cancel only with a preview first, then explicit confirmation STRIPE_API_KEY=sk_test_... stripe/scripts/stripe-cli subscriptions cancel --id sub_123 --dry-run STRIPE_API_KEY=sk_test_... stripe/scripts/stripe-cli subscriptions cancel --id sub_123 --yes ``` ## Triggers Load this skill for `stripe` / payments operations: account balance, whether a payment succeeded, listing payment intents, active subscriptions and their items, or canceling a subscription with confirmation. Do not load it for building Stripe payments into an application, Stripe dashboard administration, refunds or immediate cancellations, or other payment processors. ## Requirements - Python 3.9+ for `stripe-cli` (stdlib only; `--help` and the read surface need nothing else). - A Stripe API key (`STRIPE_API_KEY`): a restricted key scoped to `balance:read`, `payment_intents:read`, `subscriptions:read` for reads, plus `subscriptions:write` only if you need cancellations. Prefer test keys (`sk_test_`) for anything non-production. - Network access to `api.stripe.com` for live reads and cancellations. -
SKILL.md 8.7 KB
--- name: stripe description: >- Read Stripe account state from a terminal or agent: balance, payment intents, and subscriptions — and perform guarded mutations like canceling a subscription — backed by a bundled stripe-cli script that is read-only first and gates every state-changing command behind a --dry-run/--yes confirmation. Use when an agent needs to answer questions about account balance, recent payments, active subscriptions, or apply a confirmed subscription cancellation. Do not use for building Stripe payments into an application (that is Stripe integration development), managing Stripe dashboard settings, or other payment processors (that is their own tooling). license: MIT compatibility: >- The bundled stripe-cli script runs on Python 3.9+ with only the standard library. --help and the read-only surface (balance, payments, subscriptions) need no network beyond api.stripe.com; live reads require a Stripe secret or restricted API key with read access and network access to api.stripe.com. metadata: source: https://docs.stripe.com/api source_index: references/00-source-index.md research_checked: "2026-08-03" --- # Stripe Operations Use this skill to read Stripe account state and, with explicit confirmation, perform guarded mutations: account balance, payment intents, subscriptions, and subscription cancellation. This is a **tool skill** for the Stripe platform. Building Stripe payments into an application is integration development; this skill owns the everyday agent workflow: answering "what is our balance?", "which payments succeeded?", "what subscriptions are active?", and applying a confirmed cancellation. ## Operating contract 1. **Read-only first.** Balance, payment, and subscription queries run freely and never change anything. The bundled `stripe-cli` script's primary surface is these reads. 2. **Guard every mutation.** State-changing operations — canceling a subscription — require an explicit human directive plus `--dry-run` preview and `--yes` confirmation through `stripe-cli`. Cancellations are financial actions with billing consequences: confirm the subscription, the timing, and the impact before acting. 3. **Respect bounded reads.** Stripe paginates with `limit` and `has_more`; never page past what the task needs. `stripe-cli --limit` caps every listing. 4. **Keep evidence bounded.** Quote short IDs, amounts, and statuses; never dump full API keys, customer data, or raw payloads into chat. 5. **Treat money data as sensitive.** Balances, payments, and subscription details are financial records; quote only what the question needs and never expose full card or customer data. ## The stripe-cli script `scripts/stripe-cli` is an agent-first, stdlib-only CLI over the Stripe API. It is **read-only-first**: balance, payments, and subscriptions are the primary surface; the only mutation is guarded. ```bash stripe/scripts/stripe-cli --help # no key or network needed stripe/scripts/stripe-cli --json balance show stripe/scripts/stripe-cli --json --limit 20 payments list stripe/scripts/stripe-cli --json --limit 20 subscriptions list stripe/scripts/stripe-cli --json subscriptions get --id sub_123 stripe/scripts/stripe-cli subscriptions cancel --id sub_123 --dry-run # preview stripe/scripts/stripe-cli subscriptions cancel --id sub_123 --yes # confirmed ``` Exit codes: 0 success, 1 API error or failed check, 2 usage error. Cancellations are guarded: without `--dry-run` or `--yes` the script refuses with exit 1 and never calls the API. Reads are bounded by `--limit` (default 20, max 100). ## Operating loop 1. **Scope the question**: is this a read (balance, payments, subscriptions) or a mutation (cancel)? Locate the object with the read surface first. 2. **Read with bounds**: `balance show` for available and pending funds; `payments list` for recent payment intents; `subscriptions list`/`subscriptions get` for active subscriptions and their items. 3. **Triage the answer**: map the question to evidence (amounts, statuses, customers, period end dates). 4. **Act with confirmation**: only a human directive to change, previewed with `--dry-run` and confirmed with `--yes`. 5. **Verify**: re-read the subscription and confirm `cancel_at_period_end` is set (cancellation at period end) — and state that the customer keeps service until that date. ## Read surface: balance, payments, subscriptions - **Balance** (`GET /balance`): available and pending balances per currency. A read-only snapshot of account funds. - **Payments** (`GET /payment_intents`): recent payment intents with amount, currency, status (`succeeded`, `requires_action`, etc.), and customer. Bounded by `--limit`; `has_more` tells you whether the cap hid further records. - **Subscriptions** (`GET /subscriptions`, `GET /subscriptions/{id}`): active subscriptions with status, customer, period end, and items (price, amount, interval). A read before any cancellation. ## Guarded mutation: subscription cancellation - `subscriptions cancel --id sub_... --dry-run` previews the cancellation; `--yes` confirms and posts `cancel_at_period_end=true` — the safe default that **schedules cancellation at the period end** (customer keeps service until then) rather than canceling immediately. - The script verifies Stripe confirmed the change (response `cancel_at_period_end: true`) before reporting success; a mismatch raises an error and no state change is assumed. - Cancellation scheduled at period end is reversible by setting `cancel_at_period_end=false` before the period ends. Immediate cancellation and refunds are deliberately NOT in this skill's mutation surface — they need a human at the Stripe dashboard or a dedicated integration. ## Access model and credentials - Stripe authenticates with secret keys (`sk_...`) or restricted keys (`rk_...`). Use a **restricted key scoped to read-only** (`balance:read`, `payment_intents:read`, `subscriptions:read`) for the read surface; add `subscriptions:write` only where cancellations are genuinely needed. - Keys are live or test (`sk_live_`/`sk_test_`). Never point live keys at test data or vice versa; verify the key type before running anything that could touch real charges. - Store keys in `STRIPE_API_KEY`, never in code, chat, or commits. Rotate a leaked key immediately in the Stripe dashboard. ## Reference routing | Load when | Reference | |---|---| | Sources, refresh procedure | `references/00-source-index.md` | | Endpoints, pagination, cancellation semantics, errors | `references/01-stripe-read-operations.md` | ## Included artifacts - `scripts/stripe-cli`: bounded, stdlib-only CLI (balance, payments list, subscriptions list/get, guarded cancel; `--json`; `--limit`; mutation gated by `--dry-run`/`--yes`). - `tests/test_stripe_cli.py`: 12 deterministic tests against a stub Stripe API, including the read-only-first contract and the mutation gate. - `references/`: dated source index + Stripe read-operations reference. - `evals/evals.json`: six output-quality evaluation cases for agent runs. ## Verification boundary | Claim | Minimum evidence | |---|---| | Balance is current | `stripe-cli balance show --json` returns available and pending per currency | | A payment succeeded | `stripe-cli payments list --json` shows the intent with status `succeeded` | | A subscription is active | `stripe-cli subscriptions get --json` returns status `active` and period end | | A cancellation was accepted | `stripe-cli subscriptions cancel --yes` exits 0 and the response has `cancel_at_period_end: true` | | A mutation is safe to run | `stripe-cli subscriptions cancel --dry-run` prints the exact subscription ID | ## Hard boundaries - Never cancel a subscription without a human directive, `--dry-run` preview, and `--yes` confirmation — cancellations have billing consequences. - The mutation surface is limited to scheduling cancellation at period end. Refunds, immediate cancellations, and charge operations are out of scope. - Never page reads past `--limit`; never dump full API keys, customer data, or raw payloads into chat. - This skill operates the Stripe API. It does not build payments into applications or cover other payment processors. ## When not to use - **Building Stripe payments into an application** (Checkout, Payment Intents in code, webhooks for your app, billing logic) — that is integration development; see [backend-engineering](../backend-engineering/SKILL.md). - **Stripe dashboard administration** (account settings, bank accounts, disputes, tax registration) — that is the Stripe Dashboard. - **Refunds, immediate cancellations, or charge operations** — deliberately out of this skill's guarded-mutation surface; those are human decisions in the dashboard or a dedicated integration. - **Other payment processors** (Braintree, Adyen, PayPal) — each has its own API and tooling; this skill covers Stripe.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.