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_requiredwithapproval_id;executedstays 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.
