Designing API Authorization Scopes: What to Restrict and What to Leave Open

Authorization scopes let customers limit what an API key can do. The design space is wider than it looks: too few scopes leave customers exposed to credential theft, too many scopes overwhelm customers and produce inconsistent application. Most APIs that get scopes right start small and iterate

Authentication answers the question of who is calling the API. Authorization answers the question of what they are allowed to do. The two are often conflated but deserve separate design attention, and the second one is where most B2B SaaS APIs are weakest. A typical API ships with a single class of API key that can do everything the account can do, which is operationally simple but creates a blast radius problem: a leaked key gives the attacker full access to the account, regardless of what the integration actually needs to do.

What authorization scopes actually buy

Scopes let customers create API keys that can do less than the account itself can do. A typical pattern: a customer wants to integrate their internal billing system with our invoice generation API. The integration only needs to create invoices and read invoice status. It does not need to delete invoices, manage account settings, or access webhook configurations. A scoped API key restricted to "invoices:create" and "invoices:read" lets the customer deploy the integration with substantially reduced blast radius if the key leaks.

The security benefit compounds with key rotation discipline. Customers who use scoped keys can rotate the specific scope without rotating the entire account credential, which makes routine rotation less disruptive and therefore more likely to happen.

The operational benefit is integration audit. Per-scope usage logs let customers see which integrations are calling which endpoints, which makes capacity planning and abuse detection easier. The audit trail is much less useful if every key has full account permissions.

The third benefit is multi-team support within larger customer organizations. Different teams within a customer organization can have different scoped keys without each team needing a separate account, which simplifies the customer-side organizational mapping.

The design problem

Scope design is harder than it looks. The naive approach—one scope per API endpoint—produces an unusable list with hundreds of entries that customers cannot remember and inconsistently apply. The opposite naive approach—a handful of broad scopes like "read" and "write"—gives such weak isolation that scopes barely improve over no-scopes-at-all.

The right grain is somewhere in between, and depends on what customer integrations actually look like. A scope is useful if it maps to a coherent customer use case: "manage webhooks", "generate invoices", "monitor cron jobs", "read flag values". A scope is not useful if it maps to an API implementation detail: "call the /v1/invoices/list endpoint" or "use the POST verb".

The Stripe scope model has converged toward roughly the right grain after several iterations. Their restricted keys allow per-resource read and write permissions, with the resource boundaries matching the integration patterns customers actually have. The GitHub fine-grained token scopes follow a similar pattern with resource-and-permission pairs. Both companies have published explicit guidance about which scopes correspond to which integration use cases, which is the documentation that makes scopes actually usable.

The minimum viable scope set

For most B2B SaaS APIs, five to ten coarse scopes covers 95 percent of customer integration patterns. The pattern is usually one or two scopes per resource type, with the resource boundaries matching the API's URL structure. For a webhook API, the scopes might be "webhooks:read" and "webhooks:write", covering all the operations on webhook endpoints, subscriptions, and deliveries. For an invoice API, the scopes might be "invoices:create", "invoices:read", and "invoices:delete", with the delete scope separated because it is the destructive operation.

The decision of how finely to split is mostly about the destructive-versus-non-destructive boundary. Scoped keys are most valuable when they prevent destructive operations the integration does not need. A key that can read invoices but not delete them is meaningfully safer than a key with full account access, in a way that distinguishing "create" from "read" usually is not.

The complement is the cross-cutting administrative scope. Account-level operations—managing API keys, changing billing details, updating organization settings—should require an account-admin scope that is never granted to integrations. The discipline of keeping the admin scope separate from the data-plane scopes is one of the strongest single decisions in API authorization design.

The implementation

Scopes live on the API key record. The minimum schema is a key table with a scopes column (TEXT array or JSONB) and authentication middleware that reads the scopes alongside the account ID after the key validates.

Per-endpoint scope requirements are declared as part of the route registration. A route handler can have a decorator (Python), a middleware (Express), or a registration metadata field (FastAPI Depends) that specifies which scope is required. Requests that come in with a key missing the required scope return 403 Forbidden with an error code identifying the missing scope and a link to the documentation.

The error response format matters for customer-side debugging. The error should clearly identify the missing scope by name, distinguish missing-scope from invalid-key, and link to the scope documentation. The most common customer-side mistake is creating a key with the wrong scopes, and the error response is the first thing the customer sees when their integration fails.

The default scope decision

An API key created through the dashboard gets some default set of scopes. The decision is whether the default is "all scopes" or "no scopes" or "the common read scopes" or something else.

The right default depends on the customer base. For developer-tooling APIs where customers are likely to read documentation and configure carefully, "no scopes" forces customers to think about what permissions they need and produces tighter keys. For business-tooling APIs where customers may not read the scope documentation, "all scopes" reduces friction but loses the security benefit of scoping.

Stripe and most major developer APIs default to all scopes for the unrestricted key and require explicit configuration for restricted keys. The pattern produces the bimodal distribution of either fully-scoped keys (for sophisticated customers) or unscoped keys (for customers who never engaged with the feature), with little middle ground.

A possible middle approach is to default to "all scopes" but display a one-time setup wizard that suggests scope restriction based on common integration patterns. The wizard converts a docs-reading task into an in-product UI task, which more customers actually complete.

What scopes do not replace

Scopes do not replace per-resource ownership checks. A key with the "invoices:read" scope still needs to be authorized to read each specific invoice, not just any invoice in the system. The per-resource check is account-scoped (the key can only read invoices owned by its account) and possibly further scoped by per-resource policies (some keys can only read invoices for specific customers).

Scopes do not replace rate limiting. A key that is allowed to perform an operation can still abuse the rate at which it performs the operation, and rate limits are the separate defense.

Scopes do not replace audit logging. A key acting within its scopes is still doing things that an account administrator may want to review later. The audit log records the action and the key that performed it, with the scope information available for filtering.

Scopes do not replace network-level controls. A key that has the right scopes can still be used from an unexpected IP address or in unexpected time windows, and detection of those anomalies is separate machinery (IP allowlisting, behavioral anomaly detection, suspicious-activity alerts).

Three patterns that fail

First, scope inflation. Each new endpoint gets its own scope, and the list grows to hundreds of entries that customers cannot remember. The right discipline is to add new scopes only when the new endpoint represents a new integration use case, not a new implementation detail. Most new endpoints fit existing scopes.

Second, the inconsistent application. Some endpoints check scopes, others do not, because the scope checking is per-endpoint code rather than centralized middleware. Customers find the inconsistency frustrating and security-sensitive customers stop using scopes because they cannot trust the boundary.

Third, the silent grant. A new endpoint is added without a scope requirement, and existing keys with limited scopes can suddenly access it. The right discipline is to require explicit scope declaration for every endpoint, with a missing-declaration being a deployment-blocking error rather than a default-allow.

Our use

Across DocuMint, CronPing, FlagBit, and WebhookVault, we ship single-scope API keys with full account access. The scoped-key feature is planned for the v2 API across all four products, with the scope set converged to roughly the same shape: a read scope, a write scope, a delete scope per resource type, plus an account-admin scope kept separate.

The decision to ship single-scope first was deliberate: at our scale, the marginal customer who would use scopes is rare, and the engineering investment is better spent on features that more customers need. The decision to add scopes in v2 is also deliberate: as we get more customers with larger integration surfaces, the blast-radius problem grows, and scopes become valuable enough to justify the work. The transition will be carefully designed to preserve the simplicity of the no-scope keys for customers who do not need the feature.

The deeper observation about authorization scopes is that they are one of the features whose value depends entirely on adoption discipline. A scope system that customers do not use is worse than no scope system at all, because it gives a false sense of security without actually reducing blast radius. The work of making scopes genuinely useful is partly product design (the scopes have to map to integration patterns customers actually have) and partly documentation (the scope guidance has to be clear enough that customers do the right thing without research). Most APIs that have scopes get the engineering right and the adoption wrong, and end up with a feature that exists in the product but is not used in practice.


Our products: DocuMint (PDF invoice generation API), CronPing (cron job monitoring with status pages), FlagBit (feature flags API for modern teams), and WebhookVault (webhook capture and replay) put these patterns into production.

Read more