diff --git a/apps/docs/api-reference/v1/migration/keys.mdx b/apps/docs/api-reference/v1/migration/keys.mdx index dd5c8377b8..589edecaf7 100644 --- a/apps/docs/api-reference/v1/migration/keys.mdx +++ b/apps/docs/api-reference/v1/migration/keys.mdx @@ -160,12 +160,25 @@ curl -X POST https://api.unkey.com/v2/keys.createKey \ ## POST /v1/keys.verifyKey → POST /v2/keys.verifyKey **Key Changes:** +- **🚨 CRITICAL**: v2 requires root key authentication with `api.*.verify_key` permission +- **🚨 CRITICAL**: `apiId` parameter is no longer accepted in v2 - Remove `authorization` wrapper for permissions - Use string-based permission queries instead of object syntax - Change `remaining` → `credits` for cost parameters - Add support for multiple named rate limits - Response format: Direct response → `{meta, data}` envelope -- **Important**: v1 doesn't require root key authentication, v2 does + + +**Major Authentication Change in v2** + +The biggest change in v2 is that key verification now requires authentication with a root key that has the `api.*.verify_key` permission. This enables fine-grained access control: + +- **Wildcard permission**: `api.*.verify_key` allows verifying keys from any API in your workspace +- **Specific API permission**: `api.api_123.verify_key` allows verifying only keys from API `api_123` +- **No apiId parameter**: Unlike v1, you cannot specify which API's keys to verify - this is controlled by the root key's permissions + +This change improves security by ensuring only authorized services can verify keys, and provides workspace owners control over which services can verify keys from which APIs. + **Simple Key Verification** @@ -185,7 +198,7 @@ curl -X POST https://api.unkey.dev/v1/keys.verifyKey \ ```bash title="v2 cURL" icon="terminal" highlight={2} curl -X POST https://api.unkey.com/v2/keys.verifyKey \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"key": "sk_1234abcdef"}' ``` @@ -291,7 +304,7 @@ curl -X POST https://api.unkey.dev/v1/keys.verifyKey \ ```bash title="v2 cURL" icon="terminal" highlight={2,6} curl -X POST https://api.unkey.com/v2/keys.verifyKey \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "key": "sk_1234abcdef", @@ -341,7 +354,7 @@ curl -X POST https://api.unkey.dev/v1/keys.verifyKey \ ```bash title="v2 cURL" icon="terminal" highlight={2,6-10} curl -X POST https://api.unkey.com/v2/keys.verifyKey \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "key": "sk_1234abcdef", @@ -357,6 +370,94 @@ curl -X POST https://api.unkey.com/v2/keys.verifyKey \ --- +## Understanding v2 Root Key Permissions for Key Verification + +The v2 `keys.verifyKey` endpoint introduces a powerful permission system that gives you granular control over which services can verify keys from which APIs. + +### Setting Up Root Key Permissions + +When creating a root key for key verification, you need to grant it the appropriate `api.*.verify_key` permission: + + + +```json title="Root Key with Permission to Verify Any API Key" icon="key" +{ + "name": "Service Authentication Key", + "permissions": [ + { + "name": "api.*.verify_key", + "description": "Allow verification of keys from any API in the workspace" + } + ] +} +``` + +This root key can verify keys from any API in your workspace. Use this for services that need to authenticate users across multiple APIs. + + +```json title="Root Key with Permission for Specific API" icon="shield" +{ + "name": "Production API Verification Key", + "permissions": [ + { + "name": "api.api_1234567890abcdef.verify_key", + "description": "Allow verification of keys only from the Production API" + } + ] +} +``` + +This root key can only verify keys from the specific API `api_1234567890abcdef`. Use this for services that should only authenticate users from a particular API. + + +```json title="Root Key with Permission for Multiple APIs" icon="layers" +{ + "name": "Multi-Service Verification Key", + "permissions": [ + { + "name": "api.api_prod123.verify_key", + "description": "Verify keys from Production API" + }, + { + "name": "api.api_staging456.verify_key", + "description": "Verify keys from Staging API" + } + ] +} +``` + +This root key can verify keys from multiple specific APIs. Use this when you need to authenticate users from several APIs but not all APIs in the workspace. + + + +### Migration from v1 apiId Parameter + +In v1, you could specify which API's keys to verify using the `apiId` parameter: + +```json title="v1: Explicit API Selection" +{ + "key": "sk_1234abcdef", + "apiId": "api_1234567890abcdef" // ❌ No longer supported in v2 +} +``` + +In v2, this control is moved to the root key's permissions: + +```json title="v2: Permission-Based API Selection" +{ + "key": "sk_1234abcdef" + // API access controlled by root key's api.*.verify_key permissions +} +``` + +**Benefits of the New System:** +- **Better Security**: Only authorized services can verify keys +- **Granular Control**: Workspace owners control which services can verify keys from which APIs +- **Simpler Integration**: No need to manage `apiId` parameters in your application code +- **Audit Trail**: All key verifications are tied to specific root keys with known permissions + +--- + ## GET /v1/keys.getKey → POST /v2/keys.getKey **Key Changes:** @@ -1294,10 +1395,13 @@ console.log(keyData.keyId); - [ ] Add `refillDay` for monthly intervals ### Key Verification -- [ ] Add root key authentication header to key verification calls +- [ ] **CRITICAL**: Create root key with `api.*.verify_key` permission for your verification service +- [ ] Add root key authentication header to all key verification calls +- [ ] Remove `apiId` parameter from verification requests (controlled by root key permissions now) - [ ] Convert permission query objects to strings: `"perm1 AND perm2"` - [ ] Update `remaining` → `credits` for cost parameters - [ ] Handle new rate limits array structure in responses +- [ ] Test verification with both wildcard (`api.*.verify_key`) and specific API permissions ### Response Handling - [ ] Change `response` (direct) to `response.data` in all key operations diff --git a/apps/docs/errors/unkey/application/assertion_failed.mdx b/apps/docs/errors/unkey/application/assertion_failed.mdx index a7ec77ff4a..e2f6c7c004 100644 --- a/apps/docs/errors/unkey/application/assertion_failed.mdx +++ b/apps/docs/errors/unkey/application/assertion_failed.mdx @@ -1,8 +1,10 @@ --- -title: "err:unkey:application:assertion_failed" +title: "assertion_failed" description: "A runtime assertion or invariant check failed" --- +`err:unkey:application:invalid_input` + ```json Example { @@ -54,4 +56,4 @@ When contacting support, be sure to include: ## Related Errors - [err:unkey:application:unexpected_error](./unexpected_error) - A more general internal error -- [err:unkey:application:service_unavailable](./service_unavailable) - When a service is temporarily unavailable \ No newline at end of file +- [err:unkey:application:service_unavailable](./service_unavailable) - When a service is temporarily unavailable diff --git a/apps/docs/errors/unkey/application/invalid_input.mdx b/apps/docs/errors/unkey/application/invalid_input.mdx index e1eb6afa8d..73118019e8 100644 --- a/apps/docs/errors/unkey/application/invalid_input.mdx +++ b/apps/docs/errors/unkey/application/invalid_input.mdx @@ -1,8 +1,10 @@ --- -title: "err:unkey:application:invalid_input" +title: "invalid_input" description: "Client provided input that failed validation" --- +`err:unkey:application:invalid_input` + ```json Example { diff --git a/apps/docs/errors/unkey/application/protected_resource.mdx b/apps/docs/errors/unkey/application/protected_resource.mdx index f5fa2c5728..937e6118ed 100644 --- a/apps/docs/errors/unkey/application/protected_resource.mdx +++ b/apps/docs/errors/unkey/application/protected_resource.mdx @@ -1,8 +1,10 @@ --- -title: "err:unkey:application:protected_resource" +title: "protected_resource" description: "Attempt to modify a protected resource" --- +`err:unkey:application:protected_resource` + ```json Example { diff --git a/apps/docs/errors/unkey/application/service_unavailable.mdx b/apps/docs/errors/unkey/application/service_unavailable.mdx index 40b9983fbd..7da28c4ec5 100644 --- a/apps/docs/errors/unkey/application/service_unavailable.mdx +++ b/apps/docs/errors/unkey/application/service_unavailable.mdx @@ -1,8 +1,10 @@ --- -title: "err:unkey:application:service_unavailable" +title: "service_unavailable" description: "A service is temporarily unavailable" --- +`err:unkey:application:service_unavailable` + ```json Example { diff --git a/apps/docs/errors/unkey/application/unexpected_error.mdx b/apps/docs/errors/unkey/application/unexpected_error.mdx index 4b0580ecf2..7a400436c1 100644 --- a/apps/docs/errors/unkey/application/unexpected_error.mdx +++ b/apps/docs/errors/unkey/application/unexpected_error.mdx @@ -1,8 +1,10 @@ --- -title: "err:unkey:application:unexpected_error" +title: "unexpected_error" description: "An unhandled or unexpected error occurred" --- +`err:unkey:application:unexpected_error` + ```json Example { diff --git a/apps/docs/errors/unkey/authentication/key_not_found.mdx b/apps/docs/errors/unkey/authentication/key_not_found.mdx index 3ac216988c..144d005533 100644 --- a/apps/docs/errors/unkey/authentication/key_not_found.mdx +++ b/apps/docs/errors/unkey/authentication/key_not_found.mdx @@ -1,8 +1,10 @@ --- -title: "err:unkey:authentication:key_not_found" +title: "key_not_found" description: "The authentication key was not found" --- +`err:unkey:authentication:key_not_found` + ```json Example { diff --git a/apps/docs/errors/unkey/authentication/malformed.mdx b/apps/docs/errors/unkey/authentication/malformed.mdx index 789d6dc0d7..f030ea637c 100644 --- a/apps/docs/errors/unkey/authentication/malformed.mdx +++ b/apps/docs/errors/unkey/authentication/malformed.mdx @@ -1,8 +1,10 @@ --- -title: "err:unkey:authentication:malformed" +title: "malformed" description: "Authentication credentials were incorrectly formatted" --- +`err:unkey:authentication:malformed` + ```json Example { diff --git a/apps/docs/errors/unkey/authentication/missing.mdx b/apps/docs/errors/unkey/authentication/missing.mdx index a0e6fdebac..4913ce12b7 100644 --- a/apps/docs/errors/unkey/authentication/missing.mdx +++ b/apps/docs/errors/unkey/authentication/missing.mdx @@ -1,8 +1,10 @@ --- -title: "err:unkey:authentication:missing" +title: "missing" description: "Authentication credentials were not provided in the request" --- +`err:unkey:authentication:missing` + ```json Example { diff --git a/apps/docs/errors/unkey/authorization/forbidden.mdx b/apps/docs/errors/unkey/authorization/forbidden.mdx index 9450f725eb..1f20f712bf 100644 --- a/apps/docs/errors/unkey/authorization/forbidden.mdx +++ b/apps/docs/errors/unkey/authorization/forbidden.mdx @@ -1,8 +1,10 @@ --- -title: "err:unkey:authorization:forbidden" +title: "forbidden" description: "The operation is not allowed" --- +`err:unkey:authorization:forbidden` + ```json Example { diff --git a/apps/docs/errors/unkey/authorization/insufficient_permissions.mdx b/apps/docs/errors/unkey/authorization/insufficient_permissions.mdx index ba52dc8544..45301622f8 100644 --- a/apps/docs/errors/unkey/authorization/insufficient_permissions.mdx +++ b/apps/docs/errors/unkey/authorization/insufficient_permissions.mdx @@ -1,8 +1,10 @@ --- -title: "err:unkey:authorization:insufficient_permissions" +title: "insufficient_permissions" description: "The authenticated entity lacks sufficient permissions for the requested operation" --- +`err:unkey:authorization:insufficient_permissions` + ```json Example { diff --git a/apps/docs/errors/unkey/authorization/key_disabled.mdx b/apps/docs/errors/unkey/authorization/key_disabled.mdx index a811ae756f..e059c8d114 100644 --- a/apps/docs/errors/unkey/authorization/key_disabled.mdx +++ b/apps/docs/errors/unkey/authorization/key_disabled.mdx @@ -1,8 +1,10 @@ --- -title: "err:unkey:authorization:key_disabled" +title: "key_disabled" description: "The authentication key is disabled" --- +`err:unkey:authorization:key_disabled` + ```json Example { diff --git a/apps/docs/errors/unkey/authorization/workspace_disabled.mdx b/apps/docs/errors/unkey/authorization/workspace_disabled.mdx index 6cb4b0d74c..8a759bb020 100644 --- a/apps/docs/errors/unkey/authorization/workspace_disabled.mdx +++ b/apps/docs/errors/unkey/authorization/workspace_disabled.mdx @@ -1,8 +1,10 @@ --- -title: "err:unkey:authorization:workspace_disabled" +title: "workspace_disabled" description: "The associated workspace is disabled" --- +`err:unkey:authorization:workspace_disabled` + ```json Example { diff --git a/apps/docs/errors/unkey/data/api_not_found.mdx b/apps/docs/errors/unkey/data/api_not_found.mdx index 4cc819f6e7..dec0e45d06 100644 --- a/apps/docs/errors/unkey/data/api_not_found.mdx +++ b/apps/docs/errors/unkey/data/api_not_found.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:api_not_found" +title: "api_not_found" description: "The requested API was not found" --- + +err:unkey:data:api_not_found + + ```json Example { diff --git a/apps/docs/errors/unkey/data/audit_log_not_found.mdx b/apps/docs/errors/unkey/data/audit_log_not_found.mdx index c411379b6e..8af3665dbb 100644 --- a/apps/docs/errors/unkey/data/audit_log_not_found.mdx +++ b/apps/docs/errors/unkey/data/audit_log_not_found.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:audit_log_not_found" +title: "audit_log_not_found" description: "The requested audit log was not found" --- + +err:unkey:data:audit_log_not_found + + ```json Example { diff --git a/apps/docs/errors/unkey/data/identity_already_exists.mdx b/apps/docs/errors/unkey/data/identity_already_exists.mdx index 1a3c83723f..b3efae1211 100644 --- a/apps/docs/errors/unkey/data/identity_already_exists.mdx +++ b/apps/docs/errors/unkey/data/identity_already_exists.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:identity_already_exists" +title: "identity_already_exists" description: "The requested identity already exists" --- + +err:unkey:data:identity_already_exists + + ```json Example { diff --git a/apps/docs/errors/unkey/data/identity_not_found.mdx b/apps/docs/errors/unkey/data/identity_not_found.mdx index ad22b07c7e..674a5dde9a 100644 --- a/apps/docs/errors/unkey/data/identity_not_found.mdx +++ b/apps/docs/errors/unkey/data/identity_not_found.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:identity_not_found" +title: "identity_not_found" description: "The requested identity was not found" --- + +err:unkey:data:identity_not_found + + ```json Example { diff --git a/apps/docs/errors/unkey/data/key_auth_not_found.mdx b/apps/docs/errors/unkey/data/key_auth_not_found.mdx index b919e0b893..ba05799817 100644 --- a/apps/docs/errors/unkey/data/key_auth_not_found.mdx +++ b/apps/docs/errors/unkey/data/key_auth_not_found.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:key_auth_not_found" +title: "key_auth_not_found" description: "The requested key authentication was not found" --- + +err:unkey:data:key_auth_not_found + + ```json Example { diff --git a/apps/docs/errors/unkey/data/key_not_found.mdx b/apps/docs/errors/unkey/data/key_not_found.mdx index 91a2676fad..ee68df322d 100644 --- a/apps/docs/errors/unkey/data/key_not_found.mdx +++ b/apps/docs/errors/unkey/data/key_not_found.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:key_not_found" +title: "key_not_found" description: "The requested key was not found" --- + +err:unkey:data:key_not_found + + ```json Example { diff --git a/apps/docs/errors/unkey/data/permission_already_exists.mdx b/apps/docs/errors/unkey/data/permission_already_exists.mdx index da47abe0c9..95bd2cc4dd 100644 --- a/apps/docs/errors/unkey/data/permission_already_exists.mdx +++ b/apps/docs/errors/unkey/data/permission_already_exists.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:permission:duplicate" +title: "permission_already_exists" description: "A permission with this slug already exists" --- + +err:unkey:data:permission_already_exists + + ```json Example { "meta": { diff --git a/apps/docs/errors/unkey/data/permission_not_found.mdx b/apps/docs/errors/unkey/data/permission_not_found.mdx index 1bf8c2b9dc..445ddd2821 100644 --- a/apps/docs/errors/unkey/data/permission_not_found.mdx +++ b/apps/docs/errors/unkey/data/permission_not_found.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:permission_not_found" +title: "permission_not_found" description: "The requested permission was not found" --- + +err:unkey:data:permission_not_found + + ```json Example { diff --git a/apps/docs/errors/unkey/data/ratelimit_namespace_not_found.mdx b/apps/docs/errors/unkey/data/ratelimit_namespace_not_found.mdx index 119771da08..c94f12618e 100644 --- a/apps/docs/errors/unkey/data/ratelimit_namespace_not_found.mdx +++ b/apps/docs/errors/unkey/data/ratelimit_namespace_not_found.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:ratelimit_namespace_not_found" +title: "ratelimit_namespace_not_found" description: "The requested rate limit namespace was not found" --- + +err:unkey:data:ratelimit_namespace_not_found + + ```json Example { diff --git a/apps/docs/errors/unkey/data/ratelimit_override_not_found.mdx b/apps/docs/errors/unkey/data/ratelimit_override_not_found.mdx index cfa4ac1638..b6ddebacd9 100644 --- a/apps/docs/errors/unkey/data/ratelimit_override_not_found.mdx +++ b/apps/docs/errors/unkey/data/ratelimit_override_not_found.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:ratelimit_override_not_found" +title: "ratelimit_override_not_found" description: "The requested rate limit override was not found" --- + +err:unkey:data:ratelimit_override_not_found + + ```json Example { diff --git a/apps/docs/errors/unkey/data/role_already_exists.mdx b/apps/docs/errors/unkey/data/role_already_exists.mdx index 4c3777e0de..55dec3e109 100644 --- a/apps/docs/errors/unkey/data/role_already_exists.mdx +++ b/apps/docs/errors/unkey/data/role_already_exists.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:role:duplicate" +title: "role_already_exists" description: "A role with this name already exists" --- + +err:unkey:data:role_already_exists + + ```json Example { "meta": { diff --git a/apps/docs/errors/unkey/data/role_not_found.mdx b/apps/docs/errors/unkey/data/role_not_found.mdx index 3363288191..0764864215 100644 --- a/apps/docs/errors/unkey/data/role_not_found.mdx +++ b/apps/docs/errors/unkey/data/role_not_found.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:role_not_found" +title: "role_not_found" description: "The requested role was not found" --- + +err:unkey:data:role_not_found + + ```json Example { diff --git a/apps/docs/errors/unkey/data/workspace_not_found.mdx b/apps/docs/errors/unkey/data/workspace_not_found.mdx index b0fc6f05cb..6d94c79944 100644 --- a/apps/docs/errors/unkey/data/workspace_not_found.mdx +++ b/apps/docs/errors/unkey/data/workspace_not_found.mdx @@ -1,8 +1,12 @@ --- -title: "err:unkey:data:workspace_not_found" +title: "workspace_not_found" description: "The requested workspace was not found" --- + +err:unkey:data:workspace_not_found + + ```json Example { diff --git a/apps/docs/errors/user/bad_request/permissions_query_syntax_error.mdx b/apps/docs/errors/user/bad_request/permissions_query_syntax_error.mdx index 108b629fa6..68fe898862 100644 --- a/apps/docs/errors/user/bad_request/permissions_query_syntax_error.mdx +++ b/apps/docs/errors/user/bad_request/permissions_query_syntax_error.mdx @@ -1,8 +1,10 @@ --- -title: "err:user:bad_request:permissions_query_syntax_error" +title: "permissions_query_syntax_error" description: "Invalid syntax or characters in verifyKey permissions query" --- +`err:user:bad_request:permissions_query_syntax_error` + ```json Example { "meta": {