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

# Stripe template

> A real Stripe-style REST API backend — customers, payments, invoices, subscriptions, refunds, and signed webhooks.

The Stripe template (`apps/stripe-clone`, name `stripe`) is a faithful, API-only
clone of the [Stripe](https://stripe.com) REST API: a NestJS backend on Postgres.
Auth is a Stripe-style **secret key** (`sk_test_…`) sent as a bearer token — no
signup/login, no per-user session. Every request is scoped to the account that
owns the key. It's faithful enough at the **URL + wire-format level** that an
agent built for Stripe can operate it.

```bash theme={null}
asymmetric spin stripe
```

## Routing — read this first

Real Stripe serves business routes at `/v1/*` (not `/api/v1/*`), while the clone
health probe is `/api/health`. So the backend uses **no global prefix**: business
controllers bind `@Controller('v1/...')` and a single health controller binds
`@Controller('api')`. Both `/v1/customers` and `/api/health` resolve faithfully.

## What's inside

| Piece                  | Notes                                                                                                                                                     |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `backend/`             | NestJS REST API, Bearer `sk_` auth, Postgres via `pg`. Business at `/v1/*`, health at `/api/health`, Swagger at `/api/docs`.                              |
| `supabase/migrations/` | `001_initial_schema.sql` + `002_constraints_and_helpers.sql` — the schema (17 tables), `gen_stripe_id`, status checks, run on spin and replayed on reset. |
| `seeds/`               | `acct.sql` — the bundled deterministic fixture.                                                                                                           |

## Modes

| Mode            | Services  | Use it for                        |
| --------------- | --------- | --------------------------------- |
| `api` (default) | `backend` | Agents that talk to the REST API. |

This template is **API-only** — there is no frontend or `full` mode.

## Seeding

```bash theme={null}
asymmetric spin stripe --seed acct        # deterministic fixture on create
asymmetric seed stripe-a1b2 --ai          # realistic data via the REST API
```

AI seeding generates `customers`, `products`, `prices`, and `invoices` (the
template's declared entities) through real `/v1/*` calls. See
[Seeding](/concepts/seeding).

## Auth — the seeded secret key

`spin stripe` seeds one account and one secret key. The plaintext key (only its
sha256 is stored) is:

```
sk_test_asymmetric000000000000stripe
```

Use it as a bearer token against the REST API — no signup/login step:

```bash theme={null}
curl -s http://127.0.0.1:3003/v1/customers \
  -H "authorization: Bearer sk_test_asymmetric000000000000stripe"
```

Real Stripe also accepts the key as the HTTP Basic username, and so does the
clone:

```bash theme={null}
curl -s -u sk_test_asymmetric000000000000stripe: http://127.0.0.1:3003/v1/balance
```

<Note>
  There is no `POST /api/admin/provision` route (it returns 404 by design), so the
  CLI's provision step proceeds token-less — the seeded key above is the auth path.
  The optional `Stripe-Account` header is parsed but ignored (single seeded
  account).
</Note>

## Two flows worth pointing an agent at

The template is built around two end-to-end money-movement chains (both verified
against a live Postgres):

```bash theme={null}
B=http://127.0.0.1:3003
A="authorization: Bearer sk_test_asymmetric000000000000stripe"

# 1. Subscription that actually goes paid: creates items + the first invoice +
#    a PaymentIntent, confirms it to a charge + balance transaction, status=active.
curl -s $B/v1/subscriptions -H "$A" \
  -d customer=cus_JennyRosen0001 -d 'items[0][price]=price_ProMonthly01'

# 2. Partial then full refund: amount_refunded increments, a full refund sets
#    refunded=true, a negative balance transaction posts, GET /v1/balance reflects
#    it, and charge.refunded + refund.created webhooks fire.
curl -s $B/v1/refunds -H "$A" -d charge=ch_SeedPaid000001 -d amount=500
curl -s $B/v1/refunds -H "$A" -d charge=ch_SeedPaid000001
curl -s $B/v1/balance -H "$A"
```

Money movement is **simulated**: confirming a PaymentIntent with a seeded test
card (`pm_card_visa` / `pm_card_mastercard`) succeeds synchronously. There is no
real PSP, 3DS/SCA, or async bank webhook.

## API shape — read this before pointing an agent at it

Unlike the Slack clone (REST-vs-RPC, capability-level match only), Stripe is
itself a REST API, so the clone matches it on the **same literal `/v1/…` paths**
with the **same wire format** — not just the capability level:

* prefixed object ids (`cus_`, `prod_`, `price_`, `pi_`, `ch_`, `re_`, `in_`,
  `sub_`, …) — no UUIDs on the wire,
* cursor pagination wrapped in `{object:'list', url, has_more, data}` with
  `limit` (default 10, max 100) / `starting_after` / `ending_before`,
* form-encoded request bodies (nested `items[0][price]` keys), the
  `Idempotency-Key` replay header, a `Request-Id: req_…` response header, and the
  `{error:{type,code,message,param}}` error envelope,
* per-resource search at `GET /v1/<resource>/search`, wrapped in
  `{object:'search_result', url, has_more, data, next_page}`,
* per-account **rate limiting** — `100 req/s` for live keys, `25 req/s` for test
  keys (matching Stripe's published tiers); over the cap returns **HTTP 429** with
  a `Stripe-Rate-Limited-Reason` header and a `{error:{code:'rate_limit', …}}` body,
* HMAC-signed outgoing webhooks with a `Stripe-Signature: t=…,v1=…` header, and a
  byte-faithful `event` envelope (`api_version`, `pending_webhooks`,
  `request`, `data.previous_attributes`).

The template ships an `API_PARITY.md` with the per-endpoint breakdown, re-audited
against Stripe's live docs. The implemented core spans customers, products,
prices, payment methods, payment intents (both automatic and manual capture),
charges, refunds, invoices + invoice items, subscriptions + subscription items, a
read-only balance, an events feed, and webhook endpoints — **72 endpoints**:

| Status                                             | Count |
| -------------------------------------------------- | ----- |
| ✅ exact (path + convention + envelope align)       | 72    |
| ❌ missing (sub-actions outside the targeted slice) | 10    |

That's **88% of the targeted core surface**, on exact Stripe paths. Whole Stripe
surfaces outside that core (Connect, Checkout, Terminal, Issuing, tax, disputes,
SetupIntents, …) are unimplemented by design — this is an MVP+Beta subset, not the
full API. A handful of sub-actions in scope are also not yet implemented
(`payment_intents/:id/increment_authorization`, `invoices/:id/send`,
`subscriptions/:id/resume`, customer-scoped payment-method reads). Check
`apps/stripe-clone/API_PARITY.md` for the full list, including the documented
`?expand[]` allowlist.

<Info>
  The [`verify`](/cli/login#verify) command will fold live fidelity scoring into
  the CLI. Until then, `API_PARITY.md` and the `/verify-api` workflow are how
  fidelity is tracked.
</Info>

## Inspect a running clone

```bash theme={null}
asymmetric query stripe-a1b2 "select stripe_id, email from customers"
asymmetric db stripe-a1b2          # prints a psql shell command
asymmetric logs stripe-a1b2 -f     # stream the backend
```

The schema (customers, charges, invoices, subscriptions, …) comes straight from
`001_initial_schema.sql` — read it to know what's queryable.
