stripe
stripe.
Search Stripe with its own query language. One verb, no `op` — the
setup & usage
create a restricted key in Stripe (Dashboard → Developers → API keys → "Create restricted key" — see the keys doc), then paste it into oto.
rk_…) is preferable to a secret key (sk_…): it limits what the key can reach even if it leaks, and Stripe explicitly recommends it for AI agentspk_…) is refused: it is the browser token, it can read neither customers nor invoicesrk_test_… / sk_test_… = test mode, rk_live_… / sk_live_… = live mode. The two worlds are separate — a test customer does not exist in live mode, and vice versatwo optional fields next to the key:
acct_…, Stripe Connect) — if you fill it in, all reads target that account and not your ownstripe_invoice(op="totals", created_after=…, created_before=…) (sum per currency, with a complete flag)stripe_balance(op="transactions", …) — each line carries its fees and its net, which invoices do not knowstripe_balance(op="get")stripe_balance(op="payouts")stripe_search(resource="customers", query='email~"acme.com"')stripe_invoice(op="list", customer_id="cus_…")stripe_subscription(op="list", status="all") then filter on cancel_at_period_end, past_due, unpaid — Stripe has no "on the way out" statestripe_payment(op="get_intent", payment_intent_id="pi_…") and read last_payment_error; or op="get_charge" and read outcome.seller_messagestripe_customer(op="payment_methods", customer_id="cus_…")stripe_payment(op="list_disputes") — watch evidence_details.due_by, the money is already withdrawn from the balance in the meantimestripe_catalog(op="list_prices") then stripe_checkout(op="create_link", price_id="price_…")stripe_checkout(op="create_link", price_id="price_…", created_metadata={"account_id": "…"}, max_uses=1) — created_metadata is set on the subscription (recurring price) or the payment (one-time price) created at checkout, because Stripe does not copy the link's metadata; it requires Prices: Read on the key. max_uses=1 deactivates the link after the first paymentstripe_invoice(op="add_item", customer_id="cus_…", amount=20000, currency="eur")stripe_catalog(op="create_coupon", percent_off=20, duration="once", name="LAUNCH20") (returns a coupon_id) then stripe_catalog(op="create_promotion_code", coupon_id="cp_…", code="LAUNCH20") — the coupon is the discount RULE, the promotion code is the TEXT the customer typesstripe_catalog(op="update_promotion_code", promotion_code_id="promo_…", active=false) — redemptions already made are not affected, only future uses are blockedstripe_catalog(op="list_promotion_codes", active=true)refund, cancel a subscription, finalize/send/collect an invoice, transfer money, close a dispute, delete a customer: none of these operations is reachable, and not merely by configuration choice — the corresponding methods do not exist in the underlying library. do them from your Stripe dashboard, where they are traced and confirmed.
what remains possible in write mode is deliberately free of direct financial consequence: create/update a customer, put a line on a next invoice, create a draft invoice, manage the products/prices/coupons/promotion codes catalog, create a payment link (page hosted by Stripe — no card number goes through oto, and nobody is charged until a human has paid). a coupon/promo code does nothing by itself: it only applies when a customer pays through a link/checkout that accepts it, or when you attach it yourself to a subscription from the dashboard.
total=0 and zero lines, the amount left hanging — a perfectly false "invoice created". stripe_invoice(op="create_draft") therefore passes pending_items="include" by default; set "exclude" if you really want an empty draft400 "the product tax code is missing". fix → stripe_catalog(op="update_product", product_id=…, tax_code="txcd_10000000") (generic services), then recreate the linkop="update_price", active=false); update_price explicitly refuses a unit_amount2026-07-29.dahlia, the default version of a new account): Stripe removed the flat coupon field in favor of a nested promotion object. This is handled internally (coupon_id remains the parameter on the stripe_catalog side), nothing to change on the usage side — but if you read a promotion code's raw response, the applied coupon is under promotion.coupon, no longer under couponop="totals", not a hand-made sumop="totals" sweeps up to 2,000 invoices and says so (complete: false) beyond that, instead of returning a partial sum that would pass for revenuestripe_search) covers only seven resources and is eventually consistent: an object created just now may only appear after a minutestripe_event only goes back 30 days (Stripe retention) — for a quarter, go through invoices and paymentsthis connector accepts multiple comptes: each stored credential becomes a named account (one name per compte), at your level, your team's or your org's.
_account="<name>" on the tool; list them: oto_identity(op='list', connector='stripe') (scope='org' or scope='group' for the org's or team's)oto_identity(op='set', connector='stripe', identity_id='<name>'); rename: op='rename' with new_name_account) and adding upoutils
utilisation
claude plugin marketplace add otomata-tech/oto-plugin — mcp + skill configurés.pipx install oto-cli puis oto stripe …