Skip to main content
About 5 minutes. You start a Stripe account whose ledger is completely empty, your own coding agent takes a $42 payment and then refunds it, and you read the twin’s own record of every call it made — plus the two balance entries that prove both legs happened.
A digital twin is not a mock. It is a stateful service that answers the same REST and MCP calls as api.stripe.com, boots from a declared starting state, records every request, and never reaches Stripe. This is the twin that exists for the operations you would never point at production: money moves, a ledger records it, and none of it is real.

Before you start

  • A Pome account. pome login opens the browser sign-in and creates one if you do not have it. No credit card.
  • Your own coding agent — Claude Code, Cursor, anything that can run a shell command and read JSON back.
  • Node 18+ for npx, plus curl and jq for the transcripts below. Your agent can read the raw JSON without jq; it is here to keep the blocks short.
Nothing else. No Stripe account, no test keys of your own, no ANTHROPIC_API_KEY, no model inference paid for by Pome: your agent is both the operator and the actor. It drives Pome, and it is the thing that acts on the twin.
Nothing on this page is graded. No task file, no criteria, no score — and no agent eval is charged, because an eval is only ever burned when a run is graded. A sandbox you start, drive and stop costs you nothing. Grading appears exactly once in this curriculum, at the support-triage capstone, where the agent under test is sealed off from the criteria that judge it.

Paste this

Hand this to your coding agent as-is. It names the twin’s own Stripe surface and the boundary it must not cross.
One token opens a sandbox: POME_AUTH_TOKEN. It is the bearer on every call to the twin, REST and MCP alike. On most twins the secrets file also carries a provider-shaped token — POME_GITHUB_TOKEN=github_pat_…, POME_STRIPE_API_KEY=sk_test_… — and that one is not the bearer; it is what the twin serves inside the sandbox. The linear twin ships none at all. Send that provider-shaped token as the bearer, or send no bearer at all, and the proxy answers an opaque 404 readingNo twin pod for this session. So a 404 on a sandbox you just created is almost always the wrong bearer rather than a dead sandbox.

The world

sandbox create boots a Stripe twin from its declared starting state and hands back the URLs that reach it. It also writes the connection secrets to .pome-sandbox.env at mode 0600, and says so on stderr.
The world is an account that has never traded. Not “a test customer and a sample charge” — nothing:
available is not zero dollars — it is an empty list, because no currency has ever been in this account. That is the starting state, and it is the same every time you create this sandbox, which is what makes anything you observe next reproducible. It also means there is no seeded charge to refund: you build the money first, which is the honest version of this demo anyway.
Ids are minted per sandbox, so keep them in shell variables rather than pasting the ones above. Settling the deposit is what mints the charge:
Now the money is on the books — and this is the part you would never run twice against production:
Read it back through the twin’s own surface — not out of the response your agent already holds:
This is the thing worth noticing. The balance is back to 0, but it is not back to [] — the account now holds a currency it has traded in, and the ledger carries both legs rather than netting them away. amount_refunded moved 0 → 4200 and refunded flipped to true, read back through a different route from the one that changed them. The twin also kept its own event log:
Seven events for three writes, newest first, exactly as Stripe orders them — because a settled payment and a reversed one each emit more than one. Stop the sandbox and the whole account is gone.

Read the tape

Every call above was recorded by the twin as it happened. This is the part neither a mock nor a Stripe test account gives you: an account of the run written by the service, not by the agent.
Sixteen rows, and only three of them changed anything. Ask which:
You should see:
  • Sixteen rows, in the order they happened. Read top to bottom and the money story is legible without asking the agent what it did: it checked the account was empty, it created an intent, it settled it, it refunded it, it reconciled.
  • mut=true on exactly three rows. state_mutation means the call landed — a write the twin refuses reads false, not true. Thirteen of the sixteen calls here are reads, which is what a careful money agent’s tape should look like.
  • fid=semantic on /v1/refunds. That is the tier that matters most on this page: the refund route carries a full behavioural contract compared against a captured Stripe response, not a response shape with placeholder values. It refuses what Stripe refuses, with Stripe’s own codes — send that same refund a second time and you get 400 charge_already_refunded, not a duplicate row.
  • Three writes, seven events. The tape counts what the agent did; the event log counts what the account did about it. They are different numbers on purpose.
When you are done, stop the sandbox to free the slot — it also expires on its own 30 minutes after it was created:

What you could assert here

Nothing on this page was graded, but a tape and a final state are exactly what a graded check reads. The Stripe twin already declares these — no authoring required, and list_checks on the Pome MCP prints the full set with what each one actually compares: Pointers, not a task. Turning them into a graded exam is the capstone below, and Write a task is where the grammar lives.

How real is this twin?

Every surface you called above is compared against a captured response from the real Stripe API, re-run daily and published. The Stripe row on status.pome.sh carries the current count of twin responses that match, and it is the row worth reading carefully: it does not read “all of them”. A handful of Stripe surfaces have no captured baseline we are willing to compare against, so instead of quietly counting them as passes the row names each one as a ruled exception with a published id and reason. A twin that drifts turns that row red; a twin we have not fully verified says so out loud. Nobody has to take our word for the fidelity, including us. Which surfaces are covered, and which are shape-only, is on the Stripe twin reference.

Next: the one graded lesson

The support-triage capstone

The same twins, now an exam: a sealed agent under test, a deliberate failing score, and one line of prompt that turns it green.

Stripe twin reference

Every route and MCP tool the twin serves, by use case, with its fidelity tier.

Write a task

Turn the checks above into a graded exam for your own agent.

pome sandbox

Create, list and stop sandboxes — including multi-twin ones.