Documentation Index Fetch the complete documentation index at: https://unkey.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
When verifying a key, you can check if it has specific permissions. If the key lacks the required permissions, verification fails with code: INSUFFICIENT_PERMISSIONS.
Basic permission check
Pass a permission string to verify:
curl -X POST https://api.unkey.com/v2/keys.verifyKey \
-H "Authorization: Bearer $UNKEY_ROOT_KEY " \
-H "Content-Type: application/json" \
-d '{
"key": "sk_...",
"permissions": "documents.read"
}'
Permission query syntax
Unkey supports logical operators for complex permission checks:
Single permission
{ " permissions " : "documents.read" }
Key must have documents.read.
AND (all required)
{ " permissions " : "documents.read AND documents.write" }
Key must have both permissions.
OR (any required)
{ " permissions " : "admin OR editor" }
Key must have at least one of the permissions.
Complex queries with parentheses
{ " permissions " : "admin OR (documents.read AND documents.write)" }
Key must have admin OR have both documents.read and documents.write.
Real-world example
// User wants to delete a document
// They need: admin OR (documents.delete AND owner of this document)
try {
const { data } = await unkey . keys . verifyKey ({
key : request . apiKey ,
permissions : "admin OR documents.delete" ,
});
if ( ! data . valid ) {
return Response . json ({ error : data . code }, { status : 401 });
}
// Key is valid - continue with your API logic
} catch ( err ) {
console . error ( err );
return Response . json ({ error : "Internal error" }, { status : 500 });
}
Response structure
Successful verification with permissions:
{
" meta " : { " requestId " : "req_..." },
" data " : {
" valid " : true ,
" code " : "VALID" ,
" keyId " : "key_..." ,
" permissions " : [ "documents.read" , "documents.write" , "users.view" ]
}
}
Failed permission check:
{
" meta " : { " requestId " : "req_..." },
" data " : {
" valid " : false ,
" code " : "INSUFFICIENT_PERMISSIONS" ,
" keyId " : "key_..."
}
}
Manual permission checking
Sometimes you need to check permissions after loading data from your database (e.g., checking if the user owns a resource). In these cases:
Verify the key
Verify without permission requirements to get the key’s permissions list. try {
const { data } = await unkey . keys . verifyKey ({
key : "sk_..." ,
});
if ( ! data . valid ) {
return unauthorized ();
}
const permissions = data . permissions ?? [];
// Continue with your API logic
} catch ( err ) {
console . error ( err );
return Response . json ({ error : "Internal error" }, { status : 500 });
}
Load your data
Query your database for the resource. const document = await db . documents . find ( documentId );
Check permissions manually
Use the permissions array and your data to make the decision. const canDelete =
permissions . includes ( "admin" ) ||
( permissions . includes ( "documents.delete" ) &&
document . ownerId === data . identity ?. externalId );
if ( ! canDelete ) {
return forbidden ();
}
Wildcard permissions
Permissions support wildcards for broader access:
// Key has: ["documents.*"]
// This grants: documents.read, documents.write, documents.delete, etc.
try {
const { data } = await unkey . keys . verifyKey ({
key : "sk_..." ,
permissions : "documents.read" , // ✅ Passes (matched by documents.*)
});
if ( ! data . valid ) {
return Response . json ({ error : data . code }, { status : 401 });
}
// Key is valid - continue with your API logic
} catch ( err ) {
console . error ( err );
return Response . json ({ error : "Internal error" }, { status : 500 });
}
Common wildcard patterns:
*, All permissions (use carefully!)
documents.*, All document permissions
api.v1.*, All v1 API permissions
Best practices
Use specific permissions, not broad ones
Instead of admin, define specific permissions like users.delete,
billing.manage. This gives you more control and better audit trails.
Check permissions at the API layer
Don’t just check in the UI, always verify permissions server-side during API
requests.
Use roles for common access patterns
Instead of attaching 10 permissions to every key, create a role and attach
that. Easier to manage and update.
Next steps
Set up roles & permissions Create your authorization structure
Full example See a complete implementation