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

# Stripe

> Customers, Payment and Setup Intents, charges, refunds, subscriptions, invoices, Checkout, Connect, and webhooks.

Stripe API emulation with customers, payment methods, setup intents, customer sessions, payment intents, charges, refunds, products, prices, invoices, invoice items, subscriptions, subscription items, test clocks, checkout sessions, events, and webhook endpoints. Includes a hosted checkout page, idempotent requests, and signed webhook delivery. The tests drive it with stripe-node.

> [!NOTE]
>
> stripe-node accepts only `host`, `port`, and `protocol`, not a base path, so point it at Stripe's own port.

## At a glance

| | |
| - | - |
| Package | `@deploybaseline/stripe` |
| Port | `4009` in the default set, `4000` when started alone |
| Config key | `stripe` |
| HTTP operations | 141, browsable at `http://localhost:3999/docs/stripe` while it runs ([API reference](/api-reference.md)) |
| Vendor spec | [stripe/openapi](https://github.com/stripe/openapi) at `e924802` |

```bash CLI theme={null}
npx @deploybaseline/cli start --service stripe
```

```ts Programmatic theme={null}
import { createEmulator } from "@deploybaseline/cli";

const stripe = await createEmulator({ service: "stripe" });
// stripe.url is http://localhost:4000
```

```json baseline.config.json theme={null}
{
  "stripe": {
    "customers": [
      {
        "email": "test@example.com",
        "name": "Test Customer"
      }
    ],
    "products": [
      {
        "name": "Pro Plan",
        "description": "Monthly pro subscription"
      }
    ],
    "prices": [
      {
        "product_name": "Pro Plan",
        "currency": "usd",
        "unit_amount": 2000,
        "recurring": {
          "interval": "month"
        }
      }
    ]
  }
}
```

## Start

```bash theme={null}
npx @deploybaseline/cli start --service stripe
```

Stripe is in the default set and listens on `http://localhost:4009`, or on `http://localhost:4000` when it is the only service. Without a seed config there is one customer (`test@example.com`, "Test Customer").

stripe-node does not read an environment variable for the base URL, so pass the emulator's host and port when constructing the client:

```ts theme={null}
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  host: "localhost",
  port: 4009,
  protocol: "http",
});
```

Plain HTTP clients only need the base URL changed:

```bash theme={null}
curl http://localhost:4009/v1/customers \
  -H "Authorization: Bearer sk_test_baselined"
```

## Requests

These rules apply to every `/v1` endpoint, as they do on Stripe:

* **Idempotency.** A POST with an `Idempotency-Key` header (1 to 255 characters) saves its result for 24 hours. A retry with the same key and the same parameters gets the saved response back with `Idempotent-Replayed: true` and `Original-Request: <first request id>`, without running the endpoint again, so no second object or event is created. The same key with other parameters, or on another endpoint, gets a 400 `idempotency_error` (`Stripe.errors.StripeIdempotencyError` in stripe-node). A retry while the first request is still running gets a 409 `idempotency_key_in_use`. A request that fails validation (a 4xx other than 402) is not saved, so the key can be retried with fixed parameters. stripe-node adds a key to every POST on its own.
* **Request IDs.** Every response carries a `Request-Id: req_...` header (`lastResponse.requestId` in stripe-node).
* **Expand.** `expand[]` works on query strings and request bodies, including create and update calls. Lists take `data.<field>`, e.g. `expand[]=data.customer`. A field that cannot be expanded returns 400 `This property cannot be expanded (<field>).`
* **Metadata.** Updates merge `metadata` into what is stored. Set a key to `""` to remove it, or send `metadata=""` to clear all keys. Metadata values always stay strings.
* **Form values.** Booleans sent as `true` / `false` are decoded, so `active=false` stores a boolean.

## Customers

* `POST /v1/customers` — Create customer
* `GET /v1/customers/:id` — Retrieve customer
* `POST /v1/customers/:id` — Update customer
* `DELETE /v1/customers/:id` — Delete customer
* `GET /v1/customers` — List customers

`test_clock` on create puts the customer on a [test clock](#test-clocks) (at most three per clock). As on Stripe, `GET /v1/customers` lists customers on a clock only when `test_clock` is given.

`balance` (create and update) is the customer's credit (negative) or debt (positive). Finalizing an invoice applies it, as described under [Invoices](#invoices).

Deleting a customer cancels their subscriptions first, sending `customer.subscription.deleted` for each.

### Customer balance

* `POST /v1/customers/:id/balance_transactions` — Adjust customer balance: by `amount` (negative is credit) in `currency`, with `description` and `metadata`
* `GET /v1/customers/:id/balance_transactions/:txn` — Retrieve balance transaction
* `POST /v1/customers/:id/balance_transactions/:txn` — Update balance transaction: `description`, `metadata`
* `GET /v1/customers/:id/balance_transactions` — List balance transactions: the ledger, newest first (filter by `invoice`)

Every change to a customer's `balance` is recorded with the balance after it (`ending_balance`): `initial` for `balance` on create, `adjustment` for manual entries and `balance` on update, `applied_to_invoice` when finalizing uses it, `unapplied_from_invoice` when voiding gives it back, and `credit_note` for [credit notes](#credit-notes).

## Payment methods

* `POST /v1/payment_methods` — Create payment method: a card from a test token (`type=card`, `card[token]=tok_visa`; `billing_details`, `metadata` optional)
* `GET /v1/payment_methods/:id` — Retrieve payment method
* `POST /v1/payment_methods/:id` — Update payment method: `billing_details`, `metadata`, or `card[exp_month]` / `card[exp_year]` (attached payment methods only)
* `POST /v1/payment_methods/:id/attach` — Attach payment method: to a `customer`
* `POST /v1/payment_methods/:id/detach` — Detach payment method: from its customer
* `GET /v1/payment_methods` — List payment methods: `customer`, `type`; without `customer`, unattached ones
* `GET /v1/customers/:id/payment_methods` — List a customer's payment methods
* `GET /v1/customers/:id/payment_methods/:pm` — Retrieve a customer's payment method

Payment methods carry card details (`brand`, `last4`, `funding`, `country`, `exp_month`, `exp_year`) for the test card they stand for. As on Stripe, passing a magic test ID such as `pm_card_visa` to a payment intent, setup intent, or attach makes a new payment method with its own `pm_...` ID, and that ID is what responses show. Tokens such as `tok_visa` and `tok_chargeDeclined` work with `card[token]`. Raw card numbers get Stripe's "sending credit card numbers directly ... is generally unsafe" error. An ID that is neither stored nor a test ID returns 400 `No such PaymentMethod`.

Attaching checks the card, so a card that declines (`pm_card_visa_chargeDeclined`) cannot be saved; `pm_card_chargeCustomerFail` saves and then declines every charge. A saved card can only pay for its own customer: a payment or setup without that `customer`, or with another one, is refused. Customers have `invoice_settings.default_payment_method`, set on create or update to a card attached to them; detaching that card or deleting the customer clears it. Customer create also takes `payment_method` to attach a card. Events: `payment_method.attached`, `payment_method.detached`, `payment_method.updated`.

## Setup intents

* `POST /v1/setup_intents` — Create setup intent: `customer`, `payment_method`, `usage` default `off_session`, `confirm`, `return_url`, `description`, `metadata`
* `GET /v1/setup_intents/:id` — Retrieve setup intent
* `POST /v1/setup_intents/:id` — Update setup intent: `customer`, `payment_method`, `description`, `metadata`
* `POST /v1/setup_intents/:id/confirm` — Confirm setup intent: `payment_method`, `return_url` optional
* `POST /v1/setup_intents/:id/cancel` — Cancel setup intent: `cancellation_reason` optional
* `GET /v1/setup_intents` — List setup intents: filter by `customer`, `payment_method`

A SetupIntent saves a card without charging it. Confirming attaches the card to the intent's customer and sends `setup_intent.succeeded`; with `usage: off_session` the card is set up for off-session payments. A card that declines fails the setup with a 402 (`setup_intent.setup_failed`, `last_setup_error`), and a card that needs authentication goes to `requires_action` with the same hosted 3D Secure page as payments. After `pm_card_authenticationRequiredOnSetup` is set up, off-session payments with it succeed without authentication; `pm_card_authenticationRequired` keeps asking. Payment intents can save the card as they pay with `setup_future_usage` (`on_session` or `off_session`), which attaches it to the intent's customer once the payment succeeds.

## Customer sessions

* `POST /v1/customer_sessions` — Create customer session

## Payment intents

* `POST /v1/payment_intents` — Create payment intent
* `GET /v1/payment_intents/:id` — Retrieve payment intent
* `POST /v1/payment_intents/:id` — Update payment intent
* `POST /v1/payment_intents/:id/confirm` — Confirm payment intent
* `POST /v1/payment_intents/:id/capture` — Capture payment intent: an authorized one (`amount_to_capture` optional)
* `POST /v1/payment_intents/:id/cancel` — Cancel payment intent: `cancellation_reason` optional
* `GET /v1/payment_intents` — List payment intents

Confirming captures the payment by default (`capture_method: "automatic_async"`), creating a captured charge and setting `amount_received` and `latest_charge`. Create with `confirm: true` to create and confirm in one call. With `capture_method: "manual"`, confirming authorizes instead: the intent moves to `requires_capture` with `amount_capturable` set, the charge has `captured: false`, and `payment_intent.amount_capturable_updated` is sent. Capture all or part of it with `/capture`; a partial capture releases the rest, which shows in the charge's `amount_refunded`. Canceling an authorized intent releases the hold, so its charge reads as fully refunded and `charge.refunded` is sent.

Stripe payment intents and Checkout sessions follow API version `2026-09-30.endive` (stripe-node 23): send `allowed_payment_method_types`, which responses echo, and read the usable list from `payment_method_types`, which defaults to `["card"]`. The older `payment_method_types` request param is still accepted for clients pinned to earlier versions.

### Test cards

Confirming needs a payment method; without one, confirm returns 400 `payment_intent_unexpected_state`, as on Stripe. Stripe's [test payment method IDs](https://docs.stripe.com/testing) and the matching `tok_` tokens behave as they do on Stripe. Other `pm_card_` IDs, such as country cards, are cards that succeed. The [dispute cards](#disputes) succeed and then open a dispute.

| Payment method | Result |
| - | - |
| `pm_card_visa`, `pm_card_mastercard`, `pm_card_amex`, and other brand or country cards | Succeeds |
| `pm_card_visa_chargeDeclined` | 402 `card_declined`, `decline_code: generic_decline` |
| `pm_card_visa_chargeDeclinedInsufficientFunds` | 402 `card_declined`, `insufficient_funds` |
| `pm_card_visa_chargeDeclinedLostCard` | 402 `card_declined`, `lost_card` |
| `pm_card_visa_chargeDeclinedStolenCard` | 402 `card_declined`, `stolen_card` |
| `pm_card_visa_chargeDeclinedVelocityLimitExceeded` | 402 `card_declined`, `card_velocity_exceeded` |
| `pm_card_chargeDeclinedExpiredCard` | 402 `expired_card` |
| `pm_card_chargeDeclinedIncorrectCvc` | 402 `incorrect_cvc` |
| `pm_card_chargeDeclinedProcessingError` | 402 `processing_error` |
| `pm_card_chargeCustomerFail`, `pm_card_threeDSecureRequiredProcessingError` | 402 `card_declined`, `generic_decline` |
| `pm_card_radarBlock` | 402 `card_declined`, `fraudulent`; the charge outcome is `blocked` |
| `pm_card_authenticationRequired`, `pm_card_authenticationRequiredOnSetup`, `pm_card_threeDSecure2Required` | `requires_action`; off-session, 402 `authentication_required` |
| `pm_card_authenticationRequiredSetupForOffSession` | `requires_action`; off-session payments succeed |
| `pm_card_threeDSecureRequiredChargeDeclined` | `requires_action`, then `card_declined` after authentication |
| `pm_card_authenticationRequiredChargeDeclinedInsufficientFunds` | `requires_action`, then `insufficient_funds`; off-session, `insufficient_funds` |
| `pm_card_createDispute`, `pm_card_createDisputeProductNotReceived` | Succeeds, then a chargeback opens (`fraudulent`, `product_not_received`) |
| `pm_card_createDisputeInquiry` | Succeeds, then an inquiry opens |
| `pm_card_bypassPending` | Succeeds; the funds are available at once instead of after two days |

A decline returns a 402 card error (`Stripe.errors.StripeCardError` in stripe-node) with `code`, `decline_code`, `charge`, `payment_method`, and `payment_intent`. It leaves a `failed` charge with `failure_code`, `failure_message`, `outcome`, and `payment_method_details`, sets the intent's `last_payment_error`, returns it to `requires_payment_method`, and sends `charge.failed` and `payment_intent.payment_failed`. Confirm again with another `payment_method` to retry. With an `Idempotency-Key`, the 402 is replayed like any other result.

### 3D Secure

A card that needs authentication moves the intent to `requires_action`, sends `payment_intent.requires_action`, and sets `next_action` to the hosted page at `/_stripe/3ds/:id`: `redirect_to_url` when the intent has a `return_url`, otherwise `use_stripe_sdk`. The page has Complete and Fail buttons. Completing applies the card's result (success, `requires_capture` for manual capture, or a decline); failing returns the intent to `requires_payment_method` with `payment_intent_authentication_failure`. With a `return_url`, the page redirects there with `payment_intent`, `payment_intent_client_secret`, and `redirect_status` (`succeeded` or `failed`).

Tests can skip the page and POST the outcome; `Accept: application/json` returns the payment intent:

```bash theme={null}
curl -X POST http://localhost:4009/_stripe/3ds/pi_xxx \
  -H "Accept: application/json" -d "outcome=complete"
```

## Charges

* `GET /v1/charges/:id` — Retrieve charge
* `GET /v1/charges` — List charges

## Refunds

* `POST /v1/refunds` — Create refund: of a captured payment, by `payment_intent` or `charge` (`amount`, `reason`, `metadata` optional)
* `GET /v1/refunds/:id` — Retrieve refund
* `POST /v1/refunds/:id` — Update refund metadata
* `POST /v1/refunds/:id/cancel` — Cancel refund: refunds succeed at once here, so this returns Stripe's error for a refund that is not pending
* `GET /v1/refunds` — List refunds: filter by `charge`, `payment_intent`

A refund without `amount` refunds what is left. Partial refunds add up in the charge's `amount_refunded` until `refunded` is true. Refunding more than is left returns `amount_too_large`, a fully refunded charge returns `charge_already_refunded`, and an uncaptured charge returns `charge_not_refundable` (cancel its payment intent instead). Each refund sends `refund.created` and `charge.refunded`. A charge with a chargeback cannot be refunded (`charge_disputed`); an open inquiry can.

## Balance

* `GET /v1/balance` — Retrieve balance: `available` and `pending` funds per currency
* `GET /v1/balance_transactions/:id` — Retrieve balance transaction: `expand[]=source`
* `GET /v1/balance_transactions` — List balance transactions: filter by `type`, `source`, `currency`, `created`

Captured charges, refunds, and disputes write balance transactions, which charges and refunds link as `balance_transaction`; expand it, or `latest_charge.balance_transaction` on a payment intent, to read fees. A charge adds its amount less Stripe's US card fee (2.9% + 30 cents, in `fee_details`) and is `pending` until `available_on`, two days after midnight UTC. A refund takes its amount out at once, and Stripe keeps the fee. A manual capture records the captured amount when it is captured. The balance is the sum of `net` amounts, split by whether `available_on` has passed.

## Disputes

* `GET /v1/disputes/:id` — Retrieve dispute
* `POST /v1/disputes/:id` — Update dispute: stage or submit `evidence`, update `metadata`
* `POST /v1/disputes/:id/close` — Close dispute: accept it
* `GET /v1/disputes` — List disputes: filter by `charge`, `payment_intent`

Stripe's [dispute test cards](https://docs.stripe.com/testing#disputes) open a dispute once the charge is captured and set the charge's `disputed`. A chargeback starts as `needs_response` and withdraws the amount plus a \$15 fee at once (an `adjustment` balance transaction, `charge.dispute.funds_withdrawn`). An inquiry starts as `warning_needs_response` and moves no funds.

Evidence is submitted when sent, unless `submit=false` stages it. Submitting moves the dispute to `under_review` (`warning_under_review` for an inquiry), after which evidence cannot change. As on Stripe, `evidence[uncategorized_text]` decides the outcome: `winning_evidence` wins (`won`, the amount and fee come back with `charge.dispute.funds_reinstated`; an inquiry becomes `warning_closed`), and `losing_evidence` loses (`lost`; a lost inquiry withdraws the funds then). Other evidence stays under review. Closing accepts the dispute: a chargeback is `lost`, an inquiry `warning_closed`. Events: `charge.dispute.created`, `.updated`, `.closed`, `.funds_withdrawn`, `.funds_reinstated`.

## Connect

* `POST /v1/accounts` — Create connected account: `type` (`standard`, the default, `express`, or `custom`) or `controller` (`stripe_dashboard[type]` of `full`, `express`, or `none`), `country`, `email`, `business_type`, `business_profile`, `capabilities`, `tos_acceptance`, `external_account`, `metadata`
* `GET /v1/accounts/:id` — Retrieve connected account
* `POST /v1/accounts/:id` — Update connected account
* `DELETE /v1/accounts/:id` — Delete connected account
* `GET /v1/accounts` — List connected accounts
* `GET /v1/account` — Retrieve current account: the account in `Stripe-Account`, or the platform (`acct_baseline`) without it
* `POST /v1/account_links` — Create account link: hosted onboarding (`account_onboarding`, or `account_update` for Custom accounts) with `refresh_url` and `return_url`
* `POST /v1/accounts/:id/login_links` — Create login link: an Express dashboard link, for onboarded Express accounts
* `POST /v1/transfers` — Create transfer
* `GET /v1/transfers/:id` — Retrieve transfer
* `POST /v1/transfers/:id` — Update transfer: `description`, `metadata`
* `GET /v1/transfers` — List transfers: filter by `destination`, `transfer_group`
* `GET /v1/application_fees/:id` — Retrieve application fee
* `GET /v1/application_fees` — List application fees: filter by `charge`

A new account needs `business_type`, `external_account`, `tos_acceptance.date`, and `tos_acceptance.ip` (`requirements.currently_due`); until then `charges_enabled` and `payouts_enabled` are false and its capabilities are `inactive`. The account link page fills in what is missing when the user clicks Complete onboarding, then redirects to `return_url`; a used or expired link (after five minutes) redirects to `refresh_url`. Tests can POST the link URL with `Accept: application/json` to get the account back. Custom accounts can also be completed through `POST /v1/accounts/:id`; any `external_account` value counts as a payout account. Changes send `account.updated`.

A request with `Stripe-Account: acct_...` acts on that account's own data: customers, payment methods, payment intents, charges, refunds, disputes, billing, balance, and events are kept per account. An unknown or deleted account returns 403 `account_invalid`. The hosted Checkout and 3D Secure pages find a connected account's sessions and intents on their own. Events on a connected account carry `account` and go to webhook endpoints created with `connect=true` (or seeded with `connect: true`), not the platform's.

Charges move money as on Stripe:

* Direct charge (with `Stripe-Account`): the charge is the account's. `application_fee_amount` is taken out of its balance transaction (`fee_details` lists `stripe_fee` and `application_fee`) and paid to the platform as an application fee (`application_fee.created`).
* Destination charge (`transfer_data[destination]`): the charge is the platform's. The amount, or `transfer_data[amount]`, is transferred to the account (`transfer.created`, the charge's `transfer`), and `application_fee_amount` comes back to the platform out of the account's `payment`. The destination must be onboarded with `transfers` (`insufficient_capabilities_for_transfer`). `application_fee_amount` without either returns 400, as does combining it with `transfer_data[amount]`.
* Separate charges and transfers: `POST /v1/transfers` needs that much available platform balance (`balance_insufficient`; pay with `pm_card_bypassPending` to skip the pending period), or a `source_transaction` charge, whose funds it waits for.

`on_behalf_of` and `transfer_group` are kept and echoed.

## Products

* `POST /v1/products` — Create product
* `GET /v1/products/:id` — Retrieve product
* `GET /v1/products` — List products

## Prices

* `POST /v1/prices` — Create price: `unit_amount` required; `recurring`, `lookup_key`, `nickname` optional
* `GET /v1/prices/:id` — Retrieve price
* `GET /v1/prices` — List prices: filter by `product`, `active`, `type`, `lookup_keys[]`

`recurring[interval]` (`day`, `week`, `month`, `year`) with `recurring[interval_count]` makes a recurring price, up to Stripe's three-year limit. Only licensed (per unit) prices are emulated: `usage_type=metered` returns 400.

## Coupons and promotion codes

* `POST /v1/coupons` — Create coupon: `percent_off` or `amount_off` with `currency`, `duration` (`once`, `repeating` with `duration_in_months`, or `forever`), `max_redemptions`, `redeem_by`, `applies_to[products][]`, `id` (otherwise a random code), `name`
* `GET /v1/coupons/:id` — Retrieve coupon: `expand[]=applies_to` shows its products
* `POST /v1/coupons/:id` — Update coupon: `name` or `metadata` (other fields cannot change)
* `DELETE /v1/coupons/:id` — Delete coupon: discounts already made from it keep applying
* `GET /v1/coupons` — List coupons
* `POST /v1/promotion_codes` — Create promotion code: for a coupon (`promotion[coupon]`, or `coupon` for older clients) with `code` (otherwise random), `customer`, `expires_at`, `max_redemptions`, `active`, and `restrictions` (`first_time_transaction`, `minimum_amount` with `minimum_amount_currency`)
* `GET /v1/promotion_codes/:id` — Retrieve promotion code
* `POST /v1/promotion_codes/:id` — Update promotion code: `active`, `metadata`
* `GET /v1/promotion_codes` — List promotion codes: filter by `active`, `code`, `coupon`, `customer`

A discount is made when a coupon or promotion code is applied, with `discounts[0][coupon]` or `discounts[0][promotion_code]` on a subscription, invoice, Checkout session, or preview. Customers take the older `coupon` and `promotion_code` params on create and update, and `GET` or `DELETE /v1/customers/:id/discount` reads or removes the customer's discount. `DELETE /v1/subscriptions/:id/discount` removes a subscription's.

Applying a coupon checks it as Stripe does: an unknown or deleted coupon returns `resource_missing`, one past `redeem_by` or `max_redemptions` returns `coupon_expired`, and an `amount_off` coupon in another currency is refused. A promotion code must be active, unexpired, under its `max_redemptions`, for its `customer` if it has one, and meet its restrictions: `first_time_transaction` refuses customers with an earlier payment or paid invoice, and `minimum_amount` refuses smaller orders. Each redemption raises `times_redeemed` on the coupon and the code.

How discounts bill:

* A subscription's discounts apply to each of its invoices. Without its own discounts, a subscription uses the customer's discount. Invoices take the subscription's or customer's discounts unless `discounts` is given (`discounts=""` opts out).
* Percentages come off the discountable lines; `amount_off` comes off up to their total. Several discounts apply in order. Each invoice line reports its share in `discount_amounts`, and the invoice reports `total_discount_amounts`, `discounts`, and `total`. Proration lines and invoice items with `discountable=false` are not discounted, and `applies_to` limits a coupon to its products.
* `once` applies to the first invoice with something to discount. `repeating` applies to invoices made within `duration_in_months` of the discount's `start` (the `end`), so on a [test clock](#test-clocks) a three-month coupon covers three monthly renewals. `forever` never ends. A discount that has ended is removed when the next invoice finalizes, with `customer.discount.deleted`.
* Previews (`create_preview`) apply the same discounts and accept `discounts` to try one out.

Events: `coupon.created`, `.updated`, `.deleted`; `promotion_code.created`, `.updated`; `customer.discount.created`, `.deleted`.

Seed coupons and codes under `coupons` and `promotion_codes` in [seed config](#seed-config), and tax rates under `tax_rates`.

## Tax rates

* `POST /v1/tax_rates` — Create tax rate: `display_name`, `percentage`, `inclusive` (all required), `country`, `state`, `jurisdiction`, `tax_type`, `description`, `active`
* `GET /v1/tax_rates/:id` — Retrieve tax rate
* `POST /v1/tax_rates/:id` — Update tax rate: anything but `percentage` and `inclusive`; `active: false` archives it
* `GET /v1/tax_rates` — List tax rates: filter by `active`, `inclusive`

Tax rates apply by ID: `tax_rates` on invoice items, subscription items (`items[i][tax_rates]`, `add_invoice_items[i][tax_rates]`), and Checkout line items, and `default_tax_rates` on invoices and subscriptions for lines without their own. A subscription's invoices take its `default_tax_rates`. Unknown rates return `resource_missing`, and archived rates cannot be applied anew (they stay where they are used).

Taxes follow [Stripe's tax rate rules](https://docs.stripe.com/billing/taxes/tax-rates): each line is taxed on its amount after discounts. An exclusive rate adds its percentage of that amount, rounded per line. An inclusive rate is already in the amount: the taxable amount is the amount divided by one plus the inclusive rates, and the tax is the rest. Lines report `taxes` (amount, `tax_behavior`, `tax_rate_details`, `taxable_amount`), and invoices report `total_taxes` per rate, `total_excluding_tax`, and `subtotal_excluding_tax`; `total` and `amount_due` include exclusive tax. Previews and Checkout (`total_details.amount_tax`) use the same rules. Events: `tax_rate.created`, `tax_rate.updated`.

## Invoices

* `POST /v1/invoices` — Create invoice: a draft for a `customer`. `pending_invoice_items_behavior=include` takes the customer's pending items (the default, as on Stripe, is `exclude`). With `subscription`, it takes only that subscription's pending items; without it, only items not tied to a subscription
* `POST /v1/invoices/create_preview` — Preview invoice: without making it (see [Invoice previews](#invoice-previews))
* `GET /v1/invoices/:id` — Retrieve invoice
* `POST /v1/invoices/:id` — Update invoice: a draft (`description`, `metadata`, `auto_advance`, `collection_method`, `days_until_due`, `due_date`, `default_payment_method`). Open invoices take only `metadata` and `auto_advance`; others only `metadata`
* `DELETE /v1/invoices/:id` — Delete draft invoice: its items go back to pending
* `POST /v1/invoices/:id/finalize` — Finalize invoice: a draft
* `POST /v1/invoices/:id/pay` — Pay invoice: an open, uncollectible, or draft one (`payment_method`, `off_session`, `paid_out_of_band`)
* `POST /v1/invoices/:id/send` — Send invoice: a `send_invoice` invoice (finalizes a draft first)
* `POST /v1/invoices/:id/void` — Void invoice: an open or uncollectible one
* `POST /v1/invoices/:id/mark_uncollectible` — Mark invoice uncollectible: an open one
* `GET /v1/invoices/:id/lines` — List invoice lines: paginated
* `GET /v1/invoices` — List invoices: filter by `customer`, `subscription`, `status`, `collection_method`, `created`

Statuses follow Stripe's [transition table](https://docs.stripe.com/invoicing/integration/workflow-transitions). Finalizing numbers the invoice (`<customer invoice_prefix>-0001`), applies the customer `balance` (credit lowers `amount_due`; a negative total becomes credit; `starting_balance` and `ending_balance` show the change), and creates the payment intent that collects it. An invoice with nothing to pay is paid at once. `send_invoice` invoices get `due_date` from `days_until_due` (30 days when unset).

Paying charges `payment_method`, else the invoice's `default_payment_method`, else the customer's `invoice_settings.default_payment_method`; with none, it returns 400. Payment follows the [test cards](#test-cards): a decline returns the 402 card error, keeps the invoice open, raises `attempt_count`, and sends `invoice.payment_failed`. `off_session` defaults to `true`, so a card that needs authentication declines with `authentication_required`; with `off_session=false` it returns 402 `invoice_payment_intent_requires_action` and sends `invoice.payment_action_required`, and the invoice is paid once the [3D Secure](#3d-secure) page completes. `paid_out_of_band=true` marks it paid without a charge (`amount_paid_off_stripe`). Voiding cancels the payment intent and gives back the customer balance the invoice used.

To collect on the client, expand `confirmation_secret` and confirm its `client_secret` with Stripe.js. However the invoice's payment intent succeeds, the invoice becomes `paid` and sends `invoice.paid`, `invoice.payment_succeeded`, and `invoice_payment.paid`. `expand[]=payments` lists the invoice payment. The payment intent cannot be canceled directly while the invoice is open; void the invoice instead.

### Invoice items

* `POST /v1/invoiceitems` — Create invoice item: for a `customer`, priced by `amount` (negative for a credit), `unit_amount_decimal` with `quantity`, `pricing[price]`, or `price_data`. `invoice` puts it on a draft invoice; otherwise it waits as pending
* `GET /v1/invoiceitems/:id` — Retrieve invoice item
* `POST /v1/invoiceitems/:id` — Update invoice item: amount, quantity, pricing, period, description, or metadata
* `DELETE /v1/invoiceitems/:id` — Delete invoice item
* `GET /v1/invoiceitems` — List invoice items: filter by `customer`, `invoice`, `pending`

`subscription` on create ties a pending item to one of the customer's subscriptions, so it is billed on that subscription's next invoice. `tax_rates` taxes the item (see [Tax rates](#tax-rates)); invoices take `default_tax_rates` for items without their own.

Items on a finalized invoice cannot change or be deleted (`invoice_not_editable`). `price_data` makes an inactive price, as on Stripe. The currency must match the invoice's.

### Automatic collection

With `auto_advance: true`, invoices move on their own as billing time passes:

* A draft finalizes an hour after it was created (`automatically_finalizes_at`), and a `charge_automatically` invoice is charged off-session right away.
* After a manual `finalize`, the first charge is an hour later (`next_payment_attempt`).
* A failed charge (including no payment method) sends `invoice.payment_failed` and retries 3, 5, and 7 days after the previous attempt, four attempts in all. After the last, `next_payment_attempt` is null and the invoice stays open. The retry days can be changed in [seed config](#seed-config) (`billing.retry_days`).
* A `send_invoice` invoice sends `invoice.overdue` once its `due_date` passes.
* `auto_advance: false` on an open invoice stops collection; setting it back schedules a charge an hour later.

Billing time is the wall clock, or the customer's [test clock](#test-clocks). Nothing runs on a timer: wall-clock customers catch up at the start of the next `/v1` request (so actions take that request's time), and a test clock runs each action at the time it falls due while it advances. Events from automatic actions have `request.id: null`.

### Invoice previews

`POST /v1/invoices/create_preview` returns the invoice Stripe would make, without creating anything. Previews use the same billing rules as the real invoices, so a preview and the invoice that follows it match. The ID starts with `upcoming_in_`, lines start with `il_tmp_`, and `billing_reason` is `upcoming`.

* `subscription` alone previews the next renewal, with the pending items it would take.
* `subscription` with `subscription_details` (`items`, `proration_behavior`, `proration_date`, `billing_cycle_anchor`, `trial_end`, `cancel_at`, `cancel_at_period_end`, `cancel_now`) previews a change. With `always_invoice`, a new cycle, or `cancel_now`, it is the invoice made at once; otherwise it is the next renewal with the change's prorations on it.
* `customer` with `subscription_details[items]` previews the first invoice of a new subscription.
* `customer` alone previews an invoice of the customer's pending items.

With nothing to invoice it returns 404 `invoice_upcoming_none`, as Stripe does.

## Credit notes

* `POST /v1/credit_notes` — Create credit note: credit a finalized `invoice` by `lines` or `amount`, with `refund_amount`, `credit_amount`, `out_of_band_amount`, `refunds`, `reason`, `memo`, `metadata`
* `GET /v1/credit_notes/preview` — Preview credit note: the same, without issuing it
* `GET /v1/credit_notes/preview/lines` — Preview credit note lines
* `GET /v1/credit_notes/:id` — Retrieve credit note
* `GET /v1/credit_notes/:id/lines` — List credit note lines
* `POST /v1/credit_notes/:id` — Update credit note: `memo` or `metadata`
* `POST /v1/credit_notes/:id/void` — Void credit note
* `GET /v1/credit_notes` — List credit notes: filter by `invoice`, `customer`

A line of type `invoice_line_item` credits part of an invoice line, by `quantity` or `amount` (all that is left when neither is given), with its share of the line's discounts and the line's tax rates. A `custom_line_item` credits `unit_amount` × `quantity` with its own `tax_rates`. A credit note cannot take more than is left of a line, or of the invoice total.

The total first comes off what is left to pay on an open invoice (`pre_payment_amount`): `amount_due` drops, the payment intent follows, and an invoice with nothing left is paid. The rest (`post_payment_amount`) must equal `refund_amount` (a refund of the invoice's charge) plus `credit_amount` (customer balance credit, which the next invoice uses) plus `out_of_band_amount` plus linked `refunds`; otherwise it returns 400. The invoice adds them up in `pre_payment_credit_notes_amount` and `post_payment_credit_notes_amount`. Numbers follow the invoice's (`<invoice number>-CN-01`).

Voiding gives back the balance credit and raises `amount_due` again. A credit note with a refund, or one that paid its invoice, cannot be voided. Events: `credit_note.created`, `.updated`, `.voided`.

## Subscriptions

* `POST /v1/subscriptions` — Create subscription: for a `customer` with `items` (each a recurring `price` or `price_data`, with `quantity`)
* `GET /v1/subscriptions/:id` — Retrieve subscription
* `POST /v1/subscriptions/:id` — Update subscription: change items, quantities, trial, anchor, cancellation, collection, payment method, or metadata
* `DELETE /v1/subscriptions/:id` — Cancel subscription: now (`invoice_now`, `prorate`, `cancellation_details`)
* `POST /v1/subscriptions/:id/pause` — Pause subscription: `type=subscription`
* `POST /v1/subscriptions/:id/resume` — Resume subscription: a paused one (`billing_cycle_anchor`, `proration_behavior`, `payment_behavior`)
* `GET /v1/subscriptions` — List subscriptions: filter by `customer`, `price`, `status`, `collection_method`, `test_clock`

Subscriptions run on [automatic collection](#automatic-collection) and [test clocks](#test-clocks): each renewal, trial end, retry, and expiry happens at its billing time.

**Creating.** Items must share a currency and billing interval, with no price twice (1 to 20 items). The first invoice (`billing_reason: subscription_create`) is finalized and collected at once, according to `payment_behavior`:

* `allow_incomplete` (the default) charges the default card. On success the subscription is `active`; on a decline it stays `incomplete` with the invoice open.
* `error_if_incomplete` returns the card's 402 error and creates nothing when the charge would fail.
* `default_incomplete` leaves the subscription `incomplete` so the client pays it: expand `latest_invoice.confirmation_secret` and confirm it with Stripe.js.

An `incomplete` subscription becomes `active` when its first invoice is paid. If it is not paid within 23 hours, it becomes `incomplete_expired`, its invoice is voided, and `customer.subscription.deleted` is sent. The first invoice is never retried. `payment_settings[save_default_payment_method]=on_subscription` saves the card that paid it as the subscription's default.

The subscription's card is its `default_payment_method`, else the customer's `invoice_settings.default_payment_method`. `collection_method=send_invoice` (with `days_until_due`) sends invoices instead of charging them. `add_invoice_items` adds one-time prices to the first invoice. Pending invoice items for the customer go on the first invoice too.

**Periods.** Billing periods follow the price's interval from `billing_cycle_anchor`. Month periods keep the anchor day and clamp to the month's end (an anchor on 31 January renews on 28 February, then 31 March). A future `billing_cycle_anchor` bills a prorated first period up to it (`proration_behavior=none` makes it free). Each item reports `current_period_start` and `current_period_end`. Three days before each renewal, `invoice.upcoming` is sent with a preview of the invoice (not for `send_invoice`). At renewal, a draft invoice (`billing_reason: subscription_cycle`) is made with the subscription's pending items, then finalized and charged an hour later.

**Trials.** `trial_end` (a timestamp) or `trial_period_days` starts a `trialing` subscription with a \$0 invoice. `customer.subscription.trial_will_end` is sent three days before the trial ends (at once for shorter trials). When it ends, the first paid period starts. `trial_settings[end_behavior][missing_payment_method]` decides what happens without a card: `create_invoice` (the default) bills anyway, `pause` pauses the subscription, and `cancel` cancels it. An update with `trial_end=now` ends the trial at once; a later `trial_end` extends it.

**Failed payments.** A failed renewal makes the subscription `past_due` and retries on the invoice schedule. After the last retry the subscription is canceled, by default. `billing.after_final_retry` in [seed config](#seed-config) can make it `unpaid` (later invoices stay drafts) or leave it `past_due`. Paying or marking uncollectible the latest invoice makes the subscription `active` again.

**Changes and proration.** Updates take `items` (`id` to change or with `deleted=true` to remove; `price` alone to add), with `proration_behavior`:

* `create_prorations` (the default) creates pending invoice items: a credit for the unused time on what was last billed and a charge for the remaining time on the new items, prorated to the second (`proration_date` sets the time). They are billed on the next invoice.
* `always_invoice` invoices the prorations at once and collects them.
* `none` changes the items without prorations.

A change of billing interval, or `billing_cycle_anchor=now`, starts a new period at once, with credit for the unused time. During a trial, item changes cost nothing. Line descriptions follow Stripe's (`Unused time on 2 × Pro after 11 Jan 2026`, `Remaining time on Pro after 11 Jan 2026`, `1 × Pro (at $20.00 / month)`). `billing_mode` is `flexible`.

`incomplete` and `paused` subscriptions take no billing changes; canceled and expired ones take only `metadata` and `cancellation_details`.

**Canceling.** `cancel_at_period_end=true` or `cancel_at` (a timestamp, or `min_period_end` / `max_period_end`) schedules the end. A `cancel_at` within the period credits the time after it. `DELETE` cancels at once: `prorate=true` credits the unused time, `invoice_now=true` bills what is pending on a final invoice, and without either, pending prorations for the subscription are dropped. Canceling stops automatic collection on its open invoices and sends `customer.subscription.deleted`. Canceled subscriptions are left out of the list unless `status=canceled`, `ended`, or `all` is given.

**Pausing.** `pause_collection[behavior]` keeps the subscription running but changes its renewal invoices: `keep_as_draft` leaves them as drafts, `mark_uncollectible` and `void` finalize and then mark or void them. `pause_collection[resumes_at]` ends the pause at that time. `/pause` stops the subscription itself (`paused`, no invoices), and `/resume` restarts it, either keeping the cycle (`billing_cycle_anchor=unchanged`, billing the rest of the current period) or starting a new one now. With `payment_behavior=resume_on_payment_success` it resumes only once that invoice is paid. Both send `customer.subscription.paused` and `customer.subscription.resumed`.

```ts theme={null}
const sub = await stripe.subscriptions.create({
  customer: customer.id,
  items: [{ price: price.id, quantity: 2 }],
  trial_period_days: 14,
});
await stripe.subscriptions.update(sub.id, {
  items: [{ id: sub.items.data[0].id, quantity: 3 }],
  proration_behavior: "always_invoice",
});
```

Discounts are covered in [Coupons and promotion codes](#coupons-and-promotion-codes), and tax rates in [Tax rates](#tax-rates).

### Subscription items

* `POST /v1/subscription_items` — Add subscription item: to a `subscription` (`price` or `price_data`, `quantity`, `proration_behavior`)
* `GET /v1/subscription_items/:id` — Retrieve subscription item
* `POST /v1/subscription_items/:id` — Update subscription item: change its price or quantity
* `DELETE /v1/subscription_items/:id` — Delete subscription item: a subscription keeps at least one item
* `GET /v1/subscription_items?subscription=` — List subscription items: a subscription's items

These prorate like the same change through `POST /v1/subscriptions/:id`.

## Test clocks

* `POST /v1/test_helpers/test_clocks` — Create test clock: at `frozen_time` (`name` optional; `customer` moves an existing customer onto it, and then the time cannot be in the past)
* `GET /v1/test_helpers/test_clocks/:id` — Retrieve test clock
* `POST /v1/test_helpers/test_clocks/:id/advance` — Advance test clock: move it forward to `frozen_time`
* `DELETE /v1/test_helpers/test_clocks/:id` — Delete test clock: with its customers and their invoices
* `GET /v1/test_helpers/test_clocks` — List test clocks

Customers created with `test_clock` bill in the clock's time: invoices and invoice items record it in `created` and `date` and carry `test_clock`. Advancing runs everything that falls due on the way, in order, each at its own time, so one advance across three weeks runs every payment retry. A clock only moves forward, at most two years per advance. When its customers have subscriptions, an advance can cover at most two billing periods of the shortest one, as on Stripe. The advance finishes before the response returns. The response still says `status: "advancing"`, as Stripe's does, and the clock reads `ready` from then on, so code that polls for `ready` works unchanged. Each advance sends `test_helpers.test_clock.advancing` and `test_helpers.test_clock.ready`. As on Stripe, unfiltered invoice and invoice item lists leave out clock customers' objects; filter by `customer` to see them.

```ts theme={null}
const clock = await stripe.testHelpers.testClocks.create({ frozen_time: 1767225600 });
const customer = await stripe.customers.create({ test_clock: clock.id, payment_method: "pm_card_visa",
  invoice_settings: { default_payment_method: "pm_card_visa" } });
// ... create an auto-advancing invoice, then:
await stripe.testHelpers.testClocks.advance(clock.id, { frozen_time: 1767225600 + 3600 });
```

## Checkout

* `POST /v1/checkout/sessions` — Create Checkout session
* `GET /v1/checkout/sessions/:id` — Retrieve Checkout session
* `POST /v1/checkout/sessions/:id/expire` — Expire Checkout session
* `GET /v1/checkout/sessions` — List Checkout sessions: filter by `customer`, `status`, `payment_status`
* `GET /checkout/:id` — Hosted checkout page: HTML
* `POST /checkout/:id/complete` — Complete payment flow

`mode=subscription` takes recurring prices (one-time prices go on the first invoice) and `subscription_data` (`trial_period_days`, `metadata`). Completing the page creates the customer if there is none, saves the test card for off-session payments, and starts the subscription, so the session reports `subscription` and `invoice`. A session with a trial reports `payment_status: no_payment_required`. `mode=payment` refuses recurring prices.

`discounts` (one coupon or promotion code) applies a discount, or `allow_promotion_codes=true` shows a promotion code field on the hosted page (not both). The session reports `amount_subtotal`, `amount_total`, `discounts`, and `total_details.amount_discount`. A code typed on the page is checked like the API's; an invalid one shows "This code is invalid." In `mode=subscription` the discount goes on the subscription.

## Events

* `GET /v1/events` — List events: newest first. Filter by `type` (exact, or a wildcard such as `customer.*`), `types[]` (up to 20 exact types, not with `type`), `created` (exact or `gt`, `gte`, `lt`, `lte`), and `delivery_success`
* `GET /v1/events/:id` — Retrieve event

Every event the emulator sends is kept for 30 days, with `data.object` as it was when the event happened. Use this to reconcile after a missed webhook. `pending_webhooks` counts the endpoints the event has not reached yet, so it drops when a retry succeeds; `delivery_success=false` lists events with a failed or pending delivery.

## Webhooks

Events are delivered to webhook URLs from [seed config](#seed-config) (`stripe.webhooks`: `url`, `events`, optional `secret`, and `connect: true` for connected account events) and to endpoints created with `/v1/webhook_endpoints`.

### Webhook endpoints

* `POST /v1/webhook_endpoints` — Create webhook endpoint: `url` and `enabled_events` required; `description`, `metadata`, `api_version` optional
* `GET /v1/webhook_endpoints/:id` — Retrieve webhook endpoint
* `POST /v1/webhook_endpoints/:id` — Update webhook endpoint: `url`, `enabled_events`, `description`, `metadata`, or `disabled`
* `DELETE /v1/webhook_endpoints/:id` — Delete webhook endpoint
* `GET /v1/webhook_endpoints` — List webhook endpoints

Create returns a `whsec_...` `secret`, the only time it is returned, and deliveries to that endpoint are signed with it. `disabled: true` pauses deliveries and sets `status: "disabled"`. Webhooks from seed config are listed here too. `enabled_events` takes `*` or exact event types; `http` and `https` URLs, including localhost, are accepted.

### Deliveries and event types

* Webhooks configured with a secret include `Stripe-Signature: t=<timestamp>,v1=<signature>`, signed over `<timestamp>.<raw body>`
* Each event is a full Stripe event object: `id` (`evt_...`), `object: "event"`, `api_version` (`2026-09-30.endive`), `created`, `livemode: false`, `pending_webhooks`, `type`, `data.object`, and `request` with the `id` and `idempotency_key` of the API call that caused it. `*.updated` events include `data.previous_attributes` with the old values of the fields that changed.
* `customer.created`, `customer.updated`, `customer.deleted`
* `product.created`, `price.created`
* `payment_method.attached`, `payment_method.detached`, `payment_method.updated`
* `setup_intent.created`, `setup_intent.requires_action`, `setup_intent.succeeded`, `setup_intent.setup_failed`, `setup_intent.canceled`
* `payment_intent.created`, `payment_intent.requires_action`, `payment_intent.amount_capturable_updated`, `payment_intent.succeeded`, `payment_intent.payment_failed`, `payment_intent.canceled`
* `charge.succeeded` (the full charge object) when a payment intent is confirmed, `charge.failed`, `charge.captured`, `charge.refunded`
* `refund.created`, `refund.updated`
* `charge.dispute.created`, `charge.dispute.updated`, `charge.dispute.closed`, `charge.dispute.funds_withdrawn`, `charge.dispute.funds_reinstated`
* `account.updated` (to Connect endpoints, with `account`), `application_fee.created`, `transfer.created`, `transfer.updated`
* `invoiceitem.created`, `invoiceitem.deleted`
* `invoice.created`, `invoice.updated`, `invoice.deleted`, `invoice.finalized`, `invoice.sent`, `invoice.paid`, `invoice.payment_succeeded`, `invoice.payment_failed`, `invoice.payment_action_required`, `invoice.voided`, `invoice.marked_uncollectible`, `invoice_payment.paid`
* `invoice.overdue` when a `send_invoice` invoice passes its due date
* `invoice.upcoming` three days before a subscription renews
* `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted` (when a subscription is canceled or expires), `customer.subscription.trial_will_end`, `customer.subscription.paused`, `customer.subscription.resumed`
* `test_helpers.test_clock.created`, `test_helpers.test_clock.advancing`, `test_helpers.test_clock.ready`, `test_helpers.test_clock.deleted`
* `customer.updated` also fires when finalizing or voiding an invoice changes the customer `balance`
* `checkout.session.completed` when a checkout session is completed
* `checkout.session.expired` when a checkout session expires

## Seed config

```json theme={null}
{
  "stripe": {
    "customers": [
      {
        "email": "test@example.com",
        "name": "Test Customer"
      }
    ],
    "products": [
      {
        "name": "Pro Plan",
        "description": "Monthly pro subscription"
      }
    ],
    "prices": [
      {
        "product_name": "Pro Plan",
        "currency": "usd",
        "unit_amount": 2000,
        "recurring": {
          "interval": "month"
        }
      }
    ],
    "coupons": [
      {
        "id": "LAUNCH25",
        "percent_off": 25,
        "duration": "repeating",
        "duration_in_months": 3
      }
    ],
    "promotion_codes": [
      {
        "code": "SPRING",
        "coupon": "LAUNCH25",
        "first_time_transaction": true
      }
    ],
    "tax_rates": [
      {
        "id": "txr_sales",
        "display_name": "Sales tax",
        "percentage": 8.25
      }
    ],
    "accounts": [
      {
        "id": "acct_shop",
        "type": "express",
        "business_name": "Shop"
      }
    ],
    "billing": {
      "retry_days": [3, 5, 7],
      "after_final_retry": "cancel"
    },
    "webhooks": [
      {
        "url": "http://localhost:3000/api/webhooks/stripe",
        "events": ["*"],
        "secret": "whsec_test"
      }
    ]
  }
}
```

* `recurring`: optional; omit for a one-time price
* `coupons`: optional; percent\_off or amount\_off (with currency)
* `duration`: once (the default), repeating, or forever
* `promotion_codes`: optional; customer-facing codes for seeded coupons
* `first_time_transaction`: optional restrictions
* `tax_rates`: optional; id, display\_name, percentage, inclusive
* `accounts`: optional; connected accounts, onboarded unless onboarded: false
* `type`: standard (the default), express, or custom
* `billing`: optional; Stripe's automatic collection settings
* `retry_days`: days between payment retries (the default)
* `after_final_retry`: subscription after the last retry: cancel, unpaid, or past\_due

| Key | What it seeds |
| - | - |
| `customers` | Customers (`email`, `name`, `description`, optional `id`) |
| `products` | Products (`name`, `description`, optional `id`) |
| `prices` | Prices linked to a product by `product_name`, with `currency`, `unit_amount`, optional `recurring` and `lookup_key` |
| `coupons` | Coupons with `percent_off` or `amount_off` (with `currency`), `duration`, `duration_in_months`, `max_redemptions` |
| `promotion_codes` | Customer-facing codes for seeded coupons, with optional restrictions (`first_time_transaction`, `minimum_amount`) |
| `tax_rates` | Tax rates (`id`, `display_name`, `percentage`, `inclusive`) |
| `accounts` | Connected accounts, onboarded unless `onboarded: false` |
| `billing` | Automatic collection settings: `retry_days` and `after_final_retry` |
| `webhooks` | Webhook URLs (`url`, `events`, optional `secret`, and `connect: true` for connected account events) |

## Limits

* Connect: transfer reversals, refunds of application fees (`refund_application_fee`, `reverse_transfer`), payouts, persons and external account objects, and automatic billing (`auto_advance`, wall clock renewals) for connected accounts outside their own requests.
* Coupons: `discounts` on single invoice items or invoice lines (400 `parameter_unknown`) and `currency_options`.
* Tax: Stripe Tax (`automatic_tax[enabled]=true` returns 400) and flat amount rates.
* Credit notes: `shipping_cost` and line `tax_amounts`.
* Invoices: hosted invoice pages and PDFs (`hosted_invoice_url` and `invoice_pdf` are null).
* Subscriptions: Stripe Tax, metered and tiered prices, subscription schedules, billing thresholds, `pending_if_incomplete`, `trial_from_plan`, and `backdate_start_date`. They return 400 `parameter_unknown` rather than being ignored.

## Conformance

| | |
| - | - |
| Routes | 141 supported or partial, 0 planned |
| Route gate | fail |
| Spec gate | fail, 0 allowed ([stripe/openapi](https://github.com/stripe/openapi) at `e924802`) |
| SDKs tested | `stripe@23.0.0` |
| Dual mode | not yet |

See [CONFORMANCE.md](https://github.com/deploybaseline/cli/blob/main/CONFORMANCE.md) for every provider.


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