Capabilities & governance
How a handler is granted the right to reach beyond ordinary reads and writes.
Most of the Handler SDK is available to every handler. A handful of methods reach further than ordinary data access, and those require an explicit capability on that specific handler.
| Capability | Covers |
|---|---|
sdk:read · sdk:write · sdk:transaction | Ordinary data access. Granted to every handler. |
sdk:delete | Deleting rows |
sdk:query | Raw SQL |
sdk:query_ddl | Schema changes (implies sdk:query) |
sdk:shared_read | Reading across organizations |
sdk:external_email | Sending email |
sdk:external_stripe | Stripe calls |
sdk:import | CSV import |
The SDK reference badges every method that needs one.
Checked three times, on purpose
At save
A handler using an ungranted capability is rejected — you find out while writing it, not in production.
At publish
The granted set is frozen into that version's manifest. The version records what it was allowed to do at the moment it shipped.
At runtime
The frozen manifest is enforced on every execution.
Freezing at publish is what makes the grant meaningful. Without it, a capability change would silently alter the behaviour of already-shipped versions, and "what could this version do" would have no answer.
Who can grant
Granting a capability is a privileged act, and the acting principal must hold the capability
themselves — an author cannot grant their handler something they do not have. Attempting it
returns SDK_CAP_NOT_GRANTED with the specific missing capabilities listed.
Practical advice
Grant the narrowest set that works. If a handler needs sdk:query for one query, ask
whether that query could be sdk.readList instead — most can. A handler with no elevated
capability is one you never have to think about again.