← Back to blog
Use CasesShopify11 min read

September 10, 2026

Stop a fourteen-refund batch before Shopify settles

Fourteen refunds, $3,200, settled in two minutes. Every Admin API call was valid. Every customer had asked. Nobody with authority had seen the queue as a queue. Prompt text that said "ask before large refunds" was not an evaluate.

Separate courtesy refunds from manager-gated amounts

A $10 courtesy refund should not wait for a human. A $250 refund may be legitimate and still need a manager. An inventory wipe should not share the same support tool path.

In that batch: four refunds under $25; ten above the approval threshold; four of those ten were duplicates from retries. Without a runtime gate, all fourteen settled. Finance saw the total, not which four should never have gone out.

Backstory: Gate Shopify Admin mutations for AI support agents.

Call the firewall, not Shopify

@limetry/shopify keeps SHOPIFY_ADMIN_TOKEN in the merchant runtime. The agent calls the firewall. The firewall builds an ActionIntent, evaluates, and proxies the Admin API mutation only on allow.

import { ShopifyActionFirewall } from "@limetry/shopify"

const firewall = new ShopifyActionFirewall({
  shopDomain: "acme.myshopify.com",
  adminToken: process.env.SHOPIFY_ADMIN_TOKEN!,
  apiKey: process.env.LIMETRY_API_KEY!,
  baseUrl: process.env.LIMETRY_BASE_URL,
  policyId: process.env.LIMETRY_SHOPIFY_POLICY_ID!,
  dryRun: false,
})

const result = await firewall.createRefund({
  orderId: "5839201741",
  amountMinor: 2500,
  currency: "USD",
})

if (result.evaluation.decision !== "allow") {
  process.stdout.write(JSON.stringify({
    executed: result.executed,
    decision: result.evaluation.decision,
    approval_id: result.evaluation.approval_id,
    reasons: result.evaluation.reasons ?? [],
  }, null, 2))
}

Example operating policy: allow refunds and discounts; deny inventory; park refunds at or above $25 as approval_required. No hard max_cost_minor on refunds — the manager is the ceiling — so a $250 refund parks instead of hard-denying. Approvals use the approval API for operator approval.

Map outcomes by amount and type

{
  "allowed_action_types": ["shopify.refund", "shopify.discount"],
  "denied_action_types": ["shopify.inventory"],
  "require_approval_action_types": ["shopify.refund"],
  "approval_cost_minor": 2500,
  "currency": "USD",
  "audit_mode": "minimal"
}
  • $10 refund → allow; firewall executes.
  • $25+ refund → approval_required with approval_id; executed stays false.
  • Inventory set-to-zero → deny; Shopify never sees the wipe.

With decision signing configured, an allow includes a receipt bound to the evaluated intent digest.

Counterfactual on the original batch

  • 4 refunds under $25 → allow.
  • 10 refunds → approval_required.
  • Manager approves 6, rejects 4 duplicates.
  • Four rejected duplicates ≈ $1,600 that stays in the merchant account.

Support keeps the agent. The Admin token stays behind the policy gate. Audit is decisions, not a spreadsheet after settlement.

Confidence ladder: Dry-run week.

Run Limetry on your own stack

Self-host the open source evaluation server, wire evaluate into your agents, and keep privacy-safe audit under your control.