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.

Just reading? See every step with sample output.
  1. 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 of ogen_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_action among them.

  2. 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.

    ABC
    1CustomerRefund addressEntered by
    2acme12 Harbor St, Oakland, CA 94607Billing
    3acme88 Pine Rd, Portland, OR 97205Support
    4globex5 Main St, Austin, TX 78701Billing

    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 calls bind_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.

  3. 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_approver call, then one register_action call. 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": true for you, and refund-customer registered as expensive to undo.

  4. 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_charge value from the reply. It starts with ch_.

    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.

  5. 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.

  6. 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.mjs

    Nothing 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.

  7. 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: passkey for your fingerprint, token for 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’s re_… reference.

Ready for your own systems? Read the docs.