> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tybritelabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Guardrails

> The refusals built into the platform — what it declines to do even when the request is authenticated, authorized, and well-formed.

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](/agentic-commerce). 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.
