Skip to main content
Security answers who can reach what. Guardrails answer a different question: what the platform declines to do even when the caller is authenticated, authorized, and asking correctly. The two are complementary, and the second is the one that catches the expensive mistakes. A valid secret key with full permissions can still be used to sell below cost, take a payment through a test processor, or run an automation into the ground. Every guardrail below exists because that outcome is worse than the request failing. They share one property: they are enforced in the platform, not in the interface. A guardrail that lives in a form is a suggestion — anything calling the API directly walks around it. These are checked where the write happens.

Money never moves without a person

The in-app assistant can prepare gift cards, promotions, and pricing rules from what it sees in a store. It cannot issue any of them. Each one is written as a draft — a real row, inert until approved — and surfaced where the merchant already manages that kind of thing. Approval is a separate, explicitly granted permission, checked on the server at the moment of approval rather than assumed from whoever created the draft. The limits are re-validated then too, against the row as it stands: a draft that was within bounds when prepared is checked again before it goes live, and a plan that lapsed in between blocks it. The same rule governs dynamic workflows. A workflow can rotate a collection or retag products on its own, because those are reversible. It cannot run a discount — it prepares one and waits. Approval is not the only limit on those drafts, because “one approval at a time” is weaker than it sounds once a rule runs on a schedule. A workflow that drafts a gift card for every customer meeting a threshold is bounded on both how many it may prepare in one run and their combined face value, and a run matching more than that stops and reports it rather than quietly choosing a subset — being told the rule is too broad is more useful than a silent partial result. A cooldown prevents the same customer being rewarded again on the next run, which is the failure that would otherwise compound quietly at every cadence. These ceilings live in the database and are re-clamped on every call, so a rule can narrow them but never widen them, whatever the request asks for. A markdown pricing rule is bounded differently, by scope: it covers only the products it actually identified rather than the whole catalogue, and only one such draft is outstanding at a time so a weekly rule cannot stack duplicates while a merchant is deciding.

Margin is not assumed

Any automated action that affects price first checks that the store’s cost data is complete enough to reason about. The reason is specific: when a cost price is missing, margin arithmetic treats it as zero, which reads as 100% margin. A discount computed on that basis looks comfortably profitable and can be sold below cost. So rather than act on a plausible-looking number, the platform stops when coverage is too thin and says which products need costs filled in. This is a refusal, not a warning. It applies to the assistant, to standing workflows, and to advertising rules alike.

Test credentials cannot serve a real customer

A store can be live while a payment provider is still configured with test credentials. Left unchecked, a shopper would complete checkout against a sandbox processor, see a confirmation, and pay nothing. On a production request, a provider whose credentials are not live-grade is hidden from the available payment methods and refused at initialization. The shopper cannot select it, and a direct API call gets an explicit error rather than a phantom success. The check applies to production requests only. A sandbox key sees every provider, including ones still on test credentials — that is what sandbox is for, and hiding them would make the mode useless for the testing it exists to support. So a sandbox checkout listing providers marked test or sandbox is the system working, not a leak; point a live key at the same store and those providers are gone. The same asymmetry holds for every integration below: sandbox is permissive, production is strict. The merchant’s own in-store point of sale behaves differently on purpose: it warns rather than blocks, because that is the merchant’s own device and they may be mid-setup. The distinction is who is transacting — never let a customer pay into a test account; do let a merchant test their own till. The same rule extends past payments to every integration a merchant connects themselves, because every one of them has a test mode and a merchant can be live to shoppers while still holding test credentials:
  • Tax. A store connected to a sandbox tax account is not used to price real orders — a test engine’s rates are not the ones that get filed. The store’s own configured rates apply instead, so the shopper is still charged a number the merchant chose.
  • Shipping. A test carrier token cannot buy a label for a production order. A test label is not accepted by any carrier, and buying one would mark the order shipped and send the customer a tracking number that never moves. Live rates are simply not offered, and the store’s own delivery pricing stands.
  • Subscription billing. A billing processor configured with test credentials is not offered and is refused if named directly.
  • Advertising. Automation rules run against a real ad account and move real budget, so only rules created in production ever execute. A rule authored while testing stays inert.
  • Accounting. Ledger entries are never posted into a sandbox accounting company. When the connection is not pointed at a real one, entries stay queued rather than being sent — so nothing is lost, and the queue delivers once the connection is corrected. An entry that posts into a test book looks identical to a successful sync, which is precisely why it is refused instead.
Where a sensible fallback exists the request degrades to it rather than failing; where a soft failure would leave a false record — an order marked shipped, a tracking number sent, a journal entry filed in the wrong book — it is refused outright. Either way the merchant is told, on a cadence set by how much the state is costing them: daily when nothing can be sold, weekly when the store still trades correctly but not the way they configured it. A guarded action is also not offered. The refusal exists because the API is callable directly, but a merchant meets a sentence explaining the state rather than a control that fails — and that state is shown on the surface whose owner can fix it: never to a shopper, to the merchant for their own integrations, and in full to the operator, who is the one who corrects it.

Test and live data never mix

The key prefix decides the environment, and it decides everything downstream: which partition is read and written, and which mode payment providers run in. There is no request that reads across the boundary, and no flag that relaxes it. This extends past data into behaviour. Notifications, automated actions, and scheduled work are all production-scoped — a sandbox order does not email anyone, and a sandbox automation does not act on a live store. Sandbox data is purged on a schedule, so test runs don’t accumulate.

Automations stop themselves — and say so

Anything that runs on its own carries a circuit breaker. Repeated consecutive failures disable it rather than letting it retry indefinitely; a single success resets the count, so a brief outage costs a few failed runs instead of the automation itself. The important half is the second one: when something disables itself, the merchant is told. A silently disabled automation is worse than one that never ran, because they still believe it is working and discover otherwise when something didn’t happen. The same applies to autonomous action — an advertising rule that moves a budget or pauses a campaign says so, and says why. Every automated run is recorded with the figures that triggered it, so “why did this change?” always has an answer. A standing rule also stops when its output is not being used. A rule that writes content pauses once three of its drafts are sitting unread, and resumes on its own once some are published or discarded. The merchant has already shown they are not reading them, and every further run would spend a model call and image credits producing more of the same — so the backlog is the signal, and clearing it is the reset. Nothing has to be re-enabled by hand. A rule scoped to a season behaves the same way: out of season it is skipped rather than switched off, which is what lets it come back the following year with its settings and history intact.

Nothing is written until it is asked for

The assistant can prepare things, but the merchant’s words decide whether it does. A question about a capability — “can you write a blog post?”, “what would that look like?” — is answered, not performed, even though the same verbs appear in both. An explicit instruction to hold off (“just asking”, “not yet”, “don’t create it”) outranks everything else in the turn, including a request that would otherwise read as a command. The bias is deliberate: a question mistaken for an instruction leaves the merchant with something to undo, while an instruction mistaken for a question costs one more message. Where the reading is genuinely ambiguous, the assistant asks. Written content is never published on the merchant’s behalf, whether a person asked for it or a schedule produced it. It is their public voice, so it lands as a draft and stays there until they publish it.

Prices are the server’s to decide

Order totals are computed from the catalog, the store’s pricing rules, and any promotion that applies — never from amounts supplied by the caller. A client that submits its own price for a line item does not get that price. The same posture covers stock: availability is decided at reservation time inside a single-writer path, so two shoppers racing for the last unit produce one sale and one honest failure rather than an oversell.

Everything is bounded

Requests are rate-limited per key, and generous per-store quotas cover the work that costs real money — API traffic, assistant usage, generated media. Crossing a quota is designed to be recoverable rather than a wall, but the ceiling is always present, and it is enforced server-side. Automated work carries its own bounds: a maximum number of steps per run, a timeout per call, a cap on how many standing rules a store can hold. None of these are configurable from the client.

What this means for an integration

Most of the time these are invisible — a correct integration never meets them. When you do, you’ll get an explicit error rather than a silent partial success, and that is deliberate: a guardrail that fails quietly is worse than no guardrail, because it teaches you to trust something that isn’t happening. If a call is refused for one of these reasons, the response says which. Treat it as information about the state of the store, not as a transient failure to retry.