Try Ogen in ten minutes
Ask your agent for a refund, watch Ogen hold it because your sheet disagrees with itself, decide it with your fingerprint, and let your own code run it. It ends on the record: what was read, who decided, how, and what ran.
You need Claude Desktop or Cursor, a Google account, a Stripe account in test mode, and Node 18 or newer. Everything runs in Stripe test mode. No real money moves.
-
1Get a free key and connect your client
Get a free key on the home page. It arrives by email and is shown once, so keep it. Holding an action on your own sheet needs Record, which is free until your tenth save.
For Claude Desktop, paste this into
claude_desktop_config.json, with your key in place ofogen_sk_…:{ "mcpServers": { "ogen": { "command": "npx", "args": ["-y", "mcp-remote", "https://us.ogenhq.com/api/mcp", "--header", "Authorization: Bearer ogen_sk_…"] } } }For Cursor, add this to its MCP settings instead:
{ "mcpServers": { "ogen": { "type": "http", "url": "https://us.ogenhq.com/api/mcp", "headers": { "Authorization": "Bearer ogen_sk_…" } } } }Restart the client so it picks up the new server.
You should see: Ogen in your client’s list of tools, with
commit_gated_actionamong them. -
2Put two disagreeing rows in a Google Sheet
Open a new sheet at sheets.new and type these cells. Or download the template and use File → Import → Upload → Replace current sheet.
A B C 1 Customer Refund address Entered by 2 acme 12 Harbor St, Oakland, CA 94607 Billing 3 acme 88 Pine Rd, Portland, OR 97205 Support 4 globex 5 Main St, Austin, TX 78701 Billing Billing and Support entered different refund addresses for Acme. That’s the disagreement Ogen will catch.
Share the sheet with “Anyone with the link”, role Viewer, and create a Google Sheets API key restricted to the Sheets API. The Google Sheet quick start walks through both. Then, in plain chat:
Using Ogen, bind the key customer.refund_address.acme to my Google Sheet. The sheet ID is 1AbC2dEfGhIjKlMnOpQrStUvWxYz1234567890, the range is Sheet1!A2:C200, customers are in column A and refund addresses are in column B, and my Sheets API key is AIzaSy…The sheet ID is the long string in the sheet’s URL, between
/d/and/edit. Ogen looks up the last part of the key,acme, in column A. Your assistant callsbind_source:{ "key": "customer.refund_address.acme", "source": { "kind": "gsheets", "sheet_id": "1AbC2dEfGhIjKlMnOpQrStUvWxYz1234567890", "range": "Sheet1!A2:C200", "key_column_index": 0, "value_column_index": 1, "api_key": "AIzaSy…" } }Then ask: “What’s Acme’s refund address right now?”
What comes back
{ "bound": true, "key": "customer.refund_address.acme", "kind": "gsheets", "label": "1AbC…" } { "status": "ambiguous", "candidates": [ { "value": "12 Harbor St, Oakland, CA 94607", … }, { "value": "88 Pine Rd, Portland, OR 97205", … } ], … }You should see:
"bound": true, then both of Acme’s refund addresses side by side. Ogen doesn’t pick one. -
3Register the refund, with you as its approver
In plain chat, with your own name and email:
Register me as an Ogen approver: name Your Name, email you@example.com. Then register an action called refund-customer that depends on customer.refund_address.acme, cost expensive, approver Your Name.That’s one
register_approvercall, then oneregister_actioncall. The approver comes first: an action can only name someone already registered.{ "name": "Your Name", "email": "you@example.com" } { "kind": "refund-customer", "key": "customer.refund_address.acme", "cost": "expensive", "approver": "Your Name" }What comes back
{ "registered": true } Registered "refund-customer" -> "customer.refund_address.acme" (cost: expensive, decide_with: token_or_passkey, holds last 24 h). commit_gated_action will honor it from now on.You should see:
"registered": truefor you, andrefund-customerregistered as expensive to undo. -
4Ask your agent for the refund
First, make a test charge to refund. In a terminal, with your Stripe test key:
curl https://api.stripe.com/v1/payment_intents \ -u sk_test_…: \ -d amount=2000 -d currency=usd \ -d payment_method=pm_card_visa -d confirm=true \ -d "payment_method_types[]=card"Copy the
latest_chargevalue from the reply. It starts withch_.Then, in plain chat, with that charge id:
Using Ogen, refund $5 to Acme on charge ch_… Call commit_gated_action with kind refund-customer and args { "charge_id": "ch_…", "amount": 500 }.Your assistant calls
commit_gated_action:{ "kind": "refund-customer", "args": { "charge_id": "ch_…", "amount": 500 } }What comes back
{ "committed": false, "disposition": "escalated", "reason": "fact_unresolved", "confirm_url": "https://us.ogenhq.com/confirm/…", "notified": true, "approver": "Your Name", "asked_with": "…", … }You should see:
"committed": false. The refund is held for you, because the two rows disagree. Nothing ran, and an email is on its way to you. -
5Decide with your fingerprint
Open the email from Ogen and follow its link. The page shows both live addresses, and “Asked with” followed by the same characters your agent reported as
asked_with. If the page asks, press Email me the sign-in link and open that email.Press Use your fingerprint next time and touch the sensor. Setting it up decides nothing. Then press Confirm with your fingerprint. If your device has no fingerprint sensor, press Confirm from the emailed link instead.
You’re deciding the fact: which refund address holds. Ogen keeps only a fingerprint of the charge and amount, and the ticket works for those exact ones only.
You should see: a box titled “Give this to the system that runs the action”, holding your release ticket. Copy it. It works once, for 15 minutes.
-
6Run the refund with your Stripe test key
The adapter is one file that runs on your machine with your own Stripe key. It checks the ticket, refunds once, and tells Ogen what happened. Ogen never holds your Stripe key and never runs the refund.
Stripe test mode only. Use a key that starts with
sk_test_. The adapter refuses a live key.Find your tenant id:
curl -s https://us.ogenhq.com/v1/export -H "Authorization: Bearer ogen_sk_…" | grep -o '"tenant":"[^"]*"'Then download the adapter and run it, with the ticket from step 5 and the same charge and amount your agent asked with:
curl -O https://ogenhq.com/static/ogen-refund-adapter-1.0.0.mjs OGEN_TENANT=your-tenant-id OGEN_KIND=refund-customer STRIPE_KEY=sk_test_… \ OGEN_TICKET='paste-the-ticket-here' CHARGE_ID=ch_… AMOUNT=500 \ node ogen-refund-adapter-1.0.0.mjsNothing to install. A different charge or amount is refused, and so is the same ticket a second time.
What comes back
{ "refund": "re_…" }You should see:
"refund": "re_…", and the same $5.00 refund in your Stripe test dashboard. -
7Read the record
This is what the whole run was for. Download your decision record:
curl -s https://us.ogenhq.com/v1/export -H "Authorization: Bearer ogen_sk_…"Under
decisions, the refund is one row:What comes back
{ "at": "…", "actor": "Your Name", "kind": "confirmation_decided", "subject": "…", "detail": { "decision": "confirm", "key": "customer.refund_address.acme", "action": "refund-customer", "action_id": "…", "held_since": "…", "decided_by": "Your Name", "decided_via": "passkey", "ticket_id": "…", "outcome": "ran", "reference": "re_…", "outcome_reported_at": "…" }, "evidence": [ { "key": "customer.refund_address.acme", "at": "…", "state": "ambiguous", "fingerprint": "…" } ] }- evidence
- The fact, read live from your sheet when your agent asked. A fingerprint of what was read, never the address itself.
- held_since
- When Ogen held the refund instead of letting it run.
- decided_by
- Who decided: you, by your registered name.
- decided_via
- How:
passkeyfor your fingerprint,tokenfor the emailed link. - ticket_id
- The release ticket your decision issued. The ticket itself is never stored.
- outcome
- What your adapter reported:
ran. - reference
- Stripe’s own refund id, so the record points at the money that moved.
Your tenant page shows the same decision under Recent decisions.
You should see: one row that tells the whole story: read live, held, decided by you with your fingerprint, ticket issued, and
"outcome": "ran"with Stripe’sre_…reference.
Ready for your own systems? Read the docs.