Scoped API keys for agents: what seo:read should and should not include
Read and write is not enough of a split for an agent's API key. The scope that spends money has to be its own, or a read-only key quietly is not one.

An seo:read scope should cover reading indexing state, crawlability, structured data and search results already bought and stored. It should not cover buying a new search result, and in Market4 it does not: that is a third scope, seo:search, which exists because buying spends a shared monthly allowance. Splitting a namespace only into read and write puts a paid action on the same key as a free one, and an agent holding that key has no way to stay inside the free half.
Read and write answers one question — does this call change anything — and an API key for an agent needs the answer to a second one: does this call cost anything. Those two questions cut the surface differently. Fetching a fresh search result changes nothing on your own systems, so by the read-and-write test it is a read; it also draws down a prepaid allowance, so by the cost test it is the heaviest call in the namespace. A key granted a read scope in good faith would be able to spend the month's budget.
That is the reasoning behind splitting the search namespace three ways rather than two. Reading what has already been paid for is genuinely free and belongs with the other reads. Submitting to IndexNow or running an index sweep changes the outside world but costs nothing, so it belongs in the write scope. Buying a snapshot gets its own scope, and a key can be issued with the first two and not the third.
| Scope | What it grants | Changes anything | Costs anything |
|---|---|---|---|
| seo:read | Indexing state, crawlability, structured data, snapshots already bought | No | No |
| seo:write | Submit to IndexNow, run index sweeps, generate structured data | Yes, outside | No |
| seo:search | Buy SERP snapshots | No, on our side | Yes — the search allowance |
A read scope should include everything already collected and paid for, including expensive data, because withholding it does not save anything. Stored search snapshots are the clearest case: the money is spent, the rows exist, and refusing to let a read key see them means an agent has to buy a second copy of something you already own. That is the opposite of the saving the restriction was meant to produce, and it is a common way for a well-meant scope design to increase spend.
What a read scope should leave out is anything whose disclosure is itself the risk. The pattern used across this product's scopes is to grant the read and mask the secret: listing webhook endpoints is allowed under a read scope while the signing secrets stay masked, and reading the mail settings is allowed while the password is never returned. That is a better answer than refusing the whole read, because it lets an agent verify configuration without ever holding the credential.

An agent that can write its own audit row can write a plausible one. Four properties keep the trail out of reach: the row is written on the far side of the tool boundary, storage is append-only, the actor is a reference, and recording never fails the operation.

Market4 turns one release note into a changelog page, a blog post, a mail-out and a week of social posts — and then tells you which of them brought anyone back.
A scope list is too long when nobody can tell which one an action falls under, and too short when a key has to be over-granted to do an ordinary job. Market4 currently defines 27 scopes across the whole API, mostly as read and write pairs per area — apps, changelog, blog, feedback, footer, social, analytics, keywords, media, webhooks and settings — with the search namespace split three ways and two standalone permissions for deciding approvals and for the organisation kill switch.
Two of those standalone scopes are worth calling out because they resist the read and write shape entirely. Deciding an approval is not a write of content — it is permission to release something somebody else prepared, and an agent that could both request and approve its own action would make the whole gate decorative. Pausing the organisation-wide kill switch is similar: it is not writing anything, it is removing a brake.
Issue an agent's key with the scopes for the job it was given, and leave off every paid and every destructive one until the agent has demonstrated it needs them. In practice a research agent wants the read scopes plus nothing else, a drafting agent wants blog write on top, and only a deliberately supervised run wants the scope that spends. The point of separating the paid scope is that this is a decision you get to make once, at issue time, instead of one you have to police on every call.
No. Fetching a fresh search result draws on a paid allowance, so it belongs behind its own scope even though it changes nothing on your own systems. Reading a snapshot that was already bought and stored is different and does belong in the read scope — the money is already spent, and refusing that read only pushes an agent into buying a second copy of data you own.
Because write and paid are different properties, and folding them together makes both scopes mean less. A write scope covers actions that change something, such as submitting URLs to IndexNow, which costs nothing. Putting a paid action alongside them means anyone granting write for a free task also grants the ability to spend, which is exactly the surprise a scope system exists to prevent.
No. The workable pattern is to grant the read and mask the secret field: a read scope can list webhook endpoints while their signing secrets stay masked, and can return mail settings while the password is never included. That lets an agent verify that configuration is correct without ever holding a credential it could leak in a log or a transcript.
On their own, and never on the same key as the action they gate. Deciding an approval is not reading or writing content; it is releasing something already prepared. A key that can both request an action and approve it has turned the approval into a formality, so the permission to decide is kept as a standalone scope that an agent's working key should not carry.
The read scopes for the areas it reports on, and nothing else. That is enough to fetch analytics, search performance, tracked keywords, indexing state, crawlability and any search snapshots already stored. It cannot publish, cannot change configuration and cannot spend, which means an unattended run of it has no failure mode worse than a wrong answer in a report.
The server is the same in both. What differs is which layers the client implements: how it connects, how it discovers the authorisation server, which of the three capabilities it lists, and whether it reads the hints on each tool.

Both can hand a model a block of JSON, so the choice looks cosmetic. It is not. A tool call is a decision the model makes with arguments it invents; a resource read is a fetch the client performs against an address it already holds.