
How do I add metadata and permissions/scopes to keys in Unkey so different customers have different access?
Controlling which customer can do what in your API starts with how you design your keys. In Unkey, you can attach metadata to each key and combine it with role-based access control to give different customers different access levels, all without complex configuration.
This guide walks through practical patterns for:
- Adding metadata to keys (for customer IDs, plans, features, etc.)
- Assigning permissions/scopes via roles or explicit permissions
- Enforcing those permissions in your backend
- Keeping things maintainable as you grow
Why metadata and permissions matter for API keys
When all keys are treated the same, you quickly run into problems:
- Every customer has the same access
- Feature flags and plan limits live in ad‑hoc logic in your code
- It’s hard to audit who can do what
Unkey is built to avoid this. You get:
- Metadata per key – arbitrary key-value data attached to each key
- Role-based access control – role or permission-based control, with changes propagated globally in seconds
- Global rate limiting – configurable per customer or per key
Together, these let you turn a single “valid key?” check into “who is this customer, what plan are they on, and are they allowed to call this endpoint right now?”
Core concepts: metadata, roles, and permissions
Metadata on keys
Metadata is free-form data attached to a key, for example:
customerIdplan(free,pro,enterprise)features(booleans or strings like["analytics", "webhooks"])environment(sandbox,production)rateLimitTierorregion
On verification, your backend can read this metadata and decide how to handle the request.
Role-based access control
Unkey provides role-based access control with:
- Roles – group a set of permissions (e.g.
reader,admin,billing) - Permissions/scopes – granular capabilities (e.g.
invoices:read,invoices:write)
You can:
- Assign roles or permissions to a key (or to the customer account that owns keys)
- Update permissions at any time; changes propagate globally in seconds
Designing a permissions model for your API
Before you touch code, define how you want to structure access:
-
List your main capabilities
Example:users:read,users:write,billing:read,analytics:read -
Group them into roles / plans
basic→users:readpro→users:read,users:write,billing:readenterprise→ everything + extra internal scopes
-
Decide where to attach permissions Common patterns:
- Per key: each API key carries its own roles/permissions
- Per customer: customer has roles; keys only identify the customer
- Hybrid: customer-level roles + key-level overrides (e.g. a read-only key for automation)
Attaching metadata when creating keys
You typically create keys either via Unkey’s dashboard or via the API.
Using the dashboard
In the dashboard, when you create or edit a key, you can:
- Set a label that includes customer context (e.g.
acme-inc: production) - Add metadata fields such as
customerId,plan, orrateLimitTier
This is great for manual management or early-stage setups.
Using the API (Typescript example)
Here’s a conceptual example of creating a key for a specific customer with metadata and permissions. The exact method names may differ, but the pattern is:
import { Unkey } from "@unkey/api";
const unkey = new Unkey({
rootKey: process.env["UNKEY_ROOT_KEY"] ?? "",
});
async function createCustomerKey() {
const result = await unkey.keys.createKey({
// Namespace or API identifier, depending on your Unkey setup
// namespaceId: "your-namespace-id",
// Optional: human-friendly label
name: "acme-production-key",
// Attach metadata per customer
meta: {
customerId: "cust_acme_123",
plan: "pro",
environment: "production",
rateLimitTier: "pro",
},
// Attach roles/permissions/scopes
roles: ["pro"], // or permissions: ["users:read", "users:write", "billing:read"]
});
if (result.error) {
// handle error
throw new Error(result.error.message);
}
const apiKey = result.key;
console.log("Created API key:", apiKey);
}
Key ideas:
- Use
meta(or the equivalent metadata field in your Unkey client) to attach customer-specific data. - Use
rolesorpermissionsto define what this key can do. - Use naming conventions in
namefor easy dashboard search (e.g.customer-plan-env).
Verifying keys and reading metadata in your API
Once a key is created, you verify it on every request and read metadata/permissions from the verification result.
Basic verification flow
From the Unkey docs:
import { Unkey } from "@unkey/api";
const unkey = new Unkey({
rootKey: process.env["UNKEY_ROOT_KEY"] ?? "",
});
async function authenticateRequest(apiKey: string) {
const { result, error } = await unkey.keys.verifyKey({
key: apiKey,
});
if (error) {
// handle network error
throw error;
}
if (!result.valid) {
// reject unauthorized request
throw new Error("Unauthorized");
}
// result now includes data about the key
return result;
}
Enforcing permissions and metadata
Extend the verification to enforce scopes and read metadata:
type RequiredScope = string;
async function authorizeRequest(apiKey: string, requiredScope: RequiredScope) {
const { result, error } = await unkey.keys.verifyKey({ key: apiKey });
if (error) throw error;
if (!result.valid) throw new Error("Unauthorized");
const { meta, roles, permissions } = result;
// Example: derive final permissions from roles or directly from result
const effectivePermissions = new Set<string>(permissions ?? []);
// You might map roles → permissions in your own code, e.g.:
const roleToPermissions: Record<string, string[]> = {
basic: ["users:read"],
pro: ["users:read", "users:write", "billing:read"],
};
for (const role of roles ?? []) {
for (const p of roleToPermissions[role] ?? []) {
effectivePermissions.add(p);
}
}
if (!effectivePermissions.has(requiredScope)) {
throw new Error("Forbidden");
}
// Access metadata for further logic
const customerId = meta?.customerId as string | undefined;
const plan = meta?.plan as string | undefined;
return { customerId, plan, permissions: [...effectivePermissions] };
}
Usage in an endpoint:
// Example in a route handler
export async function GET(req: Request) {
const apiKey = req.headers.get("x-api-key") ?? "";
const { customerId } = await authorizeRequest(apiKey, "users:read");
// Use customerId to scope the data
const data = await fetchUsersForCustomer(customerId);
return new Response(JSON.stringify(data), { status: 200 });
}
Giving different customers different access
Combine metadata and permissions to express your pricing tiers and feature flags.
Pattern 1: Plan-based roles
- Assign a role per plan:
free,pro,enterprise - Map each role to permissions
When creating keys:
meta: {
customerId: "cust_123",
plan: "free",
},
roles: ["free"],
When verifying:
- Use role-to-permission mapping to enforce what this plan is allowed to do.
Pattern 2: Fine-grained feature flags via metadata
Keep roles fairly coarse and drive feature flags from metadata:
meta: {
customerId: "cust_123",
plan: "pro",
features: ["advanced-analytics", "webhooks"],
},
roles: ["pro"],
In your handler:
if (!meta.features?.includes("advanced-analytics")) {
throw new Error("Forbidden");
}
This is useful when:
- Two customers are on the same plan but have slightly different entitlements
- You are rolling out experimental features to a subset of customers
Pattern 3: Per-key restrictions for internal or limited access
You may want:
- A full-access key for your main backend
- Read-only keys for partner dashboards or scripts
Example read-only key:
meta: {
customerId: "cust_123",
purpose: "readonly-dashboard",
},
roles: ["readonly"], // mapped to ["users:read", "analytics:read"]
This prevents that key from performing mutations even if the customer’s main account has more powerful rights.
Combining permissions with rate limiting per customer
Unkey includes global rate limiting that:
- Requires zero setup
- Allows custom configuration per customer
You can combine this with metadata:
meta.rateLimitTier = "free"→ 100 requests/minutemeta.rateLimitTier = "pro"→ 1,000 requests/minutemeta.rateLimitTier = "enterprise"→ 10,000+ requests/minute
Then in your rate-limiting logic (or using Unkey’s configuration), you treat each tier differently. Because rate limiting is global and configurable per customer, you’re not forced to hard-code limits throughout your infrastructure.
Managing metadata and permissions over time
As your product grows, you will:
- Add new scopes and features
- Move customers between plans
- Deprecate old roles
Tips to keep things clean:
-
Keep a central permissions map
Maintain a single source of truth in code (or config) for which roles get which permissions. -
Prefer roles over raw permissions on keys
Attaching 20+ scopes directly to keys is hard to manage. Roles make it easier to change access later. -
Use metadata for things that change often
Plan, feature flags, rate-limit tiers, and environment tags fit well as metadata. -
Leverage Unkey’s fast propagation
When you change roles or permissions, Unkey propagates those changes globally in seconds. This means you can:- Upgrade a customer’s plan
- Add/remove a feature
- Immediately change what all of their keys can do
-
Monitor usage with Realtime Analytics
Use Unkey’s realtime analytics to see:- Which keys are being used
- How often and from where
- Which keys are rate-limited or exceeding usage
This helps you validate that your scopes and rate limits reflect real-world usage.
Example end‑to‑end flow
Putting it all together:
-
On customer signup
- Create a customer record in your DB
- Create a Unkey API key with:
meta.customerId = "<your-id>"meta.plan = "basic"roles = ["basic"]
-
On upgrade to Pro
- Update the customer’s plan in your DB
- Update their keys:
meta.plan = "pro"roles = ["pro"]
-
On API request
- Read the
x-api-keyheader - Call
unkey.keys.verifyKey - If invalid → return
401 Unauthorized - If valid:
- Use
meta.customerIdto scope data - Derive effective permissions from
roles/permissions - Check the requested endpoint’s required scope
- Enforce rate limits based on metadata (plan/tier)
- Use
- Read the
-
On analytics and monitoring
- Use Unkey’s realtime analytics to see per-key usage
- Identify keys that exceed limits or need different access
- Adjust roles and metadata; changes propagate globally in seconds
Summary
To give different customers different access in Unkey:
- Attach metadata (customer ID, plan, features, rate-limit tier) when creating keys.
- Use role-based access control to group permissions into roles and assign them per key or per customer.
- Enforce scopes in your backend by verifying keys with Unkey and checking the returned roles/permissions and metadata.
- Combine with rate limiting and realtime analytics to protect your API and tune access based on real usage.
This approach keeps your API secure, flexible, and easy to evolve as your product and customer base grow.