apps/stripe-clone, name stripe) is a faithful, API-only
clone of the Stripe 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.
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
Modes
This template is API-only — there is no frontend or
full mode.
Seeding
customers, products, prices, and invoices (the
template’s declared entities) through real /v1/* calls. See
Seeding.
Auth — the seeded secret key
spin stripe seeds one account and one secret key. The plaintext key (only its
sha256 is stored) is:
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).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):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}withlimit(default 10, max 100) /starting_after/ending_before, - form-encoded request bodies (nested
items[0][price]keys), theIdempotency-Keyreplay header, aRequest-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/sfor live keys,25 req/sfor test keys (matching Stripe’s published tiers); over the cap returns HTTP 429 with aStripe-Rate-Limited-Reasonheader and a{error:{code:'rate_limit', …}}body, - HMAC-signed outgoing webhooks with a
Stripe-Signature: t=…,v1=…header, and a byte-faithfuleventenvelope (api_version,pending_webhooks,request,data.previous_attributes).
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:
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.
The
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.Inspect a running clone
001_initial_schema.sql — read it to know what’s queryable.