Human Approval for AI Agent Price and Inventory Changes
Stop a repricing or inventory-management agent from pushing a bad price or stock count live by routing every change through a human approval card first.
Why this needs a gate
A pricing agent that watches competitor prices, demand signals, or stock levels and adjusts your listings accordingly is genuinely useful — until it isn't. The failure modes are specific and well known in this space: a scraped competitor price of "$9.99" that was actually a shipping fee, a demand model that decides a stockout means "raise price 40%" during a moment that looks bad publicly, or a decimal error that lists a $299 item at $2.99 and sells out the warehouse in ten minutes. None of these look like bugs from inside the agent's own reasoning — they look like confident, well-justified decisions.
Unlike a single email or a single blog post, a pricing agent usually wants to change many SKUs in one pass. That changes the shape of the approval problem: reviewing forty individual pop-ups is not realistic for a human, so the gate has to work as a batch, not just a queue of singles.
The MCP flow, one SKU at a time
For an agent running inside an MCP client (Claude Code, Claude Desktop, or your own MCP host), each proposed change is one impri_push_action call:
type PriceChange = {
sku: string;
currentPrice: number;
proposedPrice: number;
reason: string;
};
async function proposePriceChange(change: PriceChange) {
const { action_id } = await mcp.call("impri_push_action", {
kind: "price.update",
title: `${change.sku}: $${change.currentPrice} → $${change.proposedPrice}`,
preview: {
format: "markdown",
body: [
`**SKU**: ${change.sku}`,
`**Current**: $${change.currentPrice}`,
`**Proposed**: $${change.proposedPrice}`,
`**Reason**: ${change.reason}`,
].join("\n"),
},
editable: ["preview.body"],
expires_in: 3600,
});
const decision = await mcp.call("impri_await_decision", {
action_id,
timeout_s: 1800,
});
if (decision.status !== "approved") return; // rejected or expired: price stays put
await updateListingPrice(change.sku, change.proposedPrice);
await mcp.call("impri_report_result", { action_id, status: "executed" });
}A one-hour expires_in matches how fast pricing data goes stale — a price change approved eight hours later against a competitor snapshot from that morning is approving something that may no longer be true.
Batching the review
When the agent produces a whole repricing pass — say thirty SKUs at once — pushing thirty separate actions and letting the human triage them from the inbox is the right shape, not thirty individual interruptions. The inbox supports exactly this: POST /v1/actions/bulk-decision lets a reviewer approve or reject a set of pending actions in one pass instead of opening each card individually. Design the agent to tag related actions with a shared prefix in the title ("Weekly repricing: SKU-1042 ...") so they group visually in the inbox even though each one is still gated independently.
What to gate vs. what to let run
| Change | Gate it? |
|---|---|
| Price change beyond ±5% of current | Yes |
| Price change within ±1% (rounding, currency sync) | Usually not worth the interruption |
| Marking an item out of stock from a real inventory feed | Depends on your risk tolerance — often auto |
| Marking an item back in stock from a supplier feed | Same as above |
| Any change driven by scraped/unverified competitor data | Yes, always |
| Bulk changes affecting more than N SKUs at once | Yes, and batch the review |
Pick thresholds you can defend later, and let the boring, high-confidence cases (small rounding syncs, verified inventory feeds) skip the gate — a gate that fires on everything trains reviewers to rubber-stamp, which defeats the purpose.
Boundaries
Impri does not know your pricing model, your margin floor, or your competitor data — it only stores what the agent proposed and holds the human's decision on it. The threshold logic above belongs in your agent, not in Impri. And this only holds as a real gate if the agent's price-update function requires an approved decision to run — if it also has a separate cron job that writes prices directly to the store, the gate has a hole in it.
See the base integration pattern for the full three-call flow, MCP setup if you haven't wired the server in yet, and integrations for wrapping the actual store-update call so the approved path is the only path.