← Back to blog
Use CasesShopify9 min read

July 25, 2026

Gate Shopify Admin mutations for AI support agents

A design partner's support agent issued fourteen refunds totaling $3,200 in under two minutes. Every refund was a valid Admin API call. Nobody with authority approved the batch. Shopify settled each one. Limetry was not in the path.

The agent was not malicious. The missing control was approval_required before Shopify received a mutation.

Keep the Admin token out of the agent

Useful support agents need write scopes. Once the agent holds the Admin token, every refund, discount, and inventory write is one tool call away.

Per-request logic can look correct while aggregate risk is unguarded: retries, prompt injection, or a burst of legitimate returns. Prompt instructions are advisory. They are not evaluate.

Prefer a runtime gate over prompt rules

"Only process refunds under $25" in the system prompt is not an evaluate. A long thread or malformed tool-call JSON can ignore it, and you still lack an allow/deny/approval_required row you can query. Put the gate in the process that holds the Admin token: evaluate first, then maybe proxy to Shopify.

Install @limetry/shopify in the agent runtime

This is not a Shopify App Store install. Merchants keep the Admin token in their backend. @limetry/shopify (open source) owns SHOPIFY_ADMIN_TOKEN. The agent never sees the raw token. It calls firewall.createRefund({ orderId, amountMinor }). The firewall evaluates an ActionIntent and proxies to Shopify 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!,
  policyId: process.env.LIMETRY_POLICY_ID!,
  dryRun: true,
})

const result = await firewall.createRefund({
  orderId: "5678",
  amountMinor: 2500, // $25.00
})

Store policy on your self-run Limetry server. Declare allowed types (shopify.refund, shopify.discount), maximum cost per intent, and which types park as approval_required. The demo policy in packages/shopify/policies/refunds.json is deny-only for demonstration; createShopifyActionPolicy adds refund approval via require_approval_action_types.

Handle allow, deny, and approval_required

  1. allow — the mutation may proceed. With decision signing configured, the decision carries a signed receipt for downstream verification.
  2. deny — hard stop with structured reasons. The agent can report that store policy blocked the action.
  3. approval_required — parked until an operator approves via POST /v1/approvals/:id/approve with the same intent payload hash. The agent retries after allow.

Shopify action types (shopify.refund, shopify.inventory, shopify.discount) use the same ActionIntent contract as the rest of Limetry.

Query privacy-safe audit

Every evaluate writes a privacy-safe audit trail (minimal by default): action type, cost field, decision, timestamp, tenant — not raw customer payloads, card data, or chat. Answer "how many refunds did the agent attempt, and what did policy do?" from audit, not from Shopify payout reports.

Operational notes

Start with dryRun. Run dryRun: true and inspect audit until classifications match production traffic, then set dryRun: false.

Treat inventory separately. Setting inventory to zero looks like a normal stock adjustment at the API level. Define shopify.inventory as its own action type and choose deny or approval_required per merchant.

Hold the token; evaluate before Shopify sees the mutation. The fourteen refunds were valid Admin API calls. What was missing was deny for the batch, approval_required for the manager, and an audit row finance could open before settlement.

Where it ships

@limetry/shopify is open source in the Limetry repository. CI starts a Limetry node, applies a policy, and runs three evaluations (allow a small refund, deny a large one, deny an inventory wipe) without a live Shopify store. For production, set SHOPIFY_ADMIN_TOKEN and dryRun: false. Point evaluate at your self-run server and use the approval API for operator approvals.

Related: When fourteen refunds settle before coffee and 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.