API keys are still the most common way software talks to software. They are also one of the easiest credentials to over-provision: one key that can read everything, write everything, delete everything, in every environment.
This guide shows how to design API key permissions using least privilege so a single leaked key does not become a full account takeover.
What "least privilege" means for API keys
Least privilege means a key can do exactly what it needs to do, and nothing else:
- Only the minimum actions (read vs write vs admin)
- Only the minimum resources (one project, one workspace, one dataset)
- Only the minimum environments (dev vs prod)
- Only the minimum time (short lifetimes where possible)
- Only the minimum network context (IP allowlists, if feasible)
If you cannot implement all of those, start with the two that reduce blast radius fastest: action scopes and resource scopes.
Start with a permission model you can explain
If your permission system is hard to reason about, it will be misconfigured.
A simple, durable model is:
- Actions:
read,write,manage - Resources:
project:<id>,client:<id>,key:<id> - Environments:
dev,prod
That becomes a policy shape like:
- Allowed actions:
[read] - Allowed resources:
[project:123] - Allowed envs:
[prod]
When you can explain permissions in one sentence, people will actually use them.
API key scopes: pick one of these patterns
There are three common ways to represent scopes. The best choice depends on how your API is structured.
Pattern A: Endpoint-based scopes (fast to start)
Example:
keys:readkeys:writeprojects:read
Pros: easy to implement and document.
Cons: you can accidentally grant broad access to all resources unless you also add resource scoping.
Pattern B: Resource-based scopes (best blast-radius control)
Example:
project:abc123:readproject:abc123:write
Pros: extremely clear blast radius.
Cons: scope strings can grow and you need good tooling to generate them.
Pattern C: Policy documents (most flexible)
Example JSON policy:
- resources + actions + optional conditions
Pros: expressive.
Cons: complexity creeps in quickly; requires strong validation and UX.
Recommendation: start with endpoint-based scopes, then add resource filters (Pattern A + resource constraints). That gets you 80% of the benefit without building an IAM system.
Scopes vs roles vs RBAC
Teams often ask: should we use scopes or roles?
- Scopes work well for machine-to-machine access and automation.
- Roles (RBAC) work well for human users inside an app.
You can combine them:
- User roles control who can create or manage keys.
- Key scopes control what those keys can do once created.
That separation prevents a common anti-pattern: "keys inherit full admin because the user who created them is admin".
The practical minimum: five rules that prevent most incidents
1) Default to read-only
- When a user creates a key, make the default permission
read.
2) Separate dev and prod keys
- Never let a dev key work against prod.
- Store them separately and label them clearly.
3) Always scope to a resource
- At minimum: one project. Better: one project and one sub-resource.
4) Set expiry for non-human workflows
- CI keys, integration keys, vendor keys: set an expiry date.
- If expiry is not possible, enforce rotation and alerting.
5) Never share a single key across multiple systems
- One key per app, per environment, per integration.
How to design "manage" permissions safely
Most APIs only need three permission levels. The dangerous one is manage.
Define manage narrowly:
- Can rotate or revoke keys
- Can update scopes
- Can change allowed resources
Do not include:
- User management
- Billing
- Organization-level settings
If your app has a team/workspace concept, treat workspace administration as separate from key management.
Prevent privilege creep with "scope templates"
A pattern that works well in practice is scope templates, like:
- "Read-only analytics" =>
analytics:read+project:<id> - "Deploy pipeline" =>
deploy:write+project:<id>+env:prod - "Support tooling" =>
tickets:read+project:<id>
This avoids ad-hoc keys where people click "everything" to get unstuck.
Logging and audits: scopes only help if you can see them
At a minimum, log these fields for every request authenticated by an API key:
- key id (not the secret)
- team/workspace id
- project/resource id
- action scope (or endpoint)
- decision outcome (allowed/denied)
- timestamp
- IP / user agent (where applicable)
Then build two alerts:
- "Key used outside expected environment" (dev key hitting prod)
- "Key suddenly calling admin endpoints" (scope misuse or compromised system)
Common mistakes (and what to do instead)
- Mistake: one "master key" used everywhere
Fix: per-integration keys, scoped to one project and one environment.
- Mistake: write permissions granted because "it might need it later"
Fix: start read-only; add write when you have a concrete use case.
- Mistake: long-lived keys for CI/CD
Fix: short-lived credentials (OIDC where available) or strict expiry + automated rotation.
- Mistake: scopes without resource constraints
Fix: combine scopes with resource scoping so projects:read still cannot read every project.
Checklist: a least-privilege API key setup that scales
- Each key is tied to one integration or system.
- Keys are environment-specific (dev vs prod).
- Default permission is read-only.
- Keys have explicit resource scope (at least one project).
- Non-human keys expire or rotate on a schedule.
- Requests are logged with key id + scope + resource.
- Alerts exist for scope anomalies and environment misuse.
FAQ
Are API keys worse than OAuth?
Not necessarily. OAuth can be safer, but it is also easier to misconfigure. A well-scoped API key with strong rotation and monitoring can be a solid choice for server-to-server integrations.
What if an API does not support fine-grained scopes?
Add compensating controls:
- separate keys per environment
- IP allowlisting
- strict rate limits
- short expiries
- aggressive monitoring
Then push the provider to add scopes.
Should I store scopes in the key itself?
Avoid embedding authorization logic solely in the secret string. Store key metadata server-side (key id -> policy) so you can revoke or change scopes without reissuing secrets.
Closing: reduce blast radius first
You do not need a perfect permission system to be safer than 90% of teams. Start with read-only defaults, strict environment separation, and resource scoping. Those three changes dramatically reduce what an attacker can do with one leaked API key.
Try KeyVawlt Free
Secure your API keys with zero-knowledge encryption. No credit card required.