What a scoped API key is actually protecting
Because every netweft MCP tool is read-only over rows already stored — none of them writes, crawls or spends a credit — a scope cannot withhold a capability, only a subject. The useful one is billing: it is how a key handed to an outside integration is stopped from reading the workspace's balance and plan.
When we added scopes to API keys, the first draft had scopes that mapped to verbs: read, write, admin. That draft was deleted, because it described a product netweft does not have.
Every tool reads
The MCP surface exposes twenty-five tools across six scopes. Not one of them writes a row, starts a crawl, or spends a credit. They read what netweft has already stored: audits and their issues, saved keywords, rank trackers and their results, project context, the credit balance.
That is a deliberate constraint rather than an accident of scheduling. An agent holding a key should not be able to move the workspace’s balance, because the whole reason to hand an agent a key is that you are not watching what it does with it. A surface where the worst case is an expensive afternoon is a surface nobody sensible connects.
So write had nothing to gate, and admin had nothing to describe.
A scope withholds a subject
What is left is the question of what a key may read, which turns out to be
the question people actually have. The scopes are named after tool families
rather than HTTP route segments — rank_tracking, not rank-tracking —
because the tools are what a key holder sees.
The one that earns its place is billing. A key given to a reporting script,
a client’s own dashboard, or somebody else’s agent has no business reading what
the workspace pays and what it has left. Withholding billing and granting the
other five produces a key that can answer every question about the work and
none about the account.
Two details that took longer than the feature
A key with no scopes named gets all of them. The alternative — defaulting to none — produces a key that silently does nothing and a support conversation about why. Defaulting to everything is the behaviour people expect from a key they did not narrow.
An unrecognised scope is dropped, not honoured. A row written by a newer deploy can carry a scope this build has never heard of. Dropping it is the safe direction: the tool it would unlock does not exist here, so keeping the string could only widen a set that nothing can act on.
The rest of what a key is — shown once, stored as a SHA-256 hash, compared in constant time, revoked rather than deleted — is on the security page.