Security
Every scope Relay holds
Three of these eight scopes can write, and only one of the three moves money.
trigger
Eight scopes are held
Six systems, one credential each: a restricted Stripe key, a read-only replica role, an OAuth app per inbox, and one Slack bot token.
check
Can the scope write?
The credential decides, not the prompt. A read-only role cannot be talked into a write.
reads · 5 of 8
lookup
Five only read
Stripe subscriptions and invoices, the accounts replica, the help center and the ticket thread. Nothing they touch changes.
writes · 3 of 8
step
Three can write
One reply per ticket, one handoff message, and account credits. Every write is scoped to a single object.
check
Does the write move money?
Two of the three writes only post text. The third is the only path from Relay to your balance.
words · 2 of 3
step
Two post text
helpdesk.replies.write posts one reply on one ticket. slack.handoff.write posts into the one channel that owns the account.
money · 1 of 3
limit
One moves money, capped at $50
stripe.credits.write is checked against the ceiling in the tool layer, before the call is made.
handoff
The refund goes to a person
ZD-4821 asked for $70.97 against the $50 cap, so the scope was never used and Dana Okonjo answered instead.
Read the model as text
Eight scopes are held
trigger · starts the run
Six systems, one credential each: a restricted Stripe key, a read-only replica role, an OAuth app per inbox, and one Slack bot token.
Can the scope write?
check · after Eight scopes are held
The credential decides, not the prompt. A read-only role cannot be talked into a write.
Five only read
lookup · after Can the scope write? · branch reads (5 of 8)
Stripe subscriptions and invoices, the accounts replica, the help center and the ticket thread. Nothing they touch changes.
Three can write
step · after Can the scope write? · branch writes (3 of 8)
One reply per ticket, one handoff message, and account credits. Every write is scoped to a single object.
Does the write move money?
check · after Three can write
Two of the three writes only post text. The third is the only path from Relay to your balance.
Two post text
step · after Does the write move money? · branch words (2 of 3)
helpdesk.replies.write posts one reply on one ticket. slack.handoff.write posts into the one channel that owns the account.
One moves money, capped at $50
limit · after Does the write move money? · branch money (1 of 3)
stripe.credits.write is checked against the ceiling in the tool layer, before the call is made.
The refund goes to a person
handoff · after One moves money, capped at $50
ZD-4821 asked for $70.97 against the $50 cap, so the scope was never used and Dana Okonjo answered instead.
stripe.subscriptions.read
Stripe
readRestricted API key
Plan, period and the proration lines behind a charge
stripe.invoices.read
Stripe
readRestricted API key
Invoices, charges and receipts
stripe.credits.write
Stripe
writeRestricted API key
Account credits capped at $50, the only scope that moves money
postgres.accounts.read
Postgres replica
readRead-only replica role
Entitlements, seat counts and trial end dates
docs.search
Help center
readPublic URL, no key
Published articles, for the citation on every reply
helpdesk.threads.read
Zendesk · Front · Intercom
readOAuth app, per inbox
The ticket thread and its history
helpdesk.replies.write
Zendesk · Front · Intercom
writeOAuth app, per inbox
One reply per ticket, sent or left as a draft
slack.handoff.write
Slack
writeBot token, one channel
The handoff message, in the channel that owns the account
Five systems accept a write. The accounts replica is read only, and no scope reaches your primary database.
Guardrails
Four rules, enforced in code
Your security review will ask these four questions, so here are the answers first.
It cannot move money over $50
The ceiling is a number in your config, checked in the tool layer before the call is made. A prompt cannot argue with it.
It cannot send an uncited answer
Below 0.8 match against your help center, Relay drafts and escalates instead of sending. The threshold is yours to move.
It holds no write credential
Account data comes from a read replica. There is no path from Relay to a row in your primary database.
Every action keeps its cause
Each call is logged with the ticket, the inputs and the rule that allowed it, kept 13 months and exportable as CSV.
Audit
The record every call leaves
A blocked call keeps the same fields as a sent one, so nothing is missing.
13 months
Kept on every row
Exported as CSV from the console, or read through the audit API with a scoped key.
Processed in eu-west-1. Three sub-processors: the model provider, the cloud host and the error store.
One row, in full
audit.log · row 7 of 8- ticket
- ZD-4821
- call
- policy.check
- inputs
- refund · $70.97 · in_9QtL4M
- rule
- refund.ceiling · $50
- outcome
- blocked · exceeded by $20.97
- at
- 09:31 · +17m02s
- kept until
- 12 Apr 2027
The seven other rows from this run are on the run log. Read all eight
Put Relay on the billing queue
Connect Stripe and one helpdesk, approve its first 50 replies, then let it send.
- No card to start
- You approve its first 50 replies
- Cancel any month