# Using Cursor with Unkey Source: https://unkey.com/docs/ai-code-gen/cursor Leverage Cursor's AI capabilities to build applications with Unkey's APIs Cursor is an AI-powered code editor that can help you build applications faster. When combined with Unkey's APIs, you can quickly generate secure, scalable applications with API key management and rate limiting. ## Prerequisites ## Getting Started ### 1. Set Up Your Unkey Workspace First, create your Unkey workspace and get your API keys: Navigate to the [Unkey Dashboard](https://app.unkey.com/apis) and create a new API for your project. Go to [Settings > Root Keys](https://app.unkey.com/settings/root-keys) and create a new root key with the necessary permissions. Copy your API ID from the dashboard - you'll need this for generating API keys. ### 2. Set Up Unkey MCP Server (Optional) Cursor supports the Model Context Protocol (MCP) which allows you to connect directly to Unkey's APIs. This gives Cursor access to your Unkey workspace for more intelligent suggestions. #### Install Unkey MCP Server 1. **Configure the MCP Server** Create or update your Cursor configuration file with the Unkey MCP server: ```json { "mcpServers": { "Unkey": { "command": "npx", "args": [ "mcp-remote", "https://mcp.unkey.com/mcp/v1", "--header", "MCP-UNKEY-BEARER-AUTH:${UNKEY_ROOT_KEY}" ] } } } ``` For ratelimiting specific operations, you can also add: ```json { "mcpServers": { "UnkeyRateLimiting": { "command": "npx", "args": [ "mcp-remote", "https://mcp.unkey.com/mcp/ratelimits/", "--header", "MCP-UNKEY-V2-ROOT-KEY:${UNKEY_ROOT_KEY}" ] } } } ``` 2. **Set Environment Variable** ```bash export UNKEY_ROOT_KEY="your_root_key_here" ``` 3. **Restart Cursor** Restart Cursor to load the MCP server configuration. ## Tips and Tricks for Cursor with Unkey ### 1. Keep Your Requests Small When working with Cursor, break down complex tasks into smaller, focused requests: **Good:** ``` Create a function to verify an API key with Unkey that returns a boolean ``` **Better:** ``` Create a TypeScript function that: - Takes an API key string as input - Uses @unkey/api to verify the key - Returns a boolean indicating if the key is valid - Includes proper error handling ``` ### 2. Update and Reference Your README.md Keep your project's README.md updated with Unkey-specific information. Cursor uses this context to provide better suggestions: ```markdown # My Project This project uses Unkey for API authentication and ratelimiting. ## Environment Variables - `UNKEY_ROOT_KEY`: Your Unkey root key - `UNKEY_API_ID`: Your API ID from the Unkey dashboard ## API Routes - `/api/protected` - Requires valid API key - `/api/keys` - Manage API keys (admin only) ## Rate Limiting - Free tier: 100 requests/hour - Pro tier: 1000 requests/hour ``` ## Add Unkey Documentation Context Adding Unkey docs can let you specifically refer to Unkey features when building your app. From Cursor Settings > Features > Docs add new doc, use the URL "[https://unkey.com/docs](https://unkey.com/docs)" # AI Code Gen with Unkey Source: https://unkey.com/docs/ai-code-gen/overview Use AI-powered code generation tools with Unkey's APIs and services Unkey provides powerful integrations with AI code generation tools to help you build applications faster and more efficiently. Whether you're using Cursor, GitHub Copilot, Windsurf,you can leverage AI to generate code that integrates seamlessly with Unkey's APIs. ## Available AI Tools Use Cursor's AI capabilities with Unkey's API documentation and examples Build Unkey applications with Windsurf's AI-powered development environment Connect your favorite AI code generation tool to Unkey's APIs using Model Context Protocol # Unkey MCP (Model Context Protocol) Source: https://unkey.com/docs/ai-code-gen/unkey-mcp Connect AI tools to Unkey's APIs using Model Context Protocol The Unkey Model Context Protocol (MCP) servers provide direct integration between AI tools and Unkey's APIs. This allows you to interact with your Unkey workspace directly, enabling AI-powered API key management, rate limiting configuration, and analytics queries. ## What is MCP? Model Context Protocol (MCP) is an open standard that allows AI applications to securely access external data and services. Unkey's MCP servers give Claude Desktop direct access to your Unkey APIs, enabling intelligent assistance with API key management and rate limiting. ## Available MCP Servers Unkey provides two MCP servers: Full access to Unkey's API management capabilities Specialized server for rate limiting operations ## Prerequisites * [Unkey account](https://app.unkey.com/auth/sign-up) created * Unkey root key with appropriate permissions * Node.js installed (for npx command) ## Installation Below is an example of using Unkey MCP with Claude but it can also be used with other AI applications. ### Unkey API MCP The main Unkey MCP server provides access to the complete Unkey API: 1. **Open Claude Desktop Configuration** Navigate to your Claude Desktop configuration file: * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` 2. **Add the MCP Server Configuration** Add the following configuration to your `claude_desktop_config.json`: ```json { "mcpServers": { "Unkey": { "command": "npx", "args": [ "mcp-remote", "https://mcp.unkey.com/mcp/v1", "--header", "MCP-UNKEY-BEARER-AUTH:${UNKEY_ROOT_KEY}" ] } } } ``` 3. **Set Environment Variable** Set your Unkey root key as an environment variable: ```bash # macOS/Linux export UNKEY_ROOT_KEY="unkey_xxx" # Windows set UNKEY_ROOT_KEY=unkey_xxx ``` 4. **Restart Claude Desktop** Close and restart Claude Desktop to load the MCP server. ### Unkey Ratelimiting MCP For specialized rate limiting operations, use the dedicated ratelimiting MCP server: 1. **Add Ratelimiting MCP Configuration** Add this configuration to your `claude_desktop_config.json`: ```json { "mcpServers": { "UnkeyRateLimiting": { "command": "npx", "args": [ "mcp-remote", "https://mcp.unkey.com/mcp/ratelimits/", "--header", "MCP-UNKEY-V2-ROOT-KEY:${UNKEY_ROOT_KEY}" ] } } } ``` 2. **Use Both Servers** You can configure both MCP servers simultaneously: ```json { "mcpServers": { "Unkey": { "command": "npx", "args": [ "mcp-remote", "https://mcp.unkey.com/mcp/v1", "--header", "MCP-UNKEY-BEARER-AUTH:${UNKEY_ROOT_KEY}" ] }, "UnkeyRateLimiting": { "command": "npx", "args": [ "mcp-remote", "https://mcp.unkey.com/mcp/ratelimits/", "--header", "MCP-UNKEY-V2-ROOT-KEY:${UNKEY_ROOT_KEY}" ] } } } ``` ## Getting Your Root Key To use the MCP servers, you need a Unkey root key with appropriate permissions: Go to [Settings > Root Keys](https://app.unkey.com/settings/root-keys) in your Unkey dashboard. Click "Create New Root Key" and select the permissions you need: * **API Key Management**: For creating, updating, and deleting API keys * **Rate Limiting**: For configuring rate limits * **Analytics**: For querying usage data Copy your root key and store it securely. You'll use this as your environment variable. ## Using MCP with Claude Desktop Once configured, you can interact with your Unkey workspace directly through Claude Desktop: ### API Key Management Ask Claude to help with API key operations: ``` Can you create a new API key for my mobile app with the following settings: - Name: "Mobile App Production" - Rate limit: 1000 requests per hour - Expiration: 90 days from now - Metadata: {"app": "mobile", "version": "1.0", "environment": "production"} ``` ### Ratelimiting Configuration Configure rate limits through natural language: ``` I need to set up rate limiting for my API with these tiers: - Free tier: 100 requests per hour - Pro tier: 1000 requests per hour - Enterprise tier: 10000 requests per hour Can you help me configure these limits? ``` ### Analytics and Monitoring Query your API usage data: ``` Show me the API usage statistics for the last 7 days, including: - Total requests - Top 5 API keys by usage - Error rates - Geographic distribution of requests ``` ### Troubleshooting Get help with common issues: ``` I'm seeing 401 errors for API key verification. Can you help me debug this? The API key is: uk_xxx The API ID is: api_xxx ``` ## Available Commands ### API Key Operations * **Create API Key**: Generate new API keys with custom settings * **List API Keys**: View all API keys in your workspace * **Update API Key**: Modify existing API key properties * **Delete API Key**: Remove API keys from your workspace * **Verify API Key**: Check if an API key is valid and active ### Rate Limiting Operations * **Configure Rate Limits**: Set up rate limiting rules * **Check Rate Limit Status**: Monitor current rate limit usage * **Update Rate Limits**: Modify existing rate limit configurations * **Delete Rate Limits**: Remove rate limiting rules ### Analytics and Monitoring * **Usage Analytics**: Query API usage statistics * **Error Analysis**: Investigate API errors and issues * **Performance Metrics**: Monitor API performance data * **Usage Reports**: Generate custom usage reports ## Example Conversations ### Creating an API Key **You**: "Create a new API key for my development environment with a rate limit of 500 requests per hour and set it to expire in 30 days." **Claude**: "I'll create a new API key for your development environment with the specified settings. Let me use the Unkey MCP to create this key with a 500 requests/hour rate limit and 30-day expiration." ### Analyzing API Usage **You**: "What are my top 3 API keys by usage this month?" **Claude**: "Let me query your Unkey analytics to find your top 3 API keys by usage this month. I'll pull the usage data and provide you with detailed statistics." ### Debugging Issues **You**: "I'm getting rate limit errors but I thought my limit was higher. Can you check my current rate limit configuration?" **Claude**: "I'll check your current rate limit configuration using the Unkey MCP. Let me examine your rate limiting settings and current usage to help diagnose the issue." ## Security Best Practices ### Environment Variables * Store your root key in environment variables, never in configuration files * Use different root keys for different environments (development, staging, production) * Regularly rotate your root keys ### Permissions * Grant only the minimum required permissions to your root keys * Use separate root keys for different operations when possible * Monitor root key usage through audit logs ### Access Control * Limit access to your Claude Desktop configuration * Use secure storage for your root keys * Implement proper backup and recovery procedures ## Troubleshooting ### Common Issues 1. **MCP Server Not Loading** * Check that Node.js is installed and accessible * Verify your configuration file syntax * Ensure environment variables are set correctly 2. **Authentication Errors** * Verify your root key is correct and has proper permissions * Check that the environment variable is set * Confirm your root key hasn't expired 3. **Connection Issues** * Ensure you have internet connectivity * Check if your firewall is blocking connections * Verify the MCP server URLs are correct ### Getting Help If you encounter issues: 1. Check the Claude Desktop logs for error messages 2. Verify your configuration matches the examples exactly 3. Test your root key directly with the Unkey API 4. Join the [Unkey Discord](https://go.unkey.com/discord) for community support ## Advanced Configuration ### Custom Environment Variables You can use custom environment variable names: ```json { "mcpServers": { "Unkey": { "command": "npx", "args": [ "mcp-remote", "https://mcp.unkey.com/mcp/v1", "--header", "MCP-UNKEY-BEARER-AUTH:${MY_CUSTOM_UNKEY_KEY}" ] } } } ``` ### Multiple Workspaces Configure multiple Unkey workspaces: ```json { "mcpServers": { "UnkeyProduction": { "command": "npx", "args": [ "mcp-remote", "https://mcp.unkey.com/mcp/v1", "--header", "MCP-UNKEY-BEARER-AUTH:${UNKEY_PROD_KEY}" ] }, "UnkeyStaging": { "command": "npx", "args": [ "mcp-remote", "https://mcp.unkey.com/mcp/v1", "--header", "MCP-UNKEY-BEARER-AUTH:${UNKEY_STAGING_KEY}" ] } } } ``` ## Next Steps * Explore [Cursor with Unkey](/ai-code-gen/cursor) for IDE-based AI assistance * Check out [Windsurf with Unkey](/ai-code-gen/windsurf) for collaborative development ## Resources * [Unkey API Reference](/api-reference/authentication) * [Unkey Dashboard](https://app.unkey.com) * [Community Discord](https://go.unkey.com/discord) # Using Windsurf with Unkey Source: https://unkey.com/docs/ai-code-gen/windsurf Build applications with Windsurf's AI-powered development environment and Unkey's APIs Windsurf is an AI-powered development environment that combines the power of AI assistance with a full-featured IDE. When integrated with Unkey's APIs, you can rapidly build secure, scalable applications with intelligent code generation and real-time collaboration features. ## Getting Started ### 1. Set Up Your Unkey Environment Before working with Windsurf, ensure you have your Unkey credentials ready: Navigate to the [Unkey Dashboard](https://app.unkey.com/apis) and create a new API for your project. Go to [Settings > Root Keys](https://app.unkey.com/settings/root-keys) and create a new root key with appropriate permissions. Note down your API ID and root key - you'll need these for your application. ### 2. Set Up Unkey MCP Server (Optional) Windsurf supports the Model Context Protocol (MCP) which allows you to connect directly to Unkey's APIs. This gives Windsurf access to your Unkey workspace for more intelligent suggestions. #### Install Unkey MCP Server To get started with Windsurf, open "Windsurf Settings > Cascade > Model Context Protocol (MCP) Servers", click on "Add Server", click "Add custom server", and add the following configuration for Unkey. 1. **Configure the MCP Server** ```json { "mcpServers": { "Unkey": { "command": "npx", "args": [ "mcp-remote", "https://mcp.unkey.com/mcp/v1", "--header", "MCP-UNKEY-BEARER-AUTH:${UNKEY_ROOT_KEY}" ] } } } ``` For ratelimiting specific operations, you can also add: ```json { "mcpServers": { "UnkeyRateLimiting": { "command": "npx", "args": [ "mcp-remote", "https://mcp.unkey.com/mcp/ratelimits/", "--header", "MCP-UNKEY-V2-ROOT-KEY:${UNKEY_ROOT_KEY}" ] } } } ``` 2. **Set Environment Variable** ```bash export UNKEY_ROOT_KEY="your_root_key_here" ``` 3. **Restart Windsurf** Restart Windsurf to load the MCP server configuration. # Overview Source: https://unkey.com/docs/analytics/overview Unkey tracks everything for you Analytics endpoints are currently only available in our v1 API. We're working on bringing these to v2 - stay tuned for updates! Consumption based billing for APIs is getting more and more popular, but it's tedious to build in house. For low frequency events, it's quite possible to emit usage events directly to Stripe or similar, but this becomes very noisy quickly. Furthermore if you want to build end-user facing or internal analytics, you need to be able to query the events from Stripe, which often does not provide the granularity required. Most teams end up without end-user facing analytics, or build their own system to store and query usage metrics. Since Unkey already stores and aggregates verification events by time, outcome and identity, we can offer this data via an API. ## Available data Unkey stores an event for every single verification, the relevent fields are described below: | Data | Type | Explanation | | -------------- | ------------- | -------------------------------------------------------------------------------------- | | `request_id` | String | Each request has a unique id, making it possible to retrieve later. | | `time` | Int64 | A unix milli timestamp. | | `key_space_id` | String | Each workspace may have multiple key spaces. Each API you create has its own keyspace. | | `key_id` | String | The individual key being verified. | | `outcome` | String | The outcome of the verification. `VALID`, `RATE_LIMITED` etc. | | `identity_id` | String | The identity connected to this key. | | `tags` | Array(String) | Arbitrary tags you may add during the verification to filter later. | We can return this data aggregated by `hour`, `day`, `month`, `tag`, `tags`, `identity`, `key` and `outcome`. As well as filter by `identity_id`, `key_space_id`, `key_id`, `tags`, `outcome`, `start` and `end` time. ## Example For an internal dashboard you want to find the top 5 users of a specific endpoint. In order to let Unkey know about the endpoint, you specify it as a tag when verifying keys: ```bash Tagging a verification {6} curl -XPOST 'https://api.unkey.dev/v1/keys.verifyKey' \ -H 'Content-Type: application/json' \ -d '{ "key": "", "apiId": "api_", "tags": [ "path=/my/endpoint" ], }' ``` You can now query `api.unkey.dev/v1/analytics.getVerifications` via query parameters. While we can't provide raw SQL access, we wanted to stay as close to SQL semantics as possible, so you didn't need to learn a new concept and to keep the translation layer simple. | Name | Value | Explanation | | --------- | --------------------------------- | ------------------------------------------------------------------------------ | | `start` | 1733749385000 | A unix milli timestamp to limit the query to a specific time frame. | | `end` | 1736431397000 | A unix milli timestamp to limit the query to a specific time frame. | | `apiId` | api\_262b3iR7gkmP7aUyZ24uihcijsCe | The API ID to filter keys. | | `groupBy` | identity | We're not interested in individual keys, but the user/org. | | `orderBy` | total | We want to see the most active users, by how many verifications they're doing. | | `order` | desc | We're ordering from most active to least active user. | | `limit` | 5 | Only return the top 5. | Below is a curl command putting everythign together: ```bash curl 'https://api.unkey.dev/v1/analytics.getVerifications?start=1733749385000&end=1736431397000&apiId=api_262b3iR7gkmP7aUyZ24uihcijsCe&groupBy=identity&orderBy=total&order=desc&limit=5' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' ``` You'll receive a json response with a breakdown of each outcome, per identity ordered by `total`. ```json First Row [ { "valid": 186, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 184, "unauthorized": 0, "disabled": 182, "insufficientPermissions": 0, "expired": 0, "total": 552, "apiId": "api_262b3iR7gkmP7aUyZ24uihcijsCe", "identity": { "id": "test_2ipPuAgat7xuVNGpK6AuPQ2Lbk11", "externalId": "user_2rNBR4YXxKwzM8bzVrCR5q6dFlc" } }, ... ] ``` ```json Full Response [ { "valid": 186, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 184, "unauthorized": 0, "disabled": 182, "insufficientPermissions": 0, "expired": 0, "total": 552, "apiId": "api_262b3iR7gkmP7aUyZ24uihcijsCe", "identity": { "id": "test_2ipPuAgat7xuVNGpK6AuPQ2Lbk11", "externalId": "user_2rNBR4YXxKwzM8bzVrCR5q6dFlc" } }, { "valid": 190, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 161, "unauthorized": 0, "disabled": 200, "insufficientPermissions": 0, "expired": 0, "total": 551, "apiId": "api_262b3iR7gkmP7aUyZ24uihcijsCe", "identity": { "id": "test_2ipPuAiGJ3L3TUNKA6gp5eLeuyj7", "externalId": "user_2rLz6cM63ZQ2v3IU0mryKbHetjK" } }, { "valid": 197, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 154, "unauthorized": 0, "disabled": 200, "insufficientPermissions": 0, "expired": 0, "total": 551, "apiId": "api_262b3iR7gkmP7aUyZ24uihcijsCe", "identity": { "id": "test_2ipPuAwJVE4Hdet3dyEpYreP8ob7", "externalId": "user_2rLwFchrbyIDb4LUfFp4CpTG0L3" } }, { "valid": 191, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 184, "unauthorized": 0, "disabled": 171, "insufficientPermissions": 0, "expired": 0, "total": 546, "apiId": "api_262b3iR7gkmP7aUyZ24uihcijsCe", "identity": { "id": "test_2ipPuB23PVchmbkt9mMjjcpvLM8N", "externalId": "user_2rLwCGvQKtnfnemH8HTL4cxWBFo" } }, { "valid": 207, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 171, "unauthorized": 0, "disabled": 162, "insufficientPermissions": 0, "expired": 0, "total": 540, "apiId": "api_262b3iR7gkmP7aUyZ24uihcijsCe", "identity": { "id": "test_2ipPuApEvEAXJo9UParPL6inHLLJ", "externalId": "user_2rLDPPVfeNB2hn1ARMh2808CdwG" } } ] ``` # Quickstarts Source: https://unkey.com/docs/analytics/quickstarts Power your own dashboard, reports or usage-based billing Analytics endpoints are currently only available in our v1 API. We're working on bringing these to v2 - stay tuned for updates! These scenarios should give you a good starting point to understand what is possible and what you need to do. They are in no particular order and don't build upon each other. We are using cURL here for demo purposes, but you can use any of our [SDKs](/libraries) for this as well. Almost all query parameters can be combined to build powerful queries. If you run into issues or something doesn't seem possible, please get in touch, so we can figure it out together: [support@unkey.dev](mailto:support@unkey.dev) Detailed explanations about each individual parameter can be found in the [api-reference](/api-reference/analytics/get_verifications). ## User's usage over the past 24h Assuming you have an identity with `externalId=user_123` and an API with `apiId=api_123`. ```bash curl 'https://api.unkey.dev/v1/analytics.getVerifications?start=1736673687000&end=1736760087000&externalId=user_123&groupBy=hour&apiId=api_123' \ -H 'Authorization: Bearer unkey_XXX' ``` This will return 24 elements, one per hour over the last 24h. Each element tells you about the outcomes of verifications in that interval. ```json [ { "time": 1736672400000, "valid": 15125, "notFound": 0, "forbidden": 0, "usageExceeded": 1225, "rateLimited": 0, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 16350 }, { "time": 1736676000000, "valid": 765, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 0, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 765 }, ... 21 elements omited { "time": 1736755200000, "valid": 20016, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 0, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 51, "total": 20067 } ] ``` ## Daily usage of a user per key in the last month Assuming you have an identity with `externalId=user_123` and an API with `apiId=api_123`. ```bash curl 'https://api.unkey.dev/v1/analytics.getVerifications?start=1734168087000&end=1736760087000&externalId=user_123&groupBy=key&groupBy=day&apiId=api_123' \ -H 'Authorization: Bearer unkey_XXX' ``` This returns 1 element per active key per day and includes the keyId. ```json [ // ... { "time": 1736726400000, "valid": 13, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 10, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 23, "keyId": "key_2zeYsLbpULnEUsvYeFGMeJzACp4j" }, { "time": 1736726400000, "valid": 5, "notFound": 0, "forbidden": 0, "usageExceeded": 6, "rateLimited": 0, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 11, "keyId": "key_2zeViCGkJpu5zQ8G12jcBoXWy4KH" } ] ``` ## Total usage per month for an identity Assuming you have an identity with `externalId=user_123` and an API with `apiId=api_123`. You should set your `start` to the beginning of the month and `end` to now or end of the month. ```bash curl 'https://api.unkey.dev/v1/analytics.getVerifications?start=1734168087000&end=1736760087000&externalId=user_123&groupBy=month&apiId=api_123' \ -H 'Authorization: Bearer unkey_XXX' ``` This returns one element per month. ```json [ { "time": 1733011200000, "valid": 1356136098, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 925255, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 1357061353 } ] ``` ## Showing usage in the current billing period If you want to show a guage or similar to your user about their consumption in the current billing period. ```bash curl 'https://api.unkey.dev/v1/analytics.getVerifications?start=1734168087000&end=1736760087000&externalId=user_123&groupBy=day&apiId=api_123' \ -H 'Authorization: Bearer unkey_XXX' ``` This will return one element per day, which you can either display in a chart, or sum up to have a total value. ```json [ // ... { "time": 1736553600000, "valid": 98267, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 6816, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 105083 }, { "time": 1736640000000, "valid": 20125, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 2525, "unauthorized": 0, "disabled": 6261, "insufficientPermissions": 0, "expired": 0, "total": 28911 } ] ``` ## Internal dashboard showing top 10 users by API usage over the past 30 days ```bash curl 'https://api.unkey.dev/v1/analytics.getVerifications?start=1734168087000&end=1736760087000&orderBy=total&order=desc&limit=10&groupBy=identity&apiId=api_123' \ -H 'Authorization: Bearer unkey_XXX' ``` Returns 10 elements, ordered from most total verifications to least. Each element includes the `identityId` as well as the `externalId` for your reference. ```json [ { "identity": { "id": "id_123", "externalId": "user_123"}, "valid": 54, "notFound": 0, "forbidden": 3, "usageExceeded": 6, "rateLimited": 10, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 73 }, { "identity": { "id": "id_456", "externalId": "user_6dg"}, "valid": 24, "notFound": 0, "forbidden": 1, "usageExceeded": 32, "rateLimited": 10, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 67 }, ... ] ``` ## Filter by tags Find out how many verifications were done, where the tag `myTag` was specified. You can combine this with other parameters to group by days for example. You can provide multiple tags by providing them as separate query paramters: `?tag=myTag&tag=myOthertag`. Filtering multiple tags is a logical `OR`. The result includes all verifications where at least one of the filtered tags was specified. ```bash curl 'https://api.unkey.dev/v1/analytics.getVerifications?start=1734168087000&end=1736760087000&tag=myTag&apiId=api_123' \ -H 'Authorization: Bearer unkey_XXX' ``` This returns 1 element, a sum of all verifications in the selected time, where the tag `myTag` was specified. ```json [ { "valid": 5, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 0, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 5 } ] ``` ## Filter by key This only includes verifications of a specific key. You can provide multiple keyIds to filter verifications of any one of those keys. ```bash curl 'https://api.unkey.dev/v1/analytics.getVerifications?start=1734168087000&end=1736760087000&keyId=key_123&apiId=api_123' \ -H 'Authorization: Bearer unkey_XXX' ``` ```json [ { "valid": 14, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 10, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 24 } ] ``` ## Grouping by tags To understand usage across your tags, you can group by tags, breaking down all verifications and summing them up per tag combination. Note this is plural: `&groupBy=tags`. ```bash curl 'https://api.unkey.dev/v1/analytics.getVerifications?start=1734168087000&end=1736760087000&groupBy=tags&apiId=api_123' \ -H 'Authorization: Bearer unkey_XXX' ``` You'll receive an array of elements. Each element corresponds to one tag combination. ```json [ { "valid": 50, "notFound": 0, "forbidden": 3, "usageExceeded": 6, "rateLimited": 10, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 69, "tags": [] // these did not have tags specified }, { "valid": 1, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 0, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 1, "tags": [ "a", "b" ] }, { "valid": 2, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 0, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 2, "tags": [ "a", "c" ] }, { "valid": 2, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 0, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 2, "tags": [ "a" ] } ] ``` ## Breakdown by individual tag If you want to see usage for an individual tag, regardless of combination with other tags, you can group by tag. Note this is singular `&groupBy=tag`. ```bash curl 'https://api.unkey.dev/v1/analytics.getVerifications?start=1734168087000&end=1736760087000&groupBy=tag&apiId=api_123' \ -H 'Authorization: Bearer unkey_XXX' ``` You'll receive one element per unique tag. ```json [ { "valid": 1, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 0, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 1, "tag": "b" }, { "valid": 2, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 0, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 2, "tag": "c" }, { "valid": 5, "notFound": 0, "forbidden": 0, "usageExceeded": 0, "rateLimited": 0, "unauthorized": 0, "disabled": 0, "insufficientPermissions": 0, "expired": 0, "total": 5, "tag": "a" } ] ``` # Get verification analytics Source: https://unkey.com/docs/api-reference/v1/analytics/get-verification-analytics https://api.unkey.dev/openapi.json get /v1/analytics.getVerifications **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Create API namespace Source: https://unkey.com/docs/api-reference/v1/apis/create-api-namespace https://api.unkey.dev/openapi.json post /v1/apis.createApi **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Delete API keys Source: https://unkey.com/docs/api-reference/v1/apis/delete-api-keys https://api.unkey.dev/openapi.json post /v1/apis.deleteKeys **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Delete API namespace Source: https://unkey.com/docs/api-reference/v1/apis/delete-api-namespace https://api.unkey.dev/openapi.json post /v1/apis.deleteApi **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Get API namespace Source: https://unkey.com/docs/api-reference/v1/apis/get-api-namespace https://api.unkey.dev/openapi.json get /v1/apis.getApi **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # List API keys Source: https://unkey.com/docs/api-reference/v1/apis/list-api-keys https://api.unkey.dev/openapi.json get /v1/apis.listKeys **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Authentication Source: https://unkey.com/docs/api-reference/v1/authentication Securely authenticating with the Unkey API Almost all Unkey API endpoints require authentication using a root key. Root keys provide access to your Unkey resources based on their assigned permissions. ## Bearer Authentication Authentication is performed using HTTP Bearer authentication in the `Authorization` header: ```bash Authorization: Bearer unkey_1234567890 ``` Example request: ```bash curl -X POST "https://api.unkey.dev/v1/keys.createKey" \ -H "Authorization: Bearer unkey_1234567890" \ -H "Content-Type: application/json" \ -d '{ "apiId": "api_1234" }' ``` ## Security Best Practices Never expose your root key in client-side code or include it in public repositories. For frontend applications, always use a backend server to proxy requests to the Unkey API. ## Root Key Management Root keys can be created and managed through the Unkey dashboard. We recommend: 1. **Using Different Keys for Different Environments**: Maintain separate root keys for development, staging, and production 2. **Rotating Keys Regularly**: Create new keys periodically and phase out old ones 3. **Setting Clear Key Names**: Name your keys according to their use case for better manageability ## Key Permissions System Unkey implements a sophisticated RBAC (Role-Based Access Control) system for root keys. Permissions are defined as tuples of: * **ResourceType**: The category of resource (api, ratelimit, rbac, identity) * **ResourceID**: The specific resource instance * **Action**: The operation to perform on that resource ### Available Resource Types | Resource Type | Description | | ------------- | ------------------------------------------------- | | `api` | API-related resources, such as endpoints and keys | | `ratelimit` | Rate limiting resources and configuration | | `rbac` | Permissions and roles management | | `identity` | User and identity management | ### Permission Examples Specific permission to manage a single API: ``` api.api_1234.read_api api.api_1234.update_api ``` Wildcard permission to manage all rate limit namespaces: ``` ratelimit.*.create_namespace ratelimit.*.read_namespace ``` When creating root keys, you can specify exactly what actions they're allowed to perform. ## Authentication Errors If your authentication fails, you'll receive a 401 Unauthorized or 403 Forbidden response with an error message: ```json { "meta": { "requestId": "req_abc123xyz789" }, "error": { "title": "Unauthorized", "detail": "The provided root key is invalid or has been revoked", "status": 401, "type": "https://unkey.com/docs/errors/unauthorized" } } ``` If your key is valid but lacks sufficient permissions, you'll receive a 403 Forbidden response: ```json { "meta": { "requestId": "req_abc123xyz789" }, "error": { "title": "Forbidden", "detail": "Your key does not have the required 'api.api_1234.update_api' permission", "status": 403, "type": "https://unkey.com/docs/errors/forbidden" } } ``` Common authentication issues include: * Missing the Authorization header * Invalid key format * Revoked or expired root key * Using a key with insufficient permissions # Create identity Source: https://unkey.com/docs/api-reference/v1/identities/create-identity https://api.unkey.dev/openapi.json post /v1/identities.createIdentity **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Delete identity Source: https://unkey.com/docs/api-reference/v1/identities/delete-identity https://api.unkey.dev/openapi.json post /v1/identities.deleteIdentity **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Get identity Source: https://unkey.com/docs/api-reference/v1/identities/get-identity https://api.unkey.dev/openapi.json get /v1/identities.getIdentity **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # List identities Source: https://unkey.com/docs/api-reference/v1/identities/list-identities https://api.unkey.dev/openapi.json get /v1/identities.listIdentities **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Update identity Source: https://unkey.com/docs/api-reference/v1/identities/update-identity https://api.unkey.dev/openapi.json post /v1/identities.updateIdentity **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Add key permissions Source: https://unkey.com/docs/api-reference/v1/keys/add-key-permissions https://api.unkey.dev/openapi.json post /v1/keys.addPermissions **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Add key roles Source: https://unkey.com/docs/api-reference/v1/keys/add-key-roles https://api.unkey.dev/openapi.json post /v1/keys.addRoles **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Create API key Source: https://unkey.com/docs/api-reference/v1/keys/create-api-key https://api.unkey.dev/openapi.json post /v1/keys.createKey **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Delete API key Source: https://unkey.com/docs/api-reference/v1/keys/delete-api-key https://api.unkey.dev/openapi.json post /v1/keys.deleteKey **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Get API key Source: https://unkey.com/docs/api-reference/v1/keys/get-api-key https://api.unkey.dev/openapi.json get /v1/keys.getKey **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Get key information Source: https://unkey.com/docs/api-reference/v1/keys/get-key-information https://api.unkey.dev/openapi.json post /v1/keys.whoami **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Get v1keysgetverifications Source: https://unkey.com/docs/api-reference/v1/keys/get-v1keysgetverifications https://api.unkey.dev/openapi.json get /v1/keys.getVerifications **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Remove key permissions Source: https://unkey.com/docs/api-reference/v1/keys/remove-key-permissions https://api.unkey.dev/openapi.json post /v1/keys.removePermissions **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Remove key roles Source: https://unkey.com/docs/api-reference/v1/keys/remove-key-roles https://api.unkey.dev/openapi.json post /v1/keys.removeRoles **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Set key permissions Source: https://unkey.com/docs/api-reference/v1/keys/set-key-permissions https://api.unkey.dev/openapi.json post /v1/keys.setPermissions **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Set key roles Source: https://unkey.com/docs/api-reference/v1/keys/set-key-roles https://api.unkey.dev/openapi.json post /v1/keys.setRoles **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Update key credits Source: https://unkey.com/docs/api-reference/v1/keys/update-key-credits https://api.unkey.dev/openapi.json post /v1/keys.updateRemaining **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Update key settings Source: https://unkey.com/docs/api-reference/v1/keys/update-key-settings https://api.unkey.dev/openapi.json post /v1/keys.updateKey **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Verify API key Source: https://unkey.com/docs/api-reference/v1/keys/verify-api-key https://api.unkey.dev/openapi.json post /v1/keys.verifyKey **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Health check Source: https://unkey.com/docs/api-reference/v1/liveness/health-check https://api.unkey.dev/openapi.json get /v1/liveness **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # /v1/apis.* Source: https://unkey.com/docs/api-reference/v1/migration/apis Migrate API namespace management endpoints from v1 to v2 This guide covers API namespace management endpoints for creating and managing API containers that organize your keys. ## Overview API endpoints manage the namespaces that contain your keys, providing CRUD operations for API management and key listing. ### Key Changes in v2: * **Response format**: Direct responses → `{meta, data}` envelope * **HTTP methods**: Some GET → POST changes for consistency * **Enhanced responses**: Request IDs for debugging and pagination metadata * **Consistent structure**: All responses follow same envelope pattern ### Migration Impact: * **Existing in v1**: Full API CRUD operations and key listing functionality * **Enhanced in v2**: Improved response format, better pagination, and enhanced filtering * **Maintained in v2**: All core API management functionality with consistent request patterns *** ## POST /v1/apis.createApi → POST /v2/apis.createApi **Key Changes:** * Response format: Direct response → `{meta, data}` envelope ```json title="Create API Request" icon="plus-circle" { "name": "Production API" } ``` ```json title="Create API Response Diff" icon="database" expandable // v1 Response (direct, no wrapper) { "apiId": "api_1234567890abcdef" // [!code --] } // v2 Response (with meta envelope) { "meta": { // [!code ++] "requestId": "req_createapi123" // [!code ++] }, // [!code ++] "data": { // [!code ++] "apiId": "api_1234567890abcdef" // [!code ++] } // [!code ++] } ``` ```bash title="API Endpoint & Domain Change" icon="arrow-right" # v1: api.unkey.dev domain curl -X POST https://api.unkey.dev/v1/apis.createApi # [!code --] -H "Authorization: Bearer " -H "Content-Type: application/json" -d '{"name": "Production API"}' # v2: api.unkey.com domain curl -X POST https://api.unkey.com/v2/apis.createApi # [!code ++] -H "Authorization: Bearer " -H "Content-Type: application/json" -d '{"name": "Production API"}' ``` *** ## GET /v1/apis.getApi → POST /v2/apis.getApi **Key Changes:** * HTTP method: GET → POST * Request body format required instead of query parameters * Response format: Direct response → `{meta, data}` envelope ```bash title="HTTP Method & Parameter Change" icon="arrow-right" # v1: GET with query parameters curl -X GET "https://api.unkey.dev/v1/apis.getApi?apiId=api_123" # [!code --] -H "Authorization: Bearer " # v2: POST with request body curl -X POST https://api.unkey.com/v2/apis.getApi # [!code ++] -H "Authorization: Bearer " -H "Content-Type: application/json" # [!code ++] -d '{"apiId": "api_123"}' # [!code ++] ``` ```json title="Get API Response Diff" icon="database" expandable // v1 Response (direct, no wrapper) { "id": "api_123", // [!code --] "workspaceId": "ws_xyz789", // [!code --] "name": "Production API" // [!code --] } // v2 Response (with meta envelope, no workspaceId) { "meta": { // [!code ++] "requestId": "req_getapi456" // [!code ++] }, // [!code ++] "data": { // [!code ++] "id": "api_123", // [!code ++] "name": "Production API" // [!code ++] } // [!code ++] } ``` ```bash title="Complete Examples" icon="terminal" # v1: GET with query parameters curl -X GET "https://api.unkey.dev/v1/apis.getApi?apiId=api_123" # [!code --] -H "Authorization: Bearer " # [!code --] # v2: POST with request body curl -X POST https://api.unkey.com/v2/apis.getApi # [!code ++] -H "Authorization: Bearer " -H "Content-Type: application/json" # [!code ++] -d '{"apiId": "api_123"}' # [!code ++] ``` *** ## GET /v1/apis.listKeys → POST /v2/apis.listKeys **Key Changes:** * HTTP method: GET → POST * Request body format required instead of query parameters * Enhanced filtering and pagination options * Response format: Direct response → `{meta, data}` envelope ```json title="List Keys Request Diff" icon="list" expandable // v1: Query parameters only // ?apiId=api_123&limit=100 // v2: Request body with enhanced options { "apiId": "api_123", "limit": 100, "cursor": "optional_cursor_for_pagination", // [!code ++] "externalId": "optional_filter_by_external_id" // [!code ++] } ``` ```json title="List Keys Response Diff" icon="database" expandable // v1 Response (direct structure with metadata) { "keys": [ // [!code --] { // [!code --] "id": "key_123", // [!code --] "name": "Production Key", // [!code --] "start": "prod_1234" // [!code --] } // [!code --] ], // [!code --] "cursor": "next_page_cursor", // [!code --] "total": 42 // [!code --] } // v2 Response (meta envelope with direct key array) { "meta": { // [!code ++] "requestId": "req_listkeys789" // [!code ++] }, // [!code ++] "data": [ // [!code ++] { // [!code ++] "keyId": "key_123", // [!code ++] "name": "Production Key", // [!code ++] "start": "prod_1234", // [!code ++] "externalId": "customer_789", // [!code ++] "enabled": true // [!code ++] } // [!code ++] ], // [!code ++] "pagination": { // [!code ++] "cursor": "next_page_cursor_here", // [!code ++] "hasMore": true // [!code ++] } // [!code ++] } ``` ```json title="Enhanced Filtering Options" icon="filter" // Basic listing { "apiId": "api_123", "limit": 50 } // Filter by external ID { "apiId": "api_123", "externalId": "customer_789", // [!code focus] "limit": 50 } // Pagination { "apiId": "api_123", "cursor": "cursor_from_previous_response", // [!code focus] "limit": 50 } ``` ```bash title="Method & Parameter Changes" icon="arrow-right" # v1: GET with query parameters curl -X GET "https://api.unkey.dev/v1/apis.listKeys?apiId=api_123&limit=100" # [!code --] -H "Authorization: Bearer " # [!code --] # v2: POST with enhanced request body curl -X POST https://api.unkey.com/v2/apis.listKeys # [!code ++] -H "Authorization: Bearer " -H "Content-Type: application/json" # [!code ++] -d '{"apiId": "api_123", "limit": 100, "cursor": "optional_cursor", "externalId": "optional_filter"}' # [!code ++] ``` *** ## POST /v1/apis.deleteApi → POST /v2/apis.deleteApi **Key Changes:** * Response format: Direct response → `{meta, data}` envelope ```json title="Delete API Request" icon="trash" { "apiId": "api_123" } ``` ```json title="Delete API Response Diff" icon="check-circle" // v1 Response (empty object) {} // [!code --] // v2 Response (meta envelope with empty data) { "meta": { // [!code ++] "requestId": "req_deleteapi999" // [!code ++] }, // [!code ++] "data": {} // [!code ++] } ``` ```bash title="Domain Change Only" icon="arrow-right" # v1: api.unkey.dev domain curl -X POST https://api.unkey.dev/v1/apis.deleteApi # [!code --] -H "Authorization: Bearer " -H "Content-Type: application/json" -d '{"apiId": "api_123"}' # v2: api.unkey.com domain curl -X POST https://api.unkey.com/v2/apis.deleteApi # [!code ++] -H "Authorization: Bearer " -H "Content-Type: application/json" -d '{"apiId": "api_123"}' ``` *** ## POST /v1/apis.deleteKeys → Removed in v2 **Purpose:** Delete all keys within an API namespace. **Migration Path:** Use individual `POST /v2/keys.deleteKey` calls for each key or delete the entire API with `POST /v2/apis.deleteApi`. ```bash title="v1: Delete all keys in API" icon="trash" curl -X POST https://api.unkey.dev/v1/apis.deleteKeys -H "Authorization: Bearer " -H "Content-Type: application/json" -d '{"apiId": "api_123"}' ``` ```bash title="Option 1: Delete Individual Keys" icon="key" # First, list keys to get their IDs curl -X POST https://api.unkey.com/v2/apis.listKeys -H "Authorization: Bearer " -H "Content-Type: application/json" -d '{"apiId": "api_123"}' # Then delete each key individually curl -X POST https://api.unkey.com/v2/keys.deleteKey -H "Authorization: Bearer " -H "Content-Type: application/json" -d '{"keyId": "key_123"}' ``` ```bash title="Option 2: Delete Entire API" icon="database" curl -X POST https://api.unkey.com/v2/apis.deleteApi -H "Authorization: Bearer " -H "Content-Type: application/json" -d '{"apiId": "api_123"}' ``` ```typescript title="Programmatic Migration Example" // v2: Migration helper function async function deleteAllKeysInApi(apiId: string) { // List all keys first const response = await fetch('/v2/apis.listKeys', { method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ apiId }) }); const { data } = await response.json(); // Delete each key individually for (const key of data) { await fetch('/v2/keys.deleteKey', { method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ keyId: key.keyId }) }); } } ``` # Errors Source: https://unkey.com/docs/api-reference/v1/migration/errors This guide covers changes to error response formats, error codes, and debugging capabilities when migrating from v1 to v2. ## Overview Error handling changes affect all API endpoints, providing improved debugging capabilities and standardized error responses. ### Key Changes in v2: * **Standardized format**: All errors use `{meta, error}` envelope with consistent structure * **Request IDs**: Every response includes `meta.requestId` for debugging * **Enhanced error structure**: Errors follow RFC 7807 Problem Details format * **Better debugging**: Improved error context and troubleshooting information For detailed information about specific error codes and troubleshooting, see the [Error Documentation](/errors/overview). *** ## Error Response Format Changes ### v1 Error Format → v2 Error Format ```json title="Error Response Migration" icon="triangle-exclamation" // v1 Error Response { "error": { // [!code --] "code": "NOT_FOUND", // [!code --] "message": "Key not found" // [!code --] } // [!code --] } // v2 Error Response { "meta": { // [!code ++] "requestId": "req_error123abc" // [!code ++] }, // [!code ++] "error": { // [!code ++] "title": "Not Found", // [!code ++] "detail": "The requested key was not found", // [!code ++] "status": 404, // [!code ++] "type": "https://unkey.com/docs/errors/unkey/data/key_not_found" // [!code ++] } // [!code ++] } ``` ```json title="Validation Error Migration" icon="exclamation-circle" expandable // v1 Validation Error { "error": { // [!code --] "code": "BAD_REQUEST", // [!code --] "message": "Invalid input" // [!code --] } // [!code --] } // v2 Validation Error { "meta": { // [!code ++] "requestId": "req_validation456" // [!code ++] }, // [!code ++] "error": { // [!code ++] "title": "Bad Request", // [!code ++] "detail": "The request contains invalid parameters", // [!code ++] "status": 400, // [!code ++] "type": "https://unkey.com/docs/errors/unkey/application/invalid_input", // [!code ++] "errors": [ // [!code ++] { // [!code ++] "location": "body.apiId", // [!code ++] "message": "Must be at least 3 characters long", // [!code ++] "fix": "Ensure the API ID is a valid identifier" // [!code ++] } // [!code ++] ] // [!code ++] } // [!code ++] } ``` *** ## Error Code Mapping Table The following table provides a comprehensive mapping of v1 error codes to their v2 equivalents: ### HTTP Status Errors | v1 Error Code | HTTP Status | v2 Error Type | v2 Category | Description | | ----------------------- | ----------- | --------------------------------------------------------------------- | -------------- | -------------------------------------------------- | | `BAD_REQUEST` | 400 | `https://unkey.com/docs/errors/unkey/application/invalid_input` | Application | Invalid request parameters or malformed input | | `UNAUTHORIZED` | 401 | `https://unkey.com/docs/errors/unkey/authentication/key_not_found` | Authentication | Missing or invalid authentication | | `FORBIDDEN` | 403 | `https://unkey.com/docs/errors/unkey/authorization/forbidden` | Authorization | Insufficient permissions for the requested action | | `NOT_FOUND` | 404 | `https://unkey.com/docs/errors/unkey/data/key_not_found` | Data | Requested resource does not exist | | `CONFLICT` | 409 | `https://unkey.com/docs/errors/unkey/data/conflict` | Data | Resource conflict (e.g., duplicate creation) | | `PRECONDITION_FAILED` | 412 | `https://unkey.com/docs/errors/unkey/application/precondition_failed` | Application | Required preconditions not met | | `TOO_MANY_REQUESTS` | 429 | `https://unkey.com/docs/errors/unkey/application/rate_limited` | Application | Rate limit exceeded | | `INTERNAL_SERVER_ERROR` | 500 | `https://unkey.com/docs/errors/unkey/application/internal_error` | Application | Unexpected server error | | `DELETE_PROTECTED` | 403 | `https://unkey.com/docs/errors/unkey/authorization/delete_protected` | Authorization | Resource cannot be deleted due to protection rules | ### Key Verification Specific Codes | v1 Verification Code | v2 Error Type | Description | Migration Notes | | -------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------ | | `VALID` | N/A | Key is valid and verification successful | No error - successful response | | `NOT_FOUND` | `https://unkey.com/docs/errors/unkey/data/key_not_found` | Key does not exist or has been deleted | Same as HTTP 404 NOT\_FOUND | | `FORBIDDEN` | `https://unkey.com/docs/errors/unkey/authorization/forbidden` | Key is not allowed to access this API | Same as HTTP 403 FORBIDDEN | | `USAGE_EXCEEDED` | `https://unkey.com/docs/errors/unkey/data/usage_exceeded` | Key has exceeded its usage limit | New specific error type in v2 | | `RATE_LIMITED` | `https://unkey.com/docs/errors/unkey/application/rate_limited` | Key has been rate limited | Same as HTTP 429 TOO\_MANY\_REQUESTS | | `UNAUTHORIZED` | `https://unkey.com/docs/errors/unkey/authentication/unauthorized` | Key authentication failed | Same as HTTP 401 UNAUTHORIZED | | `DISABLED` | `https://unkey.com/docs/errors/unkey/authorization/key_disabled` | Key has been disabled | New specific error type in v2 | | `INSUFFICIENT_PERMISSIONS` | `https://unkey.com/docs/errors/unkey/authorization/insufficient_permissions` | Key lacks required permissions | Enhanced RBAC error in v2 | | `EXPIRED` | `https://unkey.com/docs/errors/unkey/data/key_expired` | Key has expired | New specific error type in v2 | ### Migration Code Examples ```typescript title="v1 vs v2 Error Handling" // v1: Simple error code checking const response = await fetch('/v1/keys.verifyKey', { /* ... */ }); const data = await response.json(); if (data.error) { switch (data.error.code) { case 'NOT_FOUND': console.log('Key not found'); break; case 'RATE_LIMITED': console.log('Rate limited'); break; default: console.log('Unknown error:', data.error.message); } } // v2: RFC 7807 error handling const response = await fetch('/v2/keys.verifyKey', { /* ... */ }); const result = await response.json(); if (result.error) { const { title, detail, status, type } = result.error; const requestId = result.meta.requestId; // Log for debugging console.log(`Error ${status}: ${title} - ${detail} (Request: ${requestId})`); // Handle by category if (type.includes('/authentication/')) { console.log('Authentication error'); } else if (type.includes('/authorization/')) { console.log('Authorization error'); } else if (type.includes('/data/')) { console.log('Data error'); } } ``` ```typescript title="Error Category Helper" function getErrorCategory(v2ErrorType: string): string { if (v2ErrorType.includes('/authentication/')) return 'authentication'; if (v2ErrorType.includes('/authorization/')) return 'authorization'; if (v2ErrorType.includes('/application/')) return 'application'; if (v2ErrorType.includes('/data/')) return 'data'; return 'unknown'; } function isRetryableError(v2ErrorType: string): boolean { // Rate limits and internal errors are retryable return v2ErrorType.includes('rate_limited') || v2ErrorType.includes('internal_error'); } ``` *** ## Error Documentation For comprehensive information about specific error codes, causes, and resolution steps, refer to the error documentation: ### Common Error Categories * **[Application Errors](/errors/unkey/application/invalid_input)**: Invalid input, assertion failures, service unavailable * **[Authentication Errors](/errors/unkey/authentication/key_not_found)**: Missing, malformed, or invalid keys * **[Authorization Errors](/errors/unkey/authorization/forbidden)**: Insufficient permissions, disabled keys, workspace access * **[Data Errors](/errors/unkey/data/key_not_found)**: Resource not found, conflicts, data validation issues ### Error Troubleshooting * **Request IDs**: Always include the `meta.requestId` when contacting support * **Error Types**: Use the `type` URL for detailed documentation about specific errors * **Validation Errors**: Check the `errors` array for field-specific validation failures * **Status Codes**: HTTP status codes indicate the general category of the error #### Common Error Migration Issues **Problem:** Error handling code not working after migration **Symptoms:** * Errors not being caught properly * Missing error details that were available in v1 * Unable to determine error type or category **Solutions:** 1. **Update Error Access Pattern** ```typescript // ❌ v1 pattern if (response.error) { console.log('Error:', response.error.code); } // ✅ v2 pattern if (response.error) { console.log('Error:', response.error.type); console.log('Request ID:', response.meta.requestId); } ``` 2. **Handle New Error Structure** ```typescript // v2 error handling with all fields if (response.error) { const { title, detail, status, type } = response.error; // Log complete error information console.error(`${title} (${status}): ${detail}`); console.error(`Error Type: ${type}`); console.error(`Request ID: ${response.meta.requestId}`); // Handle validation errors if (response.error.errors) { response.error.errors.forEach(err => { console.error(`Field ${err.location}: ${err.message}`); }); } } ``` 3. **Error Categorization** ```typescript function categorizeError(errorType: string): string { if (errorType.includes('/authentication/')) return 'auth'; if (errorType.includes('/authorization/')) return 'permission'; if (errorType.includes('/application/')) return 'client'; if (errorType.includes('/data/')) return 'resource'; return 'unknown'; } ``` 4. **Retry Logic for Retryable Errors** ```typescript function isRetryable(errorType: string): boolean { return errorType.includes('rate_limited') || errorType.includes('internal_error'); } if (response.error && isRetryable(response.error.type)) { // Implement retry logic setTimeout(() => retryRequest(), 1000); } ``` ### Migration Considerations When migrating error handling code: * Update error parsing to access `response.error` instead of direct error access * Extract `meta.requestId` for logging and support requests * Handle the new RFC 7807 format with `title`, `detail`, `status`, and `type` fields * Process validation errors from the `errors` array for detailed field-level feedback # /v1/identities.* Source: https://unkey.com/docs/api-reference/v1/migration/identities Changes and improvements to identity endpoints from v1 to v2 This guide covers the changes to identity endpoints between v1 and v2. Both versions provide full identity management capabilities with different API patterns. ## Overview ### What Changed in v2: * **HTTP methods**: GET endpoints changed to POST for consistency * **Request format**: Query parameters moved to request body * **Response format**: Direct response wrapped in structured `{meta, data}` format * **Parameter flexibility**: Enhanced parameter handling (accepts both identityId and externalId) * **Error handling**: Improved error response structure ### Migration Impact: * **v1**: Mixed GET/POST with query parameters and direct responses * **v2**: All POST with request bodies and structured `{meta, data}` responses * **Benefit**: Consistent API patterns, better error tracking, enhanced flexibility *** ## POST /v1/identities.createIdentity → POST /v2/identities.createIdentity **Purpose:** Create a new identity with metadata and rate limits. **Changes:** Request format unchanged, response wrapped in structured format. ```json title="v1 vs v2: Create Identity Request" expandable // v1: POST /v1/identities.createIdentity { "externalId": "user_123", "meta": { "email": "user@example.com", "plan": "pro" }, "ratelimits": [ { "name": "requests", "limit": 1000, "duration": 3600000 } ] } // v2: POST /v2/identities.createIdentity (same request format) { "externalId": "user_123", "meta": { "email": "user@example.com", "plan": "pro" }, "ratelimits": [ { "name": "requests", "limit": 1000, "duration": 3600000 } ] } ``` ```json title="v1 vs v2: Create Identity Response" expandable // v1: Direct response { "identityId": "id_abc123def456" } // v2: Structured response with meta wrapper { "meta": { "requestId": "req_createidentity123" }, "data": { "identityId": "id_abc123def456" } } ``` ```bash title="Migration Examples" # v1: Create identity curl -X POST https://api.unkey.dev/v1/identities.createIdentity \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"externalId": "user_123", "meta": {"email": "user@example.com"}, "ratelimits": [{"name": "requests", "limit": 1000, "duration": 3600000}]}' # v2: Create identity (same request, structured response) curl -X POST https://api.unkey.com/v2/identities.createIdentity \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"externalId": "user_123", "meta": {"email": "user@example.com"}, "ratelimits": [{"name": "requests", "limit": 1000, "duration": 3600000}]}' ``` *** ## GET /v1/identities.getIdentity → POST /v2/identities.getIdentity **Purpose:** Retrieve identity data by ID or external ID. **Key Changes:** GET with query parameters → POST with request body, response format enhanced. ```bash title="v1 vs v2: Get Identity Request" # v1: GET with query parameters curl -X GET "https://api.unkey.dev/v1/identities.getIdentity?externalId=user_123" \ -H "Authorization: Bearer " # Alternative v1: Using identityId curl -X GET "https://api.unkey.dev/v1/identities.getIdentity?identityId=identity_123" \ -H "Authorization: Bearer " # v2: POST with request body (accepts both ID types) curl -X POST https://api.unkey.com/v2/identities.getIdentity \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"identity": "user_123"}' ``` ```json title="v1 vs v2: Get Identity Response" expandable // v1: Direct response { "id": "id_abc123def456", "externalId": "user_123", "meta": { "email": "user@example.com", "plan": "pro" }, "ratelimits": [ { "name": "requests", "limit": 1000, "duration": 3600000 } ] } // v2: Structured response with meta wrapper { "meta": { "requestId": "req_getidentity456" }, "data": { "id": "id_abc123def456", "externalId": "user_123", "meta": { "email": "user@example.com", "plan": "pro" }, "ratelimits": [ { "id": "rl_abcdef123456", "name": "requests", "limit": 1000, "duration": 3600000 } ] } } ``` *** ## GET /v1/identities.listIdentities → POST /v2/identities.listIdentities **Purpose:** Get paginated list of all identities. **Key Changes:** GET with query parameters → POST with request body, enhanced pagination structure. ```bash title="v1 vs v2: List Identities Request" # v1: GET with query parameters curl -X GET "https://api.unkey.dev/v1/identities.listIdentities?limit=50&cursor=eyJrZXkiOiJrZXlfMTIzNCJ9" \ -H "Authorization: Bearer " # v2: POST with request body curl -X POST https://api.unkey.com/v2/identities.listIdentities \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"limit": 50, "cursor": "eyJrZXkiOiJrZXlfMTIzNCJ9"}' ``` ```json title="v1 vs v2: List Identities Response" expandable // v1: Direct response with total count { "identities": [ { "id": "id_abc123", "externalId": "user_123", "ratelimits": [ { "name": "requests", "limit": 1000, "duration": 3600000 } ] } ], "cursor": "eyJrZXkiOiJrZXlfMTIzNCJ9", "total": 42 } // v2: Structured response with pagination object { "meta": { "requestId": "req_listidentities789" }, "data": [ { "id": "id_abc123", "externalId": "user_123", "meta": { "email": "user@example.com", "plan": "pro" }, "ratelimits": [ { "id": "rl_abcdef123456", "name": "requests", "limit": 1000, "duration": 3600000 } ] } ], "pagination": { "cursor": "eyJrZXkiOiJrZXlfMTIzNCJ9" } } ``` *** ## POST /v1/identities.updateIdentity → POST /v2/identities.updateIdentity **Purpose:** Update identity metadata and rate limits. **Changes:** Enhanced parameter flexibility, structured response format. ```json title="v1 vs v2: Update Identity Request" expandable // v1: Requires specific ID field { "identityId": "id_abc123", // or "externalId": "user_123" "meta": { "email": "user@example.com", "plan": "enterprise" }, "ratelimits": [ { "name": "requests", "limit": 5000, "duration": 3600000 } ] } // v2: Flexible identity parameter { "identity": "user_123", // accepts both identityId or externalId "meta": { "email": "user@example.com", "plan": "enterprise" }, "ratelimits": [ { "name": "requests", "limit": 5000, "duration": 3600000 } ] } ``` ```json title="v1 vs v2: Update Identity Response" expandable // v1: Direct response { "id": "id_abc123def456", "externalId": "user_123", "meta": { "email": "user@example.com", "plan": "enterprise" }, "ratelimits": [ { "name": "requests", "limit": 5000, "duration": 3600000 } ] } // v2: Structured response with meta wrapper { "meta": { "requestId": "req_updateidentity789" }, "data": { "id": "id_abc123def456", "externalId": "user_123", "meta": { "email": "user@example.com", "plan": "enterprise" }, "ratelimits": [ { "id": "rl_abcdef123456", "name": "requests", "limit": 5000, "duration": 3600000 } ] } } ``` ```bash title="Migration Examples" # v1: Update identity curl -X POST https://api.unkey.dev/v1/identities.updateIdentity \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"externalId": "user_123", "meta": {"plan": "enterprise"}, "ratelimits": [{"name": "requests", "limit": 5000, "duration": 3600000}]}' # v2: Update identity (enhanced flexibility) curl -X POST https://api.unkey.com/v2/identities.updateIdentity \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"identity": "user_123", "meta": {"plan": "enterprise"}, "ratelimits": [{"name": "requests", "limit": 5000, "duration": 3600000}]}' ``` *** ## POST /v1/identities.deleteIdentity → POST /v2/identities.deleteIdentity **Purpose:** Permanently delete an identity. **Changes:** Enhanced parameter flexibility (v2 accepts both ID types), structured response. ```json title="v1 vs v2: Delete Identity Request" expandable // v1: Requires identityId specifically { "identityId": "id_abc123def456" } // v2: Flexible identity parameter { "identity": "user_123" // accepts both identityId or externalId } ``` ```json title="v1 vs v2: Delete Identity Response" expandable // v1: Empty object response {} // v2: Structured response with meta wrapper { "meta": { "requestId": "req_deleteidentity999" } } ``` ```bash title="Migration Examples" # v1: Delete identity (requires identityId) curl -X POST https://api.unkey.dev/v1/identities.deleteIdentity \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"identityId": "id_abc123def456"}' # v2: Delete identity (accepts externalId or identityId) curl -X POST https://api.unkey.com/v2/identities.deleteIdentity \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"identity": "user_123"}' ``` # Overview Source: https://unkey.com/docs/api-reference/v1/migration/index Migrate from Unkey API v1 to v2 for enhanced features and improved infrastructure Unkey v2 represents a major infrastructure upgrade with enhanced caching and cache invalidation systems. While the core functionality remains the same, there are important changes to request and response structures that require updates to your integration. The v2 API is available at **api.unkey.com** (instead of api.unkey.dev). The v1 API is deprecated and will be shut down at the end of the year 2025. ## Quick Start The fastest way to migrate is to update your SDK: ```bash npm install @unkey/api@latest ``` ```bash pip install unkey.py@^2.0.4 ``` ```bash go get github.com/unkeyed/sdks/api/go/v2 ``` The v2 SDK automatically points to the new endpoint and guides you through all request/response changes via its types. ## Why Migrate to v2? ### Proven Performance Improvements Our v2 changes produce measurable latency improvements across all regions: ### Key Changes of v2 * **New Domain:** API available at `api.unkey.com` instead of `api.unkey.dev` * **Enhanced Caching:** Server-based infrastructure with improved caching mechanisms * **Improved Invalidations:** Enhanced cache invalidation system across regions * **Improved Developer Experience:** More consistent error handling and response formats ## What You Need to Change The main changes you'll need to make when migrating: * **Update your base URL** from `api.unkey.dev` to `api.unkey.com` (The SDKs do this automatically starting at v2.0.0) * **Change response parsing** from direct responses to `response.data` * **Handle new error format** with hierarchical error codes and request IDs ## Response Format Changes All v2 responses now use a standardized envelope format with `meta` and `data` fields: #### v1 Response Format ```json { "valid": true, "keyId": "key_123", "name": "Production API Key" } ``` #### v2 Response Format ```json { "meta": { "requestId": "req_abc123" }, "data": { "valid": true, "keyId": "key_123", "name": "Production API Key" } } ``` *** ## Detailed Migration by Category Choose the endpoint category you need to migrate: Key creation, verification, updates, permissions, and roles API namespace creation, retrieval, and key listing Identity management and shared rate limits Standalone permission and role management Rate limiting and override management Error response format changes and codes ### Getting Help * **Documentation:** [https://unkey.com/docs/api-reference/v2](https://unkey.com/docs/api-reference/v2) * **Discord:** [https://unkey.com/discord](https://unkey.com/discord) * **GitHub Issues:** [https://github.com/unkeyed/unkey/issues](https://github.com/unkeyed/unkey/issues) * **Email Support:** [support@unkey.dev](mailto:support@unkey.dev) * **Book a Call:** [https://cal.com/team/unkey/founders](https://cal.com/team/unkey/founders) - Schedule time with our team for migration help or feedback # /v1/keys.* Source: https://unkey.com/docs/api-reference/v1/migration/keys Migrate key management endpoints from v1 to v2 This guide covers all key management endpoints including creation, verification, updates, permissions, and roles. ## Overview Key management endpoints are the core of the Unkey API, handling creation, verification, updates, permissions, and roles for API keys. ### Key Changes in v2: * **Response format**: Direct responses → `{meta, data}` envelope * **Owner ID**: `ownerId` field removed, use `externalId` only * **Credits**: `remaining` + `refill` → `credits` object * **Rate limits**: `ratelimit` object → `ratelimits` array * **Permission queries**: Object syntax → string syntax ### Migration Impact: * **Existing in v1**: Full key CRUD operations with permissions, roles, and rate limiting * **Enhanced in v2**: Improved response format, simplified field structures, and string-based queries * **Maintained in v2**: All core key management functionality with backward-compatible request formats *** ## POST /v1/keys.createKey → POST /v2/keys.createKey **Key Changes:** * Remove `ownerId` field, use `externalId` instead * Restructure `remaining` + `refill` → `credits` object * Change `ratelimit` object → `ratelimits` array * Response format: Direct response → `{meta, data}` envelope ```json Key Creation Request Diff expandable icon=key { "apiId": "api_1234567890abcdef", "prefix": "prod", "name": "Production API Key", "ownerId": "user_456", // [!code --] "externalId": "customer_789", "permissions": ["documents.read", "documents.write"], "roles": ["editor"], "expires": 1735689600000, "remaining": 10000, // [!code --] "refill": { // [!code --] "interval": "monthly", // [!code --] "amount": 10000 // [!code --] }, // [!code --] "credits": { // [!code ++] "remaining": 10000, // [!code ++] "refill": { // [!code ++] "interval": "monthly", // [!code ++] "amount": 10000, // [!code ++] "refillDay": 1 // [!code ++] } // [!code ++] }, // [!code ++] "ratelimit": { // [!code --] "limit": 1000, // [!code --] "duration": 3600000, // [!code --] "async": true // [!code --] }, // [!code --] "ratelimits": [ // [!code ++] { // [!code ++] "name": "api_requests", // [!code ++] "limit": 1000, // [!code ++] "duration": 3600000, // [!code ++] "autoApply": true // [!code ++] } // [!code ++] ], // [!code ++] "enabled": true } ``` ```json Create Key Response Diff expandable icon=check-circle // v1 Response (direct response) { "key": "sk_1234abcdef567890", // [!code --] "keyId": "key_abc123def456" // [!code --] } // v2 Response { "meta": { // [!code ++] "requestId": "req_xyz789abc123" // [!code ++] }, // [!code ++] "data": { // [!code ++] "key": "sk_1234abcdef567890", // [!code ++] "keyId": "key_abc123def456" // [!code ++] } // [!code ++] } ``` ```bash v1 cURL expandable icon=terminal curl -X POST https://api.unkey.dev/v1/keys.createKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "apiId": "api_1234567890abcdef", "prefix": "prod", "name": "Production API Key", "ownerId": "user_456", "externalId": "customer_789", "permissions": ["documents.read", "documents.write"], "roles": ["editor"], "expires": 1735689600000, "remaining": 10000, "refill": { "interval": "monthly", "amount": 10000 }, "ratelimit": { "limit": 1000, "duration": 3600000, "async": true }, "enabled": true }' ``` ```bash v2 cURL expandable {4,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26} icon=terminal curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "apiId": "api_1234567890abcdef", "prefix": "prod", "name": "Production API Key", "externalId": "customer_789", "permissions": ["documents.read", "documents.write"], "roles": ["editor"], "expires": 1735689600000, "credits": { "remaining": 10000, "refill": { "interval": "monthly", "amount": 10000, "refillDay": 1 } }, "ratelimits": [ { "name": "api_requests", "limit": 1000, "duration": 3600000, "autoApply": true } ], "enabled": true }' ``` *** ## 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 **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** ```json Key Verification Request Changes icon=code { "key": "sk_1234abcdef" } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.verifyKey \ -H "Content-Type: application/json" \ -d '{"key": "sk_1234abcdef"}' ``` ```bash v2 cURL {2} icon=terminal curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"key": "sk_1234abcdef"}' ``` ```json Response Format Changes expandable icon=arrow-right // v1 Response (direct response) { "valid": true, // [!code --] "code": "VALID", // [!code --] "keyId": "key_123", // [!code --] "name": "Production API Key", // [!code --] "ownerId": "user_456", // [!code --] "meta": { // [!code --] "roles": ["admin", "user"], // [!code --] "stripeCustomerId": "cus_1234" // [!code --] }, // [!code --] "expires": null, // [!code --] "remaining": 995, // [!code --] "permissions": ["documents.read"], // [!code --] "roles": ["editor"], // [!code --] "enabled": true, // [!code --] "environment": "production", // [!code --] "identity": { // [!code --] "id": "identity_123", // [!code --] "externalId": "customer_789", // [!code --] "meta": {} // [!code --] }, // [!code --] "requestId": "req_abc123" // [!code --] } // v2 Response { "meta": { // [!code ++] "requestId": "req_abc123" // [!code ++] }, // [!code ++] "data": { // [!code ++] "valid": true, // [!code ++] "code": "VALID", // [!code ++] "keyId": "key_123", // [!code ++] "credits": 995, // [!code ++] "expires": null, // [!code ++] "permissions": ["documents.read"], // [!code ++] "roles": ["editor"], // [!code ++] "identity": { // [!code ++] "id": "id_123", // [!code ++] "externalId": "customer_789", // [!code ++] "meta": {}, // [!code ++] "ratelimits": [] // [!code ++] }, // [!code ++] "ratelimits": [ // [!code ++] { // [!code ++] "id": "rl_123", // [!code ++] "name": "api_requests", // [!code ++] "limit": 1000, // [!code ++] "remaining": 999, // [!code ++] "reset": 1672531200000, // [!code ++] "exceeded": false, // [!code ++] "duration": 3600000, // [!code ++] "autoApply": true // [!code ++] } // [!code ++] ] // [!code ++] } // [!code ++] } ``` **Permission Verification** ```json Permission Query Syntax icon=shield // v1 Request { "key": "sk_1234abcdef", "authorization": { // [!code --] "permissions": { // [!code --] "and": ["documents.read", "documents.write"] // [!code --] } // [!code --] } // [!code --] } // v2 Request { "key": "sk_1234abcdef", "permissions": "documents.read AND documents.write" // [!code ++] } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.verifyKey \ -H "Content-Type: application/json" \ -d '{ "key": "sk_1234abcdef", "authorization": { "permissions": { "and": ["documents.read", "documents.write"] } } }' ``` ```bash v2 cURL {2,6} icon=terminal curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "key": "sk_1234abcdef", "permissions": "documents.read AND documents.write" }' ``` **Credits and Rate Limits** ```json Credits & Rate Limits Structure icon=coins // v1 Request { "key": "sk_1234abcdef", "remaining": { // [!code --] "cost": 5 // [!code --] } // [!code --] } // v2 Request { "key": "sk_1234abcdef", "credits": { // [!code ++] "cost": 5 // [!code ++] }, // [!code ++] "ratelimits": [ // [!code ++] { // [!code ++] "name": "heavy_operations", // [!code ++] "cost": 3 // [!code ++] } // [!code ++] ] // [!code ++] } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.verifyKey \ -H "Content-Type: application/json" \ -d '{ "key": "sk_1234abcdef", "remaining": {"cost": 5} }' ``` ```bash v2 cURL {2,6,7,8,9,10} icon=terminal curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "key": "sk_1234abcdef", "credits": {"cost": 5}, "ratelimits": [{ "name": "heavy_operations", "cost": 3 }] }' ``` *** ## 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 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 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 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 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 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:** * HTTP method: GET → POST * Request body format required instead of query parameters * Response format: Direct response → `{meta, data}` envelope ```bash HTTP Method Change icon=arrow-right # v1: GET with query parameters curl -X GET "https://api.unkey.dev/v1/keys.getKey?keyId=key_123" \ -H "Authorization: Bearer " # v2: POST with request body curl -X POST https://api.unkey.com/v2/keys.getKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"keyId": "key_123"}' ``` ```json Get Key Response Diff expandable icon=database // v1 Response (direct response) { "id": "key_123", // [!code --] "start": "sk_5j1", // [!code --] "workspaceId": "ws_1234", // [!code --] "apiId": "api_abc", // [!code --] "name": "Production API Key", // [!code --] "ownerId": "user_456", // [!code --] "meta": { // [!code --] "roles": ["admin", "user"], // [!code --] "stripeCustomerId": "cus_1234" // [!code --] }, // [!code --] "createdAt": 1705306200000, // [!code --] "updatedAt": 1705306200000, // [!code --] "expires": null, // [!code --] "remaining": 995, // [!code --] "refill": { // [!code --] "interval": "monthly", // [!code --] "amount": 1000, // [!code --] "refillDay": 1, // [!code --] "lastRefillAt": 1705306200000 // [!code --] }, // [!code --] "ratelimit": { // [!code --] "async": true, // [!code --] "type": "fast", // [!code --] "limit": 100, // [!code --] "duration": 60000 // [!code --] }, // [!code --] "roles": ["admin", "finance"], // [!code --] "permissions": ["documents.read", "documents.write"], // [!code --] "enabled": true, // [!code --] "identity": { // [!code --] "id": "identity_123", // [!code --] "externalId": "customer_789", // [!code --] "meta": {} // [!code --] } // [!code --] } // v2 Response { "meta": { // [!code ++] "requestId": "req_xyz789" // [!code ++] }, // [!code ++] "data": { // [!code ++] "id": "key_123", // [!code ++] "name": "Production API Key", // [!code ++] "start": "prod_1234", // [!code ++] "meta": { // [!code ++] "plan": "enterprise" // [!code ++] }, // [!code ++] "createdAt": 1754304517, // [!code ++] "updatedAt": 1754304518, // [!code ++] "expires": 1764841705, // [!code ++] "credits": { // [!code ++] "remaining": 995, // [!code ++] "refill": { // [!code ++] "interval": "monthly", // [!code ++] "amount": 1000, // [!code ++] "refillDay": 1 // [!code ++] } // [!code ++] }, // [!code ++] "ratelimits": [ // [!code ++] { // [!code ++] "name": "api_requests", // [!code ++] "limit": 100, // [!code ++] "duration": 60000, // [!code ++] "autoApply": true // [!code ++] } // [!code ++] ], // [!code ++] "identity": { // [!code ++] "id": "id_123", // [!code ++] "externalId": "customer_789", // [!code ++] "meta": {}, // [!code ++] "ratelimits": [] // [!code ++] }, // [!code ++] "enabled": true, // [!code ++] "permissions": ["documents.read"], // [!code ++] "roles": ["editor"] // [!code ++] } // [!code ++] } ``` ```bash v1 cURL icon=terminal curl -X GET "https://api.unkey.dev/v1/keys.getKey?keyId=key_123" \ -H "Authorization: Bearer " ``` ```bash v2 cURL {1,3} icon=terminal curl -X POST https://api.unkey.com/v2/keys.getKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123" }' ``` *** ## POST /v1/keys.deleteKey → POST /v2/keys.deleteKey **Purpose:** Permanently delete an API key. **Key Changes:** * Response format: Direct response → `{meta, data}` envelope * Added `permanent` parameter for hard deletion * Added `meta.requestId` for debugging ```json Delete Key Request expandable icon=trash // v1 Request { "keyId": "key_123" } // v2 Request (enhanced) { "keyId": "key_123", "permanent": false // [!code ++] } ``` ```json Delete Key Response Diff icon=check-circle // v1 Response (direct empty response) {} // [!code --] // v2 Response { "meta": { // [!code ++] "requestId": "req_deletekey789" // [!code ++] }, // [!code ++] "data": {} // [!code ++] } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.deleteKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123" }' ``` ```bash v2 cURL {1,6} icon=terminal curl -X POST https://api.unkey.com/v2/keys.deleteKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "permanent": false }' ``` *** ## POST /v1/keys.updateKey → POST /v2/keys.updateKey **Purpose:** Update an existing API key's properties. **Key Changes:** * Same structural changes as `createKey` (credits, ratelimits, no ownerId) * Response format: Direct response → `{meta, data}` envelope * Support for partial updates ```json Update Key Request Diff expandable icon=edit // v1 Request { "keyId": "key_123", "name": "Updated Production Key", "ownerId": "user_456", // [!code --] "remaining": 5000, // [!code --] "ratelimit": { // [!code --] "limit": 2000, // [!code --] "duration": 3600000 // [!code --] } // [!code --] } // v2 Request { "keyId": "key_123", "name": "Updated Production Key", "externalId": "user_456", // [!code ++] "credits": { // [!code ++] "remaining": 5000 // [!code ++] }, // [!code ++] "ratelimits": [ // [!code ++] { // [!code ++] "name": "api_requests", // [!code ++] "limit": 2000, // [!code ++] "duration": 3600000 // [!code ++] } // [!code ++] ] // [!code ++] } ``` ```json Update Key Response Diff icon=check-circle // v1 Response (direct empty response) {} // [!code --] // v2 Response { "meta": { // [!code ++] "requestId": "req_updatekey456" // [!code ++] }, // [!code ++] "data": {} } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.updateKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "name": "Updated Production Key", "ownerId": "user_456", "remaining": 5000 }' ``` ```bash v2 cURL {1,6,7,8} icon=terminal curl -X POST https://api.unkey.com/v2/keys.updateKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "name": "Updated Production Key", "externalId": "user_456", "credits": { "remaining": 5000 } }' ``` *** ## POST /v1/keys.updateRemaining → POST /v2/keys.updateCredits **Purpose:** Update the credit/usage count for an API key. **Key Changes:** * Endpoint name change: `updateRemaining` → `updateCredits` * Request changes: `op` → `operation` * Response format: Direct response → `{meta, data}` envelope ```json Update Credits Request Diff expandable icon=coins // v1 Request { "keyId": "key_123", "op": "set", // [!code --] "value": 1000 } // v2 Request { "keyId": "key_123", "operation": "set", // [!code ++] "value": 1000 } ``` ```json Update Credits Response Diff expandable icon=check-circle // v1 Response (direct response) { "remaining": 1000 // [!code --] } // v2 Response { "meta": { // [!code ++] "requestId": "req_updatecredits123" // [!code ++] }, // [!code ++] "data": { // [!code ++] "credits": { // [!code ++] "remaining": 1000 // [!code ++] } // [!code ++] } // [!code ++] } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.updateRemaining \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "value": 1000 }' ``` ```bash v2 cURL {1,6} icon=terminal curl -X POST https://api.unkey.com/v2/keys.updateCredits \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "operation": "set", "value": 1000 }' ``` *** ## POST /v1/keys.whoami → POST /v2/keys.whoami **Purpose:** Get information about the current API key being used. **Key Changes:** * Response format: Direct response → `{meta, data}` envelope * Enhanced response with additional metadata, mirroring the `v2/keys.getKey` response. * Added `meta.requestId` for debugging ```json Whoami Request icon=user // v1 & v2 Request (unchanged) { "key": "your_api_key_here" } ``` ```json Whoami Response Diff expandable icon=database // v1 Response (direct response) { "id": "key_123", // [!code --] "name": "Production API Key", // [!code --] "remaining": 1000, // [!code --] "identity": { // [!code --] "id": "id_123", // [!code --] "externalId": "ext123" // [!code --] }, // [!code --] "meta": { // [!code --] "role": "admin", // [!code --] "plan": "premium" // [!code --] }, // [!code --] "createdAt": 1620000000000, // [!code --] "enabled": true, // [!code --] "environment": "production" // [!code --] } // v2 Response { "meta": { // [!code ++] "requestId": "req_whoami789" // [!code ++] }, // [!code ++] "data": { // [!code ++] "keyId": "key_123", // [!code ++] "name": "Production API Key", // [!code ++] "permissions": ["documents.read"], // [!code ++] "roles": ["editor"], // [!code ++] "identity": { // [!code ++] "id": "id_123", // [!code ++] "externalId": "customer_789", // [!code ++] "meta": {}, // [!code ++] "ratelimits": [] // [!code ++] }, // [!code ++] "createdAt": 1754304517 // [!code ++] } // [!code ++] } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.whoami \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "key": "some_api_key" }' ``` ```bash v2 cURL {1} icon=terminal curl -X POST https://api.unkey.com/v2/keys.whoami \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "key": "some_api_key" }' ``` *** ## New: /v2/keys.rerollKey **Purpose:** Generate a new API key while preserving the configuration from an existing key.\ With this endpoint, you no longer have to delete an old key and recreate it if a key is unrecoverable and you don't know the plaintext key anymore. **Important:** Analytics and usage metrics are tracked at both the key level AND identity level. \ If the original key has an identity, the new key will inherit it, allowing you to track usage across both individual keys and the overall identity.\ The new key will NOT inherit the usage from the old keyId. ```json { "keyId": "key_123", "expiration": 0 // This expires the key directly } ``` ```json expandable { "key": "sk_1234abcdef5678", "keyId": "key_456" } ``` ## Permission Management Endpoints ### POST /v1/keys.addPermissions → POST /v2/keys.addPermissions **Purpose:** Add permissions to an existing API key. **Key Changes:** * Response format: Direct response → `{meta, data}` envelope * Will auto create permissions if they don't exist, and the root key has the `rbac.*.create_permission` permission. Otherwise it will return a permission error. * Only identify permission by their slugs. * Added `meta.requestId` for debugging ```json Add Permissions Request expandable icon=shield-plus // v1 request { "keyId": "key_123", "permissions": [ { // [!code --] "id": "perm_123", // [!code --] "create": true // [!code --] }, // [!code --] { // [!code --] "name": "documents.read", // [!code --] } // [!code --] ] } // v2 request { "keyId": "key_123", "permissions": ["documents.read", "documents.write"] // [!code ++] } ``` ```json Add Permissions Response Diff icon=check-circle // v1 Response (array of attached permissions) [ { "id": "perm_123", "name": "documents.read" }, { "id": "perm_456", "name": "documents.write" } ] // v2 Response, responds with the permissions that are attached to this key { "meta": { // [!code ++] "requestId": "req_addperms123" // [!code ++] }, // [!code ++] "data": [{ "id": "perm_123", "name" : "Read Documents", "slug" : "documents.read", // [!code ++] "description": "Read documents but don't write them", // [!code ++] }] // [!code ++] } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.addPermissions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "permissions": [ { "name": "documents.read", "description": "Read access to documents" } ] }' ``` ```bash v2 cURL {1} icon=terminal curl -X POST https://api.unkey.com/v2/keys.addPermissions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "permissions": ["documents.read"] }' ``` *** ### POST /v1/keys.removePermissions → POST /v2/keys.removePermissions **Purpose:** Remove permissions from an existing API key. **Key Changes:** * Response format: Direct response → `{meta, data}` envelope * Only accepts permission slugs now * Added `meta.requestId` for debugging ```json Remove Permissions Request icon=shield-minus // v1 Request { "keyId": "key_123", "permissions": [ { // [!code --] "id": "perm_123" // [!code --] }, // [!code --] { // [!code --] "slug": "description.read" // [!code --] }, // [!code --] ] } // v2 Request { "keyId": "key_123", "permissions": ["documents.write", "documents.delete"] // [!code ++] } ``` ```json Remove Permissions Response Diff icon=check-circle // v1 Response (direct empty response) {} // [!code --] // v2 Response, responds with the remaining permissions { "meta": { // [!code ++] "requestId": "req_removeperms456" // [!code ++] }, // [!code ++] "data": [ // [!code ++] { // [!code ++] "id": "perm_123", // [!code ++] "slug": "documents.archive", // [!code ++] "name": "Documents Archive", // [!code ++] "description": "Allows archiving documents" // [!code ++] }, // [!code ++] ] // [!code ++] } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.removePermissions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "permissions": [{ "slug": "documents.write", }] }' ``` ```bash v2 cURL {1} icon=terminal curl -X POST https://api.unkey.com/v2/keys.removePermissions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "permissions": ["documents.write", "documents.delete"] }' ``` *** ### POST /v1/keys.setPermissions → POST /v2/keys.setPermissions **Purpose:** Atomically replace all permissions on an API key. **Key Changes:** * Response format: Direct response → `{meta, data}` envelope * Atomic replacement of all permissions * Will auto create permissions if they don't exist, and the root key has the `rbac.*.create_permission` permission. Otherwise it will return a permission error. * Added `meta.requestId` for debugging ```json Set Permissions Request expandable icon=shield-check // v1 Request { "keyId": "key_123", "permissions": [ { // [!code --] "slug": "documents.read", // [!code --] "create": true, // [!code --] }, // [!code --] { // [!code --] "id": "perm_123", // [!code --] } // [!code --] ] } // v2 Request { "keyId": "key_123", "permissions": ["documents.read", "comments.moderate"] // [!code ++] } ``` ```json Set Permissions Response Diff icon=check-circle // v1 Response (array of all permissions on key) [ { "id": "perm_123", // [!code --] "name": "documents.read" // [!code --] }, { "id": "perm_789", // [!code --] "name": "comments.moderate" // [!code --] } ] // [!code --] // v2 Response { "meta": { // [!code ++] "requestId": "req_setperms789" // [!code ++] }, // [!code ++] "data": [{ // [!code ++] "id": "perm_123", // [!code ++] "slug": "documents.read", // [!code ++] "name": "Documents Read", // [!code ++] "description": "Read access to documents" // [!code ++] }, // [!code ++] { // [!code ++] "id": "perm_789", // [!code ++] "slug": "comments.moderate", // [!code ++] "name": "Comments Moderate", // [!code ++] "description": "Moderate comments" // [!code ++] }] // [!code ++] } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.setPermissions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "permissions": [ { "slug": "documents.read", } ] }' ``` ```bash v2 cURL {1} icon=terminal curl -X POST https://api.unkey.com/v2/keys.setPermissions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "permissions": ["documents.read"] }' ``` *** ## Role Management Endpoints ### POST /v1/keys.addRoles → POST /v2/keys.addRoles **Purpose:** Add roles to an existing API key. **Key Changes:** * Response format: Direct response → `{meta, data}` envelope * No option to auto-create roles if they don't exist * Responds with all the roles that are attached to the key * Added `meta.requestId` for debugging ```json Add Roles Request icon=user-plus // v1 Request { "keyId": "key_123", "roles": [ { "name": "editor" }, // [!code --] { "id": "role_123" } // [!code --] ] } // v2 Request { "keyId": "key_123", "roles": ["editor", "moderator"] // [!code ++] } ``` ```json Add Roles Response Diff icon=check-circle // v1 Response (array of added roles) [ { "id": "role_123", "name": "editor" }, { "id": "role_456", "name": "moderator" } ] // v2 Response { "meta": { // [!code ++] "requestId": "req_addroles123" // [!code ++] }, // [!code ++] "data": [ { "id": "role_123", "name": "editor", "permissions": [{ // [!code ++] "id": "perm_123", // [!code ++] "name": "document reader", // [!code ++] "slug": "document.read", // [!code ++] "description": "Read documents but don't edit them" // [!code ++] }] // [!code ++] }, { "id": "role_456", "name": "moderator" } ] } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.addRoles \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "roles": [ { "id": "role_123" }, { "name": "editor" } ] }' ``` ```bash v2 cURL {1} icon=terminal curl -X POST https://api.unkey.com/v2/keys.addRoles \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "roles": ["editor", "moderator"] }' ``` *** ### POST /v1/keys.removeRoles → POST /v2/keys.removeRoles **Purpose:** Remove roles from an existing API key. **Key Changes:** * Response format: Direct response → `{meta, data}` envelope * Responds with the remaining roles on the key * Added `meta.requestId` for debugging ```json Remove Roles Request icon=user-minus // v1 Request { "keyId": "key_123", "roles": [ { "name": "editor" }, // [!code --] { "id": "role_123" } // [!code --] ] } // v2 Request { "keyId": "key_123", "roles": ["editor", "moderator"] // [!code ++] } ``` ```json Remove Roles Response Diff icon=check-circle // v1 Response (direct empty response) {} // [!code --] // v2 Response { "meta": { // [!code ++] "requestId": "req_removeroles456" // [!code ++] }, // [!code ++] "data": [] } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.removeRoles \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "roles": ["moderator"] }' ``` ```bash v2 cURL {1} icon=terminal curl -X POST https://api.unkey.com/v2/keys.removeRoles \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "roles": ["moderator"] }' ``` *** ### POST /v1/keys.setRoles → POST /v2/keys.setRoles **Purpose:** Atomically replace all roles on an API key. **Key Changes:** * Response format: Direct response → `{meta, data}` envelope * Atomic replacement of all roles * Responds with all the roles that are now set * No auto creation of roles anymore * Added `meta.requestId` for debugging ```json Set Roles Request icon=users-cog // v1 Request { "keyId": "key_123", "roles": [ { "name": "editor" }, // [!code --] { "id": "role_123" } // [!code --] ] } // v2 Request { "keyId": "key_123", "roles": ["editor", "moderator"] // [!code ++] } ``` ```json Set Roles Response Diff icon=check-circle // v1 Response (array of all roles on key) [ { "id": "role_123", "name": "editor" }, { "id": "role_789", "name": "moderator" } ] // v2 Response { "meta": { // [!code ++] "requestId": "req_setroles789" // [!code ++] }, // [!code ++] "data": [ { "id": "role_123", "name": "editor", "permissions": [ // [!code ++] { // [!code ++] "id": "perm_123", // [!code ++] "name": "document reader", // [!code ++] "slug": "document.read", // [!code ++] "description": "Read documents but don't edit them" // [!code ++] } ] }, { "id": "role_456", "name": "moderator" } ] } ``` ```bash v1 cURL icon=terminal curl -X POST https://api.unkey.dev/v1/keys.setRoles \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "roles": [ { "name": "editor" }, { "id": "role_789" } ] }' ``` ```bash v2 cURL {1} icon=terminal curl -X POST https://api.unkey.com/v2/keys.setRoles \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "keyId": "key_123", "roles": ["editor", "admin"] }' ``` *** ## Migration Patterns ### Response Format Migration ```typescript v1 vs v2: Response Handling // v1: Access data directly const key = await fetch('/v1/keys.getKey', { // [!code --] method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ keyId: 'key_123' }) }); const data = await key.json(); // [!code --] const keyData = data; // v1 direct format // [!code --] console.log(keyData.keyId); // v2: Access data through data field const key = await fetch('/v2/keys.getKey', { // [!code ++] method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ keyId: 'key_123' }) }); const response = await key.json(); // [!code ++] const keyData = response.data; // v2 format // [!code ++] const requestId = response.meta.requestId; // for debugging // [!code ++] console.log(keyData.keyId); ``` ### Key Structure Migration ```json v1 vs v2: Key Structure // v1 Key Structure { "apiId": "api_123", "ownerId": "user_456", // [!code --] "remaining": 1000, // [!code --] "refill": { // [!code --] "interval": "monthly", // [!code --] "amount": 1000 // [!code --] }, // [!code --] "ratelimit": { // [!code --] "limit": 100, // [!code --] "duration": 60000, // [!code --] "async": true // [!code --] } // [!code --] } // v2 Key Structure { "identity": { // [!code ++] "externalId": "user_456", // [!code ++] "id": "id_123", // [!code ++] "ratelimits": [], // [!code ++] "meta": {}, // [!code ++] }, // [!code ++] "credits": { // [!code ++] "remaining": 1000, // [!code ++] "refill": { // [!code ++] "interval": "monthly", // [!code ++] "amount": 1000, // [!code ++] "refillDay": 1 // [!code ++] } // [!code ++] }, // [!code ++] "ratelimits": [ // [!code ++] { // [!code ++] "name": "api_requests", // [!code ++] "limit": 100, // [!code ++] "duration": 60000, // [!code ++] "autoApply": true // [!code ++] } // [!code ++] ] // [!code ++] } ``` *** ## Migration Checklist ### Key Creation & Updates * Replace `ownerId` with `externalId` * Update `remaining` + `refill` → `credits` structure * Convert `ratelimit` → `ratelimits` array * Add `name` field to rate limits * Change `async` parameter to `autoApply` * Add `refillDay` for monthly intervals ### Key Verification * **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 * Extract and log `meta.requestId` from responses for debugging * Remove references to `ownerId` in response parsing * Update error handling for new response structure ### Endpoint Updates * Update `keys.updateRemaining` → `keys.updateCredits` * Add `operation` parameter for credit updates (set/increment/decrement) * Add `permanent` parameter for key deletion if needed ### Testing * Test key creation with new structure * Test key verification with string-based permission queries * Test permission and role management operations * Verify key updates work with new credit structure * Confirm all responses follow new envelope format # /v1/permissions.* Source: https://unkey.com/docs/api-reference/v1/migration/permissions Migrate from key-based permission patterns to dedicated permission and role management in v2 This guide covers migrating from v1's key-based permission patterns to v2's dedicated RBAC (Role-Based Access Control) system. ## Overview Both v1 and v2 have standalone permission and role management endpoints. The main differences are in request/response formats, HTTP methods, and enhanced functionality in v2. ### Key Changes in v2: * **Response format**: Direct responses → `{meta, data}` envelope * **HTTP methods**: Some GET → POST changes for consistency * **Enhanced pagination**: Better pagination support in list endpoints * **Domain change**: `api.unkey.dev` → `api.unkey.com` ### Migration Impact: * **Both versions**: Have standalone permission and role endpoints * **v2 Enhancement**: Improved response format, consistent HTTP methods, simplified permission queries * **Benefit**: Better API consistency, enhanced debugging with request IDs, simplified permission syntax *** ## POST /v1/permissions.createPermission → POST /v2/permissions.createPermission **Purpose:** Create a standalone permission that can be reused across keys and roles. **Key Changes:** * Response format: Direct response → `{meta, data}` envelope * Domain change: `api.unkey.dev` → `api.unkey.com` ```bash title="v1 vs v2: Standalone Permission Creation" icon="shield-plus" # v1: Create permission independently curl -X POST https://api.unkey.dev/v1/permissions.createPermission # [!code --] -H "Authorization: Bearer " # [!code --] -H "Content-Type: application/json" -d '{"name": "documents.read", "description": "Read access to documents"}' # [!code --] # v2: Create permission once, reuse everywhere curl -X POST https://api.unkey.com/v2/permissions.createPermission # [!code ++] -H "Authorization: Bearer " # [!code ++] -H "Content-Type: application/json" -d '{"slug": "documents.read", "name": "documents.read", "description": "Read access to documents"}' # [!code ++] ``` ```json title="Create Permission Response" icon="check-circle" expandable { "meta": { "requestId": "req_createpermission123" }, "data": { "id": "perm_abc123def456", "name": "documents.read", "slug": "documents.read", "description": "Read access to documents" } } ``` *** ## GET /v1/permissions.getPermission → POST /v2/permissions.getPermission **Purpose:** Retrieve permission details by name. **Key Changes:** * HTTP method: GET → POST * Request format: Query parameters → Request body * Response format: Direct response → `{meta, data}` envelope * Lookup Permission by either ID or Slug ```bash title="v1 vs v2: Direct Permission Retrieval" icon="shield" # v1: Get permission by ID curl -X GET "https://api.unkey.dev/v1/permissions.getPermission?permissionId=perm_123" # [!code --] -H "Authorization: Bearer " # [!code --] # v2: Direct permission lookup curl -X POST https://api.unkey.com/v2/permissions.getPermission # [!code ++] -H "Authorization: Bearer " # [!code ++] -H "Content-Type: application/json" -d '{"permission": "documents.read"}' # [!code ++] ``` ```json title="Get Permission Response" icon="database" expandable { "meta": { "requestId": "req_getpermission456" }, "data": { "id": "perm_abc123def456", "name": "documents.read", "slug": "documents.read", "description": "Read access to documents" } } ``` *** ## GET /v1/permissions.listPermissions → POST /v2/permissions.listPermissions **Purpose:** Get paginated list of all permissions. **Key Changes:** * HTTP method: GET → POST * Request format: Query parameters → Request body * Response format: Direct array → `{meta, data}` envelope with pagination ```bash title="v1 vs v2: Direct Permission Listing" icon="shield-check" # v1: List all permissions directly curl -X GET "https://api.unkey.dev/v1/permissions.listPermissions" # [!code --] -H "Authorization: Bearer " # [!code --] # v2: Direct permission listing with pagination curl -X POST https://api.unkey.com/v2/permissions.listPermissions # [!code ++] -H "Authorization: Bearer " # [!code ++] -H "Content-Type: application/json" -d '{"limit": 100, "cursor": "optional_cursor"}' # [!code ++] ``` ```json title="List Permissions Response" icon="list" expandable { "meta": { "requestId": "req_listpermissions789" }, "data": [ { "permissionId": "perm_abc123", "name": "Read documents", "slug": "documents.read", "description": "Read access to documents", }, { "permissionId": "perm_def456", "name": "Write documents", "slug": "documents.write", "description": "Write access to documents", } ], "pagination": { "cursor": "next_page_cursor_here", "hasMore": true } } ``` *** ## POST /v1/permissions.deletePermission → POST /v2/permissions.deletePermission **Purpose:** Permanently delete a permission globally. **Key Changes:** * Response format: Direct response → `{meta, data}` envelope * Delete permission by ID or slug * Domain change: `api.unkey.dev` → `api.unkey.com` ```bash title="v1 vs v2: Global Permission Deletion" icon="shield-minus" # v1: Delete permission globally curl -X POST https://api.unkey.dev/v1/permissions.deletePermission # [!code --] -H "Authorization: Bearer " # [!code --] -H "Content-Type: application/json" -d '{"permissionId": "perm_123"}' # [!code --] # v2: Delete permission globally (removes from all keys and roles) curl -X POST https://api.unkey.com/v2/permissions.deletePermission # [!code ++] -H "Authorization: Bearer " # [!code ++] -H "Content-Type: application/json" -d '{"permission": "documents.read"}' # [!code ++] ``` ```json title="Delete Permission Response" icon="check-circle" { "meta": { "requestId": "req_deletepermission999" }, "data": {} } ``` *** ## Role-Based Access Control (RBAC) Migration **Purpose:** Group permissions into roles for easier management - available in both v1 and v2. **Key Changes:** * Response format: Direct responses → `{meta, data}` envelope * Enhanced role listing with better pagination in v2 ### POST /v1/permissions.createRole → POST /v2/permissions.createRole ```bash title="v1 vs v2: Role Creation" icon="users-cog" # v1: Create role curl -X POST https://api.unkey.dev/v1/permissions.createRole # [!code --] -H "Authorization: Bearer " # [!code --] -H "Content-Type: application/json" -d '{"name": "editor", "description": "Content editor role"}' # [!code --] # v2: Create role (envelope response) curl -X POST https://api.unkey.com/v2/permissions.createRole # [!code ++] -H "Authorization: Bearer " # [!code ++] -H "Content-Type: application/json" -d '{"name": "editor", "description": "Content editor role"}' # [!code ++] ``` ```json title="Create Role Response" icon="check-circle" expandable { "meta": { "requestId": "req_createrole123" }, "data": { "roleId": "role_abc123def456" } } ``` ```bash title="Assign Role to Key" icon="key" # Assign role to key (gives all role permissions) curl -X POST https://api.unkey.com/v2/keys.addRoles # [!code ++] -H "Authorization: Bearer " # [!code ++] -H "Content-Type: application/json" -d '{"keyId": "key_123", "roles": ["editor"]}' # [!code ++] ``` ### GET /v1/permissions.getRole → POST /v2/permissions.getRole * Lookup role by either ID or name ```bash title="v1 vs v2: Role Retrieval" icon="user" # v1: GET with query parameter curl -X GET "https://api.unkey.dev/v1/permissions.getRole?roleId=role_123" # [!code --] -H "Authorization: Bearer " # [!code --] # v2: POST with request body curl -X POST https://api.unkey.com/v2/permissions.getRole # [!code ++] -H "Authorization: Bearer " # [!code ++] -H "Content-Type: application/json" -d '{"role": "editor"}' # [!code ++] ``` ```json title="Get Role Response" icon="database" expandable { "meta": { "requestId": "req_getrole456" }, "data": { "id": "role_abc123def456", "name": "editor", "description": "Content editor role", "permissions": [ "documents.read", "documents.write", "comments.moderate" ] } } ``` ### GET /v1/permissions.listRoles → POST /v2/permissions.listRoles ```bash title="v1 vs v2: Role Listing" icon="user-group" # v1: GET request curl -X GET "https://api.unkey.dev/v1/permissions.listRoles" # [!code --] -H "Authorization: Bearer " # [!code --] # v2: POST with enhanced pagination curl -X POST https://api.unkey.com/v2/permissions.listRoles # [!code ++] -H "Authorization: Bearer " # [!code ++] -H "Content-Type: application/json" -d '{"limit": 100, "cursor": "optional_cursor"}' # [!code ++] ``` ```json title="List Roles Response" icon="list" expandable { "meta": { "requestId": "req_listroles789" }, "data": [ { "roleId": "role_abc123", "name": "editor", "description": "Content editor role", "permissions": [ "documents.read", "documents.write", "comments.moderate" ] }, { "roleId": "role_def456", "name": "admin", "description": "Full administrative access", "permissions": [ "documents.*", "users.*", "settings.*" ] } ], "pagination": { "cursor": "next_page_cursor_here", "hasMore": true } } ``` ### POST /v1/permissions.deleteRole → POST /v2/permissions.deleteRole * Delete role by either ID or name ```bash title="v1 vs v2: Role Deletion" icon="user-minus" # v1: Delete role globally curl -X POST https://api.unkey.dev/v1/permissions.deleteRole # [!code --] -H "Authorization: Bearer " # [!code --] -H "Content-Type: application/json" -d '{"roleId": "role_123"}' # [!code --] # v2: Delete role globally (envelope response) curl -X POST https://api.unkey.com/v2/permissions.deleteRole # [!code ++] -H "Authorization: Bearer " # [!code ++] -H "Content-Type: application/json" -d '{"role": "editor"}' # [!code ++] ``` ```json title="Delete Role Response" icon="check-circle" { "meta": { "requestId": "req_deleterole999" }, "data": {} } ``` *** ## Permission Query Migration: v1 Object → v2 String Syntax **Key Change:** v2 simplifies permission queries from complex object syntax to intuitive string syntax. ```json title="AND Query Migration" icon="arrow-right" // v1: Object syntax for key verification { "authorization": { // [!code --] "permissions": { // [!code --] "and": ["documents.read", "documents.write"] // [!code --] } // [!code --] } // [!code --] } // v2: String syntax for key verification { "permissions": "documents.read AND documents.write" // [!code ++] } ``` ```json title="OR Query Migration" icon="arrow-right" // v1: Object syntax { "authorization": { // [!code --] "permissions": { // [!code --] "or": ["documents.read", "documents.write"] // [!code --] } // [!code --] } // [!code --] } // v2: String syntax { "permissions": "documents.read OR documents.write" // [!code ++] } ``` ```json title="Complex Query Migration" icon="arrow-right" // v1: Nested object syntax { "authorization": { // [!code --] "permissions": { // [!code --] "and": [ // [!code --] "documents.read", // [!code --] { // [!code --] "or": ["documents.write", "documents.delete"] // [!code --] } // [!code --] ] // [!code --] } // [!code --] } // [!code --] } // v2: String syntax with parentheses { "permissions": "documents.read AND (documents.write OR documents.delete)" // [!code ++] } ``` ```bash title="Permission Query in Key Verification" icon="terminal" # v1: Complex object syntax curl -X POST https://api.unkey.dev/v1/keys.verifyKey # [!code --] -H "Authorization: Bearer " # [!code --] -H "Content-Type: application/json" -d '{"key": "uk_123", "authorization": {"permissions": {"and": ["documents.read", "documents.write"]}}}' # [!code --] # v2: Simple string syntax curl -X POST https://api.unkey.com/v2/keys.verifyKey # [!code ++] -H "Authorization: Bearer " # [!code ++] -H "Content-Type: application/json" -d '{"key": "uk_123", "permissions": "documents.read AND documents.write"}' # [!code ++] ``` *** ## Migration Patterns ### Response Format Migration ```typescript title="v1 vs v2: Standalone Permission Management" // v1: Create permission independently const response = await fetch('/v1/permissions.createPermission', { // [!code --] method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'documents.read', description: 'Read access to documents' }) }); const permissionData = await response.json(); // Direct response // [!code --] // Permission exists independently, can be reused // v2: Create permission independently const permissionResponse = await fetch('/v2/permissions.createPermission', { // [!code ++] method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'documents.read', description: 'Read access to documents' }) }); const result = await permissionResponse.json(); // [!code ++] const permissionData = result.data; // v2 envelope format // [!code ++] const requestId = result.meta.requestId; // For debugging // [!code ++] // Permission now exists independently, can be reused ``` ### RBAC Implementation Patterns ```typescript title="v1 vs v2: RBAC Implementation Patterns" // v1: Had to add permissions to each key individually const keys = ['key_123', 'key_456', 'key_789']; const permissions = [ // [!code --] { name: 'documents.read', description: 'Read access' }, // [!code --] { name: 'documents.write', description: 'Write access' } // [!code --] ]; // [!code --] // Add same permissions to multiple keys // [!code --] for (const keyId of keys) { // [!code --] await fetch('/v1/keys.addPermissions', { // [!code --] method: 'POST', // [!code --] headers: { // [!code --] 'Authorization': 'Bearer ', // [!code --] 'Content-Type': 'application/json' // [!code --] }, // [!code --] body: JSON.stringify({ keyId, permissions }) // [!code --] }); // [!code --] } // [!code --] // v2: Create role once, assign to multiple keys const roleResponse = await fetch('/v2/permissions.createRole', { // [!code ++] method: 'POST', // [!code ++] headers: { // [!code ++] 'Authorization': 'Bearer ', // [!code ++] 'Content-Type': 'application/json' // [!code ++] }, // [!code ++] body: JSON.stringify({ // [!code ++] name: 'editor', // [!code ++] description: 'Content editor role', // [!code ++] permissions: ['documents.read', 'documents.write', 'comments.moderate'] // [!code ++] }) // [!code ++] }); // [!code ++] // Assign role to multiple keys (much more efficient) // [!code ++] for (const keyId of keys) { // [!code ++] await fetch('/v2/keys.addRoles', { // [!code ++] method: 'POST', // [!code ++] headers: { // [!code ++] 'Authorization': 'Bearer ', // [!code ++] 'Content-Type': 'application/json' // [!code ++] }, // [!code ++] body: JSON.stringify({ keyId, roles: ['editor'] }) // [!code ++] }); // [!code ++] } // [!code ++] ``` *** ## Key Benefits of v2 Permission Management ### Reusable Permission Definitions ```json title="Standalone permission creation" icon="shield-plus" { "name": "api.execute", "description": "Execute API operations" } ``` Create permissions once, use across multiple keys and roles. ### Role-Based Access Control ```json title="Role with grouped permissions" icon="users-cog" { "name": "api_admin", "description": "Full API administrative access", "permissions": [ "api.execute", "api.read", "api.write", "api.delete", "users.manage" ] } ``` Group related permissions into roles for easier management. ### Auto-Creation Support ```json title="Auto-create when referenced" icon="magic-wand" { "keyId": "key_123", "permissions": ["new.permission"] } ``` Permissions and roles are automatically created when referenced if they don't exist. ### Simplified Query Syntax ```bash title="String-based permission queries" icon="code" # Simple AND "permissions": "read AND write" # Simple OR "permissions": "read OR write" # Complex with parentheses "permissions": "read AND (write OR delete)" ``` ## Migration Checklist ### Pattern Migration * [ ] Identify current v1 permission and role usage patterns * [ ] Update HTTP methods (GET → POST for some endpoints) * [ ] Update request formats (query parameters → request body) * [ ] Update response parsing (direct → envelope format) ### Enhanced Functionality * [ ] Update to v2 envelope response format with `meta.requestId` * [ ] Use enhanced pagination in list endpoints * [ ] Update domain from `api.unkey.dev` to `api.unkey.com` * [ ] Leverage auto-creation for dynamic permission scenarios ### Query Syntax Migration * [ ] Convert object-based permission queries to string syntax in key verification * [ ] Update AND operations: `{"and": []}` → `"perm1 AND perm2"` * [ ] Update OR operations: `{"or": []}` → `"perm1 OR perm2"` * [ ] Handle complex nested queries with parentheses: `"perm1 AND (perm2 OR perm3)"` ### Response Format Updates * [ ] Update response parsing from direct format to `response.data` * [ ] Extract and log `meta.requestId` from responses for debugging * [ ] Handle new error structure with meta envelope ### Testing * [ ] Test HTTP method changes (GET → POST) * [ ] Verify request body format vs query parameters * [ ] Test permission queries with new string syntax * [ ] Confirm envelope response format parsing # /v1/ratelimits.* Source: https://unkey.com/docs/api-reference/v1/migration/ratelimiting Migrate rate limiting and override management endpoints from v1 to v2 This guide covers rate limiting functionality including namespace creation, override management, and rate limit checking. ## Overview Rate limiting endpoints manage request limits, overrides, and namespace-based rate limiting across your API infrastructure. ### Key Changes in v2: * **Response format**: `result` → `{meta, data}` wrapper * **Rate limit structure**: Single `ratelimit` object → `ratelimits` array with named limits * **Override management**: Enhanced override response format with additional metadata * **Async handling**: Removed `async` parameter * **Auto apply**: Added `autoApply` parameter to apply ratelimits to key verifications automatically * **Resources**: Removed `resources` array * **Metadata**: Removed `meta` object from request body ### Migration Impact: * **Existing in v1**: Full rate limiting and override management functionality * **Enhanced in v2**: Improved response format, better override metadata, and new listing capabilities * **Maintained in v2**: All core rate limiting functionality with backward-compatible request formats *** ## Rate Limit Checking ### POST /v1/ratelimits.limit → POST /v2/ratelimits.limit **Key Changes:** * Response format: Direct response → `{meta, data}` envelope * Enhanced response with additional metadata * Better override handling ```json title="Rate Limit Request" icon="bolt" { "namespace": "email_sending", "identifier": "user_123", "limit": 100, "duration": 3600000, "cost": 1, "async": false, // [!code --] "meta": {}, // [!code --] "resources": [ // [!code --] { // [!code --] "type": "project", // [!code --] "id": "p_123", // [!code --] "name": "unkey" // [!code --] } // [!code --] ] // [!code --] } ``` ```json title="Rate Limit Response Diff" icon="database" expandable // v1 Response (direct response) { "success": true, // [!code --] "limit": 100, // [!code --] "remaining": 99, // [!code --] "reset": 1672531200000 // [!code --] } // v2 Response { "meta": { // [!code ++] "requestId": "req_ratelimit123" // [!code ++] }, // [!code ++] "data": { // [!code ++] "success": true, // [!code ++] "limit": 100, // [!code ++] "remaining": 99, // [!code ++] "reset": 1672531200000, // [!code ++] "overrideId": "rlor_123" // [!code ++] } // [!code ++] } ``` ```bash title="v1 cURL" icon="terminal" curl -X POST https://api.unkey.dev/v1/ratelimits.limit \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "namespace": "email_sending", "identifier": "user_123", "limit": 100, "duration": 3600000, "cost": 1 }' ``` ```bash title="v2 cURL" icon="terminal" curl -X POST https://api.unkey.com/v2/ratelimits.limit \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "namespace": "email_sending", "identifier": "user_123", "limit": 100, "duration": 3600000, "cost": 1 }' ``` *** ## Rate Limit Overrides ### POST /v1/ratelimits.setOverride → POST /v2/ratelimits.setOverride **Key Changes:** * Response format: Direct response → `{meta, data}` envelope * Enhanced override targeting options * Better validation and error handling ```json title="Set Override Request" icon="settings" expandable { "namespace": "api_requests", "identifier": "premium_user_456", "limit": 10000, "duration": 3600000, "async": false } ``` ```json title="Set Override Response Diff" icon="check-circle" expandable // v1 Response (direct response) { "overrideId": "rlor_123" // [!code --] } // v2 Response { "meta": { // [!code ++] "requestId": "req_setoverride456" // [!code ++] }, // [!code ++] "data": { // [!code ++] "overrideId": "rlor_123" // [!code ++] } // [!code ++] } ``` ```bash title="v1 cURL" icon="terminal" curl -X POST https://api.unkey.dev/v1/ratelimits.setOverride \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "namespace": "api_requests", "identifier": "premium_user_456", "limit": 10000, "duration": 3600000 }' ``` ```bash title="v2 cURL" icon="terminal" curl -X POST https://api.unkey.com/v2/ratelimits.setOverride \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "namespace": "api_requests", "identifier": "premium_user_456", "limit": 10000, "duration": 3600000 }' ``` *** ### GET /v1/ratelimits.getOverride → POST /v2/ratelimits.getOverride **Key Changes:** * HTTP method: GET → POST * Request format: Query parameters → Request body * Response format: Direct response → `{meta, data}` envelope ```json title="Get Override Request" icon="search" { "namespace": "api_requests", "identifier": "premium_user_456" } ``` ```json title="Get Override Response Diff" icon="database" expandable // v1 Response (direct response) { "id": "over_123", // [!code --] "identifier": "premium_user_456", // [!code --] "limit": 10000, // [!code --] "duration": 3600000, // [!code --] "async": false // [!code --] } // v2 Response { "meta": { // [!code ++] "requestId": "req_getoverride789" // [!code ++] }, // [!code ++] "data": { // [!code ++] "overrideId": "override_abc123", // [!code ++] "identifier": "premium_user_456", // [!code ++] "limit": 10000, // [!code ++] "duration": 3600000 // [!code ++] } // [!code ++] } ``` ```bash title="v1 cURL" icon="terminal" curl -X GET "https://api.unkey.dev/v1/ratelimits.getOverride?identifier=premium_user_456&namespaceName=api_requests" \ -H "Authorization: Bearer " ``` ```bash title="v2 cURL" icon="terminal" curl -X POST https://api.unkey.com/v2/ratelimits.getOverride \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "namespace": "api_requests", "identifier": "premium_user_456" }' ``` *** ### GET /v1/ratelimits.listOverrides → POST /v2/ratelimits.listOverrides **Purpose:** Get paginated list of all overrides in a namespace. **Key Changes:** * HTTP method: GET → POST * Request format: Query parameters → Request body * Response format: Direct response → `{meta, data}` envelope ```json title="List Overrides Request" icon="list" { "namespace": "api_requests", "limit": 100, "cursor": "optional_cursor" } ``` ```json title="List Overrides Response Diff" icon="database" expandable // v1 Response (direct response) { "overrides": [ // [!code --] { // [!code --] "id": "override_abc123", // [!code --] "identifier": "premium_user_456", // [!code --] "limit": 10000, // [!code --] "duration": 3600000, // [!code --] "async": false // [!code --] } // [!code --] ], // [!code --] "cursor": "next_page_cursor_here", // [!code --] "total": 42 // [!code --] } // v2 Response (envelope format) { "meta": { // [!code ++] "requestId": "req_listoverrides123" // [!code ++] }, // [!code ++] "data": { // [!code ++] "overrides": [ // [!code ++] { // [!code ++] "overrideId": "override_abc123", // [!code ++] "identifier": "premium_user_456", // [!code ++] "limit": 10000, // [!code ++] "duration": 3600000, // [!code ++] } // [!code ++] ], // [!code ++] "cursor": "next_page_cursor_here" // [!code ++] } // [!code ++] } ``` ```bash title="v1 cURL" icon="terminal" curl -X GET "https://api.unkey.dev/v1/ratelimits.listOverrides?namespaceName=api_requests&limit=100" \ -H "Authorization: Bearer " ``` ```bash title="v2 cURL" icon="terminal" curl -X POST https://api.unkey.com/v2/ratelimits.listOverrides \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "namespace": "api_requests", "limit": 100, "cursor": "optional_cursor" }' ``` *** ### POST /v1/ratelimits.deleteOverride → POST /v2/ratelimits.deleteOverride **Key Changes:** * Response format: Direct response → `{meta, data}` envelope ```json title="Delete Override Request" icon="trash" { "namespace": "api_requests", "identifier": "premium_user_456" } ``` ```json title="Delete Override Response Diff" icon="check-circle" // v1 Response (direct empty response) {} // [!code --] // v2 Response { "meta": { // [!code ++] "requestId": "req_deleteoverride999" // [!code ++] }, // [!code ++] "data": {} // [!code ++] } ``` ```bash title="v1 cURL" icon="terminal" curl -X POST https://api.unkey.dev/v1/ratelimits.deleteOverride \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "namespace": "api_requests", "identifier": "premium_user_456" }' ``` ```bash title="v2 cURL" icon="terminal" curl -X POST https://api.unkey.com/v2/ratelimits.deleteOverride \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "namespace": "api_requests", "identifier": "premium_user_456" }' ``` *** ## Key-Level Rate Limiting Changes ### v1 Single Rate Limit → v2 Multiple Named Rate Limits ```json title="Key Rate Limit Structure Migration" icon="key" expandable // v1 Key Creation { "apiId": "api_123", "ratelimit": { // [!code --] "limit": 1000, // [!code --] "duration": 3600000, // [!code --] "async": true // [!code --] } // [!code --] } // v2 Key Creation { "apiId": "api_123", "ratelimits": [ // [!code ++] { // [!code ++] "name": "api_requests", // [!code ++] "limit": 1000, // [!code ++] "duration": 3600000, // [!code ++] "autoApply": true // [!code ++] }, // [!code ++] { // [!code ++] "name": "heavy_operations", // [!code ++] "limit": 10, // [!code ++] "duration": 60000, // [!code ++] "autoApply": false // [!code ++] } // [!code ++] ] // [!code ++] } ``` ```json title="Rate Limit Verification Migration" icon="shield-check" expandable // v1 Key Verification Request { "key": "sk_123" } // v2 Key Verification Request with Named Rate Limits { "key": "sk_123", "ratelimits": [ { "name": "api_requests", "cost": 1 }, { "name": "heavy_operations", "cost": 5 } ] } ``` ```json title="Rate Limit Response Migration" icon="database" expandable // v1 Response { "result": { "valid": true, "ratelimit": { // [!code --] "limit": 1000, // [!code --] "remaining": 999, // [!code --] "reset": 1672531200000 // [!code --] } // [!code --] } } // v2 Response { "data": { "valid": true, "ratelimits": [ // [!code ++] { // [!code ++] "id": "rl_123", // [!code ++] "name": "api_requests", // [!code ++] "limit": 1000, // [!code ++] "remaining": 999, // [!code ++] "reset": 1672531200000, // [!code ++] "exceeded": false, // [!code ++] "duration": 3600000, // [!code ++] "autoApply": true // [!code ++] }, // [!code ++] { // [!code ++] "id": "rl_456", // [!code ++] "name": "heavy_operations", // [!code ++] "limit": 10, // [!code ++] "remaining": 5, // [!code ++] "reset": 1672531200000, // [!code ++] "exceeded": false, // [!code ++] "duration": 60000, // [!code ++] "autoApply": false // [!code ++] } // [!code ++] ] // [!code ++] } } ``` *** ## Migration Patterns ### Response Format Migration ```typescript title="v1 vs v2: Response Handling" // v1: Direct response access const rateLimit = await fetch('/v1/ratelimits.limit', { // [!code --] method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ namespace: 'api_calls', identifier: 'user_123', limit: 100, duration: 3600000 }) }); const data = await rateLimit.json(); // [!code --] const success = data.success; // v1 direct format // [!code --] const remaining = data.remaining; // [!code --] // v2: Access data through data field const rateLimit = await fetch('/v2/ratelimits.limit', { // [!code ++] method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ namespace: 'api_calls', identifier: 'user_123', limit: 100, duration: 3600000 }) }); const response = await rateLimit.json(); // [!code ++] const success = response.data.success; // v2 format // [!code ++] const remaining = response.data.remaining; // [!code ++] const requestId = response.meta.requestId; // for debugging // [!code ++] ``` ### Key-Level Rate Limiting Migration ```json title="v1 vs v2: Key Rate Limit Structure" // v1: Single Rate Limit { "apiId": "api_123", "ratelimit": { // [!code --] "limit": 1000, // [!code --] "duration": 3600000, // [!code --] "async": true // [!code --] } // [!code --] } // v2: Multiple Named Rate Limits { "apiId": "api_123", "ratelimits": [ // [!code ++] { // [!code ++] "name": "general_requests", // [!code ++] "limit": 1000, // [!code ++] "duration": 3600000, // [!code ++] "autoApply": true // [!code ++] }, // [!code ++] { // [!code ++] "name": "expensive_ops", // [!code ++] "limit": 10, // [!code ++] "duration": 60000, // [!code ++] "autoApply": false // [!code ++] } // [!code ++] ] // [!code ++] } ``` ### Override Management Patterns ```typescript title="Override CRUD Operations" // Set override (same in v1 & v2) const override = await fetch('/v2/ratelimits.setOverride', { method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ namespace: 'api_requests', identifier: 'premium_user', limit: 10000, duration: 3600000 }) }); // Get override (same in v1 & v2) const existing = await fetch('/v2/ratelimits.getOverride', { method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ namespace: 'api_requests', identifier: 'premium_user' }) }); const result = await existing.json(); const limit = result.data.limit; // v2: access via data ``` ```typescript title="v2: List and Batch Management" // v2: List all overrides (new capability) const overrides = await fetch('/v2/ratelimits.listOverrides', { method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ namespace: 'api_requests', limit: 100 }) }); const result = await overrides.json(); const overrideList = result.data.overrides; // Process overrides in batches for (const override of overrideList) { if (override.limit < 1000) { // Update low-limit overrides await fetch('/v2/ratelimits.setOverride', { method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json' }, body: JSON.stringify({ namespace: override.namespace, identifier: override.identifier, limit: 1000, duration: override.duration }) }); } } ``` *** ## Advanced Features in v2 ### Multiple Rate Limits per Key ```json title="Complex rate limiting setup" icon="bolt" expandable { "apiId": "api_123", "ratelimits": [ { "name": "requests_per_minute", "limit": 60, "duration": 60000, "autoApply": true }, { "name": "requests_per_hour", "limit": 1000, "duration": 3600000, "autoApply": true }, { "name": "expensive_operations", "limit": 5, "duration": 300000, "autoApply": false } ] } ``` ### Named Rate Limit Targeting ```json title="Selective rate limit application" icon="target" { "key": "sk_123", "ratelimits": [ { "name": "expensive_operations", "cost": 1 } ] } ``` Only applies cost to the "expensive\_operations" rate limit, leaving others unchanged. ### Batch Override Management ```typescript title="Managing multiple overrides" icon="list-check" // List all overrides in namespace const overrides = await unkey.ratelimits.listOverrides({ namespace: "api_requests" }); // Process overrides in batches for (const override of overrides.data?.overrides || []) { if (override.limit < 1000) { // Update low-limit overrides await unkey.ratelimits.setOverride({ namespace: override.namespace, identifier: override.identifier, limit: 1000, duration: override.duration }); } } ``` *** ## Migration Checklist ### Response Format Updates * [ ] Change direct response access to `response.data` in all rate limiting calls * [ ] Extract and log `meta.requestId` from responses for debugging * [ ] Update error handling for new envelope response structure * [ ] Handle enhanced metadata in override responses ### Key-Level Rate Limiting Updates * [ ] Convert `ratelimit` object to `ratelimits` array in key creation * [ ] Add `name` field to all rate limit configurations * [ ] Remove `async` parameter * [ ] Update Key creation to use autoApply if necessary * [ ] Plan for multiple rate limits per key (different operation types) * [ ] Update key verification to handle multiple rate limits ### Override Management Updates * [ ] Update override response parsing from `result` to `data` * [ ] Utilize new `listOverrides` endpoint for enhanced management * [ ] Handle enhanced override metadata (overrideId, createdAt) * [ ] Implement cursor-based pagination for large override lists ### Enhanced Features * [ ] Implement named rate limit targeting in key verification * [ ] Use multiple rate limits for different operation types * [ ] Set up batch override management processes using listOverrides * [ ] Plan for granular rate limit control and monitoring * [ ] Use request IDs for debugging and support ### Advanced Rate Limiting Patterns * [ ] Implement selective rate limit application by name * [ ] Set up different costs for different rate limits * [ ] Use identity-level rate limiting combined with key-level limits * [ ] Build override management dashboards with enhanced data ### Testing * [ ] Test rate limiting with new response format * [ ] Verify override creation, retrieval, and deletion * [ ] Test multiple rate limits on single keys * [ ] Validate named rate limit targeting in key verification * [ ] Confirm override listing and pagination works correctly * [ ] Test batch override management workflows # Enqueue key migration Source: https://unkey.com/docs/api-reference/v1/migrations/enqueue-key-migration https://api.unkey.dev/openapi.json post /v1/migrations.enqueueKeys **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Migrate keys Source: https://unkey.com/docs/api-reference/v1/migrations/migrate-keys https://api.unkey.dev/openapi.json post /v1/migrations.createKeys **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Overview Source: https://unkey.com/docs/api-reference/v1/overview General information about the API. The v1 API is deprecated and will be removed in January 2026. Please [migrate to the v2 API](/api-reference/v1/migration). The Unkey API uses HTTP RPC-style methods and generally follow the schema: ``` https://api.unkey.dev/{version}/{service}.{method} ``` For example `GET https://api.unkey.dev/v1/apis.listKeys` to list all keys for an API. ## HTTP Methods We strictly use only `GET` and `POST` methods. `PUT` and `DELETE` are not used. ### `GET` `GET` methods are used for reading data. Filtering, sorting, or pagination is done via query parameters. ```http curl "https://api.unkey.dev/v1/keys.getKey?keyId=key_123" \ -H "Authorization: Bearer " ``` ### `POST` `POST` methods are used for creating, updating, and deleting data. Data is passed as `application/json` in the request body. ```http curl -XPOST "https://api.unkey.dev/v1/keys.createKey" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"apiId": "api_123", "name": "My Key"}' ``` # Create permission Source: https://unkey.com/docs/api-reference/v1/permissions/create-permission https://api.unkey.dev/openapi.json post /v1/permissions.createPermission **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Create role Source: https://unkey.com/docs/api-reference/v1/permissions/create-role https://api.unkey.dev/openapi.json post /v1/permissions.createRole **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Delete permission Source: https://unkey.com/docs/api-reference/v1/permissions/delete-permission https://api.unkey.dev/openapi.json post /v1/permissions.deletePermission **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Delete role Source: https://unkey.com/docs/api-reference/v1/permissions/delete-role https://api.unkey.dev/openapi.json post /v1/permissions.deleteRole **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Get permission Source: https://unkey.com/docs/api-reference/v1/permissions/get-permission https://api.unkey.dev/openapi.json get /v1/permissions.getPermission **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Get role Source: https://unkey.com/docs/api-reference/v1/permissions/get-role https://api.unkey.dev/openapi.json get /v1/permissions.getRole **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # List permissions Source: https://unkey.com/docs/api-reference/v1/permissions/list-permissions https://api.unkey.dev/openapi.json get /v1/permissions.listPermissions **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # List roles Source: https://unkey.com/docs/api-reference/v1/permissions/list-roles https://api.unkey.dev/openapi.json get /v1/permissions.listRoles **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Check rate limit Source: https://unkey.com/docs/api-reference/v1/ratelimits/check-rate-limit https://api.unkey.dev/openapi.json post /v1/ratelimits.limit **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Delete rate limit override Source: https://unkey.com/docs/api-reference/v1/ratelimits/delete-rate-limit-override https://api.unkey.dev/openapi.json post /v1/ratelimits.deleteOverride **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Get rate limit override Source: https://unkey.com/docs/api-reference/v1/ratelimits/get-rate-limit-override https://api.unkey.dev/openapi.json get /v1/ratelimits.getOverride **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # List rate limit overrides Source: https://unkey.com/docs/api-reference/v1/ratelimits/list-rate-limit-overrides https://api.unkey.dev/openapi.json get /v1/ratelimits.listOverrides **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Set rate limit override Source: https://unkey.com/docs/api-reference/v1/ratelimits/set-rate-limit-override https://api.unkey.dev/openapi.json post /v1/ratelimits.setOverride **DEPRECATED**: This API version is deprecated. Please migrate to v2. See https://www.unkey.com/docs/api-reference/v1/migration for more information. # Create API namespace Source: https://unkey.com/docs/api-reference/v2/apis/create-api-namespace https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/apis.createApi Create an API namespace for organizing keys by environment, service, or product. Use this to separate production from development keys, isolate different services, or manage multiple products. Each API gets a unique identifier and dedicated infrastructure for secure key operations. **Important**: API names must be unique within your workspace and cannot be changed after creation. **Required Permissions** Your root key must have one of the following permissions: - `api.*.create_api` (to create APIs in any workspace) # Delete API namespace Source: https://unkey.com/docs/api-reference/v2/apis/delete-api-namespace https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/apis.deleteApi Permanently delete an API namespace and immediately invalidate all associated keys. Use this for cleaning up development environments, retiring deprecated services, or removing unused resources. All keys in the namespace are immediately marked as deleted and will fail verification with `code=NOT_FOUND`. **Important**: This operation is immediate and permanent. Verify you have the correct API ID before deletion. If delete protection is enabled, disable it first through the dashboard or API configuration. **Required Permissions** Your root key must have one of the following permissions: - `api.*.delete_api` (to delete any API) - `api..delete_api` (to delete a specific API) # Get API namespace Source: https://unkey.com/docs/api-reference/v2/apis/get-api-namespace https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/apis.getApi Retrieve basic information about an API namespace including its ID and name. Use this to verify an API exists before performing operations, get the human-readable name when you only have the API ID, or confirm access to a specific namespace. For detailed key information, use the `listKeys` endpoint instead. **Required Permissions** Your root key must have one of the following permissions: - `api.*.read_api` (to read any API) - `api..read_api` (to read a specific API) # List API keys Source: https://unkey.com/docs/api-reference/v2/apis/list-api-keys https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/apis.listKeys Retrieve a paginated list of API keys for dashboard and administrative interfaces. Use this to build key management dashboards, filter keys by user with `externalId`, or retrieve key details for administrative purposes. Each key includes status, metadata, permissions, and usage limits. **Important**: Set `decrypt: true` only in secure contexts to retrieve plaintext key values from recoverable keys. **Required Permissions** Your root key must have one of the following permissions for basic key listing: - `api.*.read_key` (to read keys from any API) - `api..read_key` (to read keys from a specific API) Additionally, you need read access to the API itself: - `api.*.read_api` or `api..read_api` Additional permission required for decrypt functionality: - `api.*.decrypt_key` or `api..decrypt_key` # Authentication Source: https://unkey.com/docs/api-reference/v2/auth Securely authenticating with the Unkey API Almost all Unkey API endpoints require authentication using a root key. Root keys provide access to your Unkey resources based on their assigned permissions. ## Bearer Authentication Authentication is performed using HTTP Bearer authentication in the `Authorization` header: ```bash Authorization: Bearer unkey_1234567890 ``` Example request: ```bash curl -X POST "https://api.unkey.com/v2/keys.createKey" \ -H "Authorization: Bearer unkey_1234567890" \ -H "Content-Type: application/json" \ -d '{ "apiId": "api_1234" }' ``` ## Security Best Practices Never expose your root key in client-side code or include it in public repositories. For frontend applications, always use a backend server to proxy requests to the Unkey API. ## Root Key Management Root keys can be created and managed through the Unkey dashboard. We recommend: 1. **Using Different Keys for Different Environments**: Maintain separate root keys for development, staging, and production 2. **Rotating Keys Regularly**: Create new keys periodically and phase out old ones 3. **Setting Clear Key Names**: Name your keys according to their use case for better manageability ## Key Permissions System Unkey implements a sophisticated RBAC (Role-Based Access Control) system for root keys. Permissions are defined as tuples of: * **ResourceType**: The category of resource (api, ratelimit, rbac, identity) * **ResourceID**: The specific resource instance * **Action**: The operation to perform on that resource ### Available Resource Types | Resource Type | Description | | ------------- | ------------------------------------------------- | | `api` | API-related resources, such as endpoints and keys | | `ratelimit` | Rate limiting resources and configuration | | `rbac` | Permissions and roles management | | `identity` | User and identity management | ### Permission Examples Specific permission to manage a single API: ``` api.api_1234.read_api api.api_1234.update_api ``` Wildcard permission to manage all rate limit namespaces: ``` ratelimit.*.create_namespace ratelimit.*.read_namespace ``` When creating root keys, you can specify exactly what actions they're allowed to perform. ## Authentication Errors If your authentication fails, you'll receive a 401 Unauthorized or 403 Forbidden response with an error message: ```json { "meta": { "requestId": "req_abc123xyz789" }, "error": { "title": "Unauthorized", "detail": "The provided root key is invalid or has been revoked", "status": 401, "type": "https://unkey.com/docs/errors/unauthorized" } } ``` If your key is valid but lacks sufficient permissions, you'll receive a 403 Forbidden response: ```json { "meta": { "requestId": "req_abc123xyz789" }, "error": { "title": "Forbidden", "detail": "Your key does not have the required 'api.api_1234.update_api' permission", "status": 403, "type": "https://unkey.com/docs/errors/forbidden" } } ``` Common authentication issues include: * Missing the Authorization header * Invalid key format * Revoked or expired root key * Using a key with insufficient permissions # Error Handling Source: https://unkey.com/docs/api-reference/v2/errors Understanding and working with API errors Error responses maintain the same top-level structure as successful responses, but with an `error` object instead of `data`: ```json { "meta": { "requestId": "req_abc123xyz789" }, "error": { "title": "Validation Error", "detail": "You must provide a valid API ID.", "status": 400, "type": "https://unkey.com/docs/errors/validation-error", "errors": [ { "location": "body.apiId", "message": "API not found", "fix": "Provide a valid API ID or create a new API" } ] } } ``` ## Error Format Our error format follows RFC7807 Problem Details standard within our consistent envelope structure, providing: * **title**: A short, human-readable summary of the problem * **detail**: A human-readable explanation specific to this occurrence * **status**: The HTTP status code (also returned in the HTTP response) * **type**: A URI reference that identifies the problem type and points to documentation * **errors**: (Optional) An array of specific validation errors when multiple issues occur ## Common Error Types | Status | Error Type | Description | | ------ | --------------------- | ------------------------------------------------ | | 400 | validation-error | The request body failed validation | | 401 | unauthorized | Missing or invalid authorization | | 403 | forbidden | Valid authorization but insufficient permissions | | 404 | not-found | The requested resource was not found | | 409 | conflict | The request conflicts with the current state | | 429 | rate-limited | You've exceeded your rate limit | | 500 | internal-server-error | An unexpected error occurred on our servers | ## Validation Errors For validation errors, we provide detailed information about each failed validation: * **location**: Where in the request the error occurred (e.g., `body.name`, `query.limit`) * **message**: What went wrong with the specific field * **fix**: (When possible) A suggestion for how to fix the issue ## Error Recovery Our error messages are designed to be actionable. Each error includes: 1. A clear explanation of what went wrong 2. Often, a suggestion for how to fix the issue 3. For validation errors, the specific fields that failed validation ## Using the Request ID for Support When reporting issues to our support team, always include the `requestId` from the error response. This unique identifier allows us to quickly locate the specific request in our logs and provide faster, more accurate assistance. ## Error Handling Best Practices 1. **Check for Status Codes**: Always check HTTP status codes first to determine broad error categories 2. **Extract Error Details**: Parse the error object for detailed information 3. **Implement Retries Carefully**: Only retry on 5xx errors or when explicitly advised 4. **Log Complete Errors**: Log the full error response for debugging purposes Example error handling in JavaScript: ```javascript try { const response = await fetch('https://api.unkey.com/v2/keys.create', { method: 'POST', headers: { 'Authorization': `Bearer ${rootKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify(keyData) }); const data = await response.json(); if (!response.ok) { // Extract and handle the error const { meta, error } = data; console.error(`Error ${error.status}: ${error.title}`, { requestId: meta.requestId, detail: error.detail, docs: error.type }); // Handle validation errors specifically if (error.errors) { error.errors.forEach(err => { console.error(`- ${err.location}: ${err.message}`); }); } throw new Error(`API Error: ${error.detail}`); } return data.data; // Return just the data portion on success } catch (err) { // Handle network errors or other exceptions console.error('Request failed:', err); throw err; } ``` # Create Identity Source: https://unkey.com/docs/api-reference/v2/identities/create-identity https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/identities.createIdentity Create an identity to group multiple API keys under a single entity. Identities enable shared rate limits and metadata across all associated keys. Perfect for users with multiple devices, organizations with multiple API keys, or when you need unified rate limiting across different services. **Important** Requires `identity.*.create_identity` permission # Delete Identity Source: https://unkey.com/docs/api-reference/v2/identities/delete-identity https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/identities.deleteIdentity Permanently delete an identity. This operation cannot be undone. Use this for data cleanup, compliance requirements, or when removing entities from your system. > **Important** > Requires `identity.*.delete_identity` permission > Associated API keys remain functional but lose shared resources > External ID becomes available for reuse immediately # Get Identity Source: https://unkey.com/docs/api-reference/v2/identities/get-identity https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/identities.getIdentity Retrieve an identity by external ID. Returns metadata, rate limits, and other associated data. Use this to check if an identity exists, view configurations, or build management dashboards. > **Important** > Requires `identity.*.read_identity` permission # List Identities Source: https://unkey.com/docs/api-reference/v2/identities/list-identities https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/identities.listIdentities Get a paginated list of all identities in your workspace. Returns metadata and rate limit configurations. Perfect for building management dashboards, auditing configurations, or browsing your identities. > **Important** > Requires `identity.*.read_identity` permission # Update Identity Source: https://unkey.com/docs/api-reference/v2/identities/update-identity https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/identities.updateIdentity Update an identity's metadata and rate limits. Only specified fields are modified - others remain unchanged. Perfect for subscription changes, plan upgrades, or updating user information. Changes take effect immediately. > **Important** > Requires `identity.*.update_identity` permission > Rate limit changes propagate within 30 seconds # Add key permissions Source: https://unkey.com/docs/api-reference/v2/keys/add-key-permissions https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.addPermissions Add permissions to a key without affecting existing permissions. Use this for privilege upgrades, enabling new features, or plan changes that grant additional capabilities. Permissions granted through roles remain unchanged. **Important**: Changes take effect immediately with up to 30-second edge propagation. **Required Permissions** Your root key must have one of the following permissions: - `api.*.update_key` (to update keys in any API) - `api..update_key` (to update keys in a specific API) **Side Effects** Invalidates the key cache for immediate effect, and makes permissions available for verification within 30 seconds across all regions. # Add key roles Source: https://unkey.com/docs/api-reference/v2/keys/add-key-roles https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.addRoles Add roles to a key without affecting existing roles or permissions. Use this for privilege upgrades, enabling new feature sets, or subscription changes that grant additional role-based capabilities. Direct permissions remain unchanged. **Important**: Changes take effect immediately with up to 30-second edge propagation. **Required Permissions** Your root key must have one of the following permissions: - `api.*.update_key` (to update keys in any API) - `api..update_key` (to update keys in a specific API) **Side Effects** Invalidates the key cache for immediate effect, and makes role assignments available for verification within 30 seconds across all regions. # Create API key Source: https://unkey.com/docs/api-reference/v2/keys/create-api-key https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.createKey Create a new API key for user authentication and authorization. Use this endpoint when users sign up, upgrade subscription tiers, or need additional keys. Keys are cryptographically secure and unique to the specified API namespace. **Important**: The key is returned only once. Store it immediately and provide it to your user, as it cannot be retrieved later. **Common use cases:** - Generate keys for new user registrations - Create additional keys for different applications - Issue keys with specific permissions or limits **Required Permissions** Your root key needs one of: - `api.*.create_key` (create keys in any API) - `api..create_key` (create keys in specific API) # Delete API keys Source: https://unkey.com/docs/api-reference/v2/keys/delete-api-keys https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.deleteKey Delete API keys permanently from user accounts or for cleanup purposes. Use this for user-requested key deletion, account deletion workflows, or cleaning up unused keys. Keys are immediately invalidated. Two modes: soft delete (default, preserves audit records) and permanent delete. **Important**: For temporary access control, use `updateKey` with `enabled: false` instead of deletion. **Required Permissions** Your root key must have one of the following permissions: - `api.*.delete_key` (to delete keys in any API) - `api..delete_key` (to delete keys in a specific API) # Get API key Source: https://unkey.com/docs/api-reference/v2/keys/get-api-key https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.getKey Retrieve detailed key information for dashboard interfaces and administrative purposes. Use this to build key management dashboards showing users their key details, status, permissions, and usage data. You can identify keys by `keyId` or the actual key string. **Important**: Set `decrypt: true` only in secure contexts to retrieve plaintext key values from recoverable keys. **Required Permissions** Your root key must have one of the following permissions for basic key information: - `api.*.read_key` (to read keys from any API) - `api..read_key` (to read keys from a specific API) Additional permission required for decrypt functionality: - `api.*.decrypt_key` or `api..decrypt_key` # Get API key by hash Source: https://unkey.com/docs/api-reference/v2/keys/get-api-key-by-hash https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.whoami Find out what key this is. **Required Permissions** Your root key must have one of the following permissions for basic key information: - `api.*.read_key` (to read keys from any API) - `api..read_key` (to read keys from a specific API) If your rootkey lacks permissions but the key exists, we may return a 404 status here to prevent leaking the existance of a key to unauthorized clients. If you believe that a key should exist, but receive a 404, please double check your root key has the correct permissions. # Remove key permissions Source: https://unkey.com/docs/api-reference/v2/keys/remove-key-permissions https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.removePermissions Remove permissions from a key without affecting existing roles or other permissions. Use this for privilege downgrades, removing temporary access, or plan changes that revoke specific capabilities. Permissions granted through roles remain unchanged. **Important**: Changes take effect immediately with up to 30-second edge propagation. **Required Permissions** Your root key must have one of the following permissions: - `api.*.update_key` (to update keys in any API) - `api..update_key` (to update keys in a specific API) **Side Effects** Invalidates the key cache for immediate effect, and makes permission changes available for verification within 30 seconds across all regions. # Remove key roles Source: https://unkey.com/docs/api-reference/v2/keys/remove-key-roles https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.removeRoles Remove roles from a key without affecting direct permissions or other roles. Use this for privilege downgrades, removing temporary access, or subscription changes that revoke specific role-based capabilities. Direct permissions remain unchanged. **Important**: Changes take effect immediately with up to 30-second edge propagation. **Required Permissions** Your root key must have one of the following permissions: - `api.*.update_key` (to update keys in any API) - `api..update_key` (to update keys in a specific API) **Side Effects** Invalidates the key cache for immediate effect, and makes role changes available for verification within 30 seconds across all regions. # Reroll Key Source: https://unkey.com/docs/api-reference/v2/keys/reroll-key https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.rerollKey Generate a new API key while preserving the configuration from an existing key. This operation creates a fresh key with a new token while maintaining all settings from the original key: - Permissions and roles - Custom metadata - Rate limit configurations - Identity associations - Remaining credits - Recovery settings **Key Generation:** - The system attempts to extract the prefix from the original key - If prefix extraction fails, the default API prefix is used - Key length follows the API's default byte configuration (or 16 bytes if not specified) **Original Key Handling:** - The original key will be revoked after the duration specified in `expiration` - Set `expiration` to 0 to revoke immediately - This allows for graceful key rotation with an overlap period Common use cases include: - Rotating keys for security compliance - Issuing replacement keys for compromised credentials - Creating backup keys with identical permissions **Important:** Analytics and usage metrics are tracked at both the key level AND identity level. If the original key has an identity, the new key will inherit it, allowing you to track usage across both individual keys and the overall identity. **Required Permissions** Your root key must have: - `api.*.create_key` or `api..create_key` - `api.*.encrypt_key` or `api..encrypt_key` (only when the original key is recoverable) # Set key permissions Source: https://unkey.com/docs/api-reference/v2/keys/set-key-permissions https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.setPermissions Replace all permissions on a key with the specified set in a single atomic operation. Use this to synchronize with external systems, reset permissions to a known state, or apply standardized permission templates. Permissions granted through roles remain unchanged. **Important**: Changes take effect immediately with up to 30-second edge propagation. **Required Permissions** Your root key must have one of the following permissions: - `api.*.update_key` (to update keys in any API) - `api..update_key` (to update keys in a specific API) **Side Effects** Invalidates the key cache for immediate effect, and makes permission changes available for verification within 30 seconds across all regions. # Set key roles Source: https://unkey.com/docs/api-reference/v2/keys/set-key-roles https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.setRoles Replace all roles on a key with the specified set in a single atomic operation. Use this to synchronize with external systems, reset roles to a known state, or apply standardized role templates. Direct permissions are never affected. **Important**: Changes take effect immediately with up to 30-second edge propagation. **Required Permissions** Your root key must have one of the following permissions: - `api.*.update_key` (to update keys in any API) - `api..update_key` (to update keys in a specific API) **Side Effects** Invalidates the key cache for immediate effect, and makes role changes available for verification within 30 seconds across all regions. # Update key credits Source: https://unkey.com/docs/api-reference/v2/keys/update-key-credits https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.updateCredits Update credit quotas in response to plan changes, billing cycles, or usage purchases. Use this for user upgrades/downgrades, monthly quota resets, credit purchases, or promotional bonuses. Supports three operations: set, increment, or decrement credits. Set to null for unlimited usage. **Important**: Setting unlimited credits automatically clears existing refill configurations. **Required Permissions** Your root key must have one of the following permissions: - `api.*.update_key` (to update keys in any API) - `api..update_key` (to update keys in a specific API) **Side Effects** Credit updates remove the key from cache immediately. Setting credits to unlimited automatically clears any existing refill settings. Changes take effect instantly but may take up to 30 seconds to propagate to all edge regions. # Update key settings Source: https://unkey.com/docs/api-reference/v2/keys/update-key-settings https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.updateKey Update key properties in response to plan changes, subscription updates, or account status changes. Use this for user upgrades/downgrades, role modifications, or administrative changes. Supports partial updates - only specify fields you want to change. Set fields to null to clear them. **Important**: Permissions and roles are replaced entirely. Use dedicated add/remove endpoints for incremental changes. **Required Permissions** Your root key must have one of the following permissions: - `api.*.update_key` (to update keys in any API) - `api..update_key` (to update keys in a specific API) **Side Effects** If you specify an `externalId` that doesn't exist, a new identity will be automatically created and linked to the key. Permission updates will auto-create any permissions that don't exist in your workspace. Changes take effect immediately but may take up to 30 seconds to propagate to all edge regions due to cache invalidation. # Verify API key Source: https://unkey.com/docs/api-reference/v2/keys/verify-api-key https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/keys.verifyKey Verify an API key's validity and permissions for request authentication. Use this endpoint on every incoming request to your protected resources. It checks key validity, permissions, rate limits, and usage quotas in a single call. **Important**: Always returns HTTP 200. Check the `valid` field in response data to determine if the key is authorized. **Common use cases:** - Authenticate API requests before processing - Enforce permission-based access control - Track usage and apply rate limits **Required Permissions** Your root key needs one of: - `api.*.verify_key` (verify keys in any API) - `api..verify_key` (verify keys in specific API) If you are getting a NOT_FOUND error, ensure your root key has the required verify key permissions. # Overview Source: https://unkey.com/docs/api-reference/v2/overview Our philosophy for building developer-friendly APIs ## Core Principles * **Clear Communication**: Structured responses make success and failure equally informative * **Practical Over Purist**: We make pragmatic choices rather than rigidly adhering to any single paradigm * **Predictable Patterns**: Once you learn one endpoint, you'll understand them all ## Response Structure All API responses follow a consistent structure, making them predictable and easy to parse: ```json { "meta": { "requestId": "req_abc123xyz789" }, "data": { // Operation-specific response data } } ``` For paginated responses, we include a pagination object: ```json { "meta": { "requestId": "req_abc123xyz789", "timestamp": "2023-11-08T15:22:30Z" }, "data": [ // Array of items ], "pagination": { "cursor": "cursor_xyz123", "hasMore": true } } ``` ## Working with the API ### Always Use the Request ID Every response includes a unique `requestId` in the metadata. When seeking support or debugging issues, always include this ID as it allows us to trace exactly what happened with your request. You can also search for the `requestId` yourself in the [logs](https://app.unkey.com/logs). ### Handling Pagination For endpoints that return lists of items: 1. Make your initial request 2. Check for `pagination.hasMore` to see if more items exist 3. If `true`, use the `pagination.cursor` value in your next request Example: ```js // First request const response = await fetch('https://api.unkey.com/v2/keys.listKeys', { method: 'POST', headers: { Authorization: `Bearer ${rootKey}` }, body: JSON.stringify({ apiId: 'api_123' }) }); // Follow-up request with cursor if (response.pagination?.hasMore) { const nextPage = await fetch('https://api.unkey.com/v2/keys.listKeys', { method: 'POST', headers: { Authorization: `Bearer ${rootKey}` }, body: JSON.stringify({ apiId: 'api_123', cursor: response.pagination.cursor }) }); } ``` ## Versioning Our API uses a major version in the URL (e.g., `/v2/`) and maintains backwards compatibility within each major version. When we need to make breaking changes, we increment the major version number. We communicate deprecations well in advance, giving you time to update your integration before endpoints are removed. # Create permission Source: https://unkey.com/docs/api-reference/v2/permissions/create-permission https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/permissions.createPermission Create a new permission to define specific actions or capabilities in your RBAC system. Permissions can be assigned directly to API keys or included in roles. Use hierarchical naming patterns like `documents.read`, `admin.users.delete`, or `billing.invoices.create` for clear organization. **Important:** Permission names must be unique within the workspace. Once created, permissions are immediately available for assignment. **Required Permissions** Your root key must have the following permission: - `rbac.*.create_permission` # Create role Source: https://unkey.com/docs/api-reference/v2/permissions/create-role https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/permissions.createRole Create a new role to group related permissions for easier management. Roles enable consistent permission assignment across multiple API keys. **Important:** Role names must be unique within the workspace. Once created, roles are immediately available for assignment. **Required Permissions** Your root key must have the following permission: - `rbac.*.create_role` # Delete permission Source: https://unkey.com/docs/api-reference/v2/permissions/delete-permission https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/permissions.deletePermission Remove a permission from your workspace. This also removes the permission from all API keys and roles. **Important:** This operation cannot be undone and immediately affects all API keys and roles that had this permission assigned. **Required Permissions** Your root key must have the following permission: - `rbac.*.delete_permission` # Delete role Source: https://unkey.com/docs/api-reference/v2/permissions/delete-role https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/permissions.deleteRole Remove a role from your workspace. This also removes the role from all assigned API keys. **Important:** This operation cannot be undone and immediately affects all API keys that had this role assigned. **Required Permissions** Your root key must have the following permission: - `rbac.*.delete_role` # Get permission Source: https://unkey.com/docs/api-reference/v2/permissions/get-permission https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/permissions.getPermission Retrieve details about a specific permission including its name, description, and metadata. **Required Permissions** Your root key must have the following permission: - `rbac.*.read_permission` # Get role Source: https://unkey.com/docs/api-reference/v2/permissions/get-role https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/permissions.getRole Retrieve details about a specific role including its assigned permissions. **Required Permissions** Your root key must have the following permission: - `rbac.*.read_role` # List permissions Source: https://unkey.com/docs/api-reference/v2/permissions/list-permissions https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/permissions.listPermissions Retrieve all permissions in your workspace. Results are paginated and sorted by their id. **Required Permissions** Your root key must have the following permission: - `rbac.*.read_permission` # List roles Source: https://unkey.com/docs/api-reference/v2/permissions/list-roles https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/permissions.listRoles Retrieve all roles in your workspace including their assigned permissions. Results are paginated and sorted by their id. **Required Permissions** Your root key must have the following permission: - `rbac.*.read_role` # Apply rate limiting Source: https://unkey.com/docs/api-reference/v2/ratelimit/apply-rate-limiting https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/ratelimit.limit Check and enforce rate limits for any identifier (user ID, IP address, API client, etc.). Use this for rate limiting beyond API keys - limit users by ID, IPs by address, or any custom identifier. Supports namespace organization, variable costs, and custom overrides. **Response Codes**: Rate limit checks return HTTP 200 regardless of whether the limit is exceeded - check the `success` field in the response to determine if the request should be allowed. 4xx responses indicate auth, namespace existence/deletion, or validation errors (e.g., 410 Gone for deleted namespaces). 5xx responses indicate server errors. **Required Permissions** Your root key must have one of the following permissions: - `ratelimit.*.limit` (to check limits in any namespace) - `ratelimit..limit` (to check limits in a specific namespace) **Side Effects** Records rate limit metrics for analytics and monitoring, updates rate limit counters with sliding window algorithm, and optionally triggers override matching for custom limits. # Delete ratelimit override Source: https://unkey.com/docs/api-reference/v2/ratelimit/delete-ratelimit-override https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/ratelimit.deleteOverride Permanently remove a rate limit override. Affected identifiers immediately revert to the namespace default. Use this to remove temporary overrides, reset identifiers to standard limits, or clean up outdated rules. **Important:** Deletion is immediate and permanent. The override cannot be recovered and must be recreated if needed again. **Permissions:** Requires `ratelimit.*.delete_override` or `ratelimit..delete_override` # Get ratelimit override Source: https://unkey.com/docs/api-reference/v2/ratelimit/get-ratelimit-override https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/ratelimit.getOverride Retrieve the configuration of a specific rate limit override by its identifier. Use this to inspect override configurations, audit rate limiting policies, or debug rate limiting behavior. **Important:** The identifier must match exactly as specified when creating the override, including wildcard patterns. **Permissions:** Requires `ratelimit.*.read_override` or `ratelimit..read_override` # List ratelimit overrides Source: https://unkey.com/docs/api-reference/v2/ratelimit/list-ratelimit-overrides https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/ratelimit.listOverrides Retrieve a paginated list of all rate limit overrides in a namespace. Use this to audit rate limiting policies, build admin dashboards, or manage override configurations. **Important:** Results are paginated. Use the cursor parameter to retrieve additional pages when more results are available. **Permissions:** Requires `ratelimit.*.read_override` or `ratelimit..read_override` # Set ratelimit override Source: https://unkey.com/docs/api-reference/v2/ratelimit/set-ratelimit-override https://spec.speakeasy.com/unkey/unkey/openapi-json-with-code-samples post /v2/ratelimit.setOverride Create or update a custom rate limit for specific identifiers, bypassing the namespace default. Use this to create premium tiers with higher limits, apply stricter limits to specific users, or implement emergency throttling. **Important:** Overrides take effect immediately and completely replace the default limit for matching identifiers. Use wildcard patterns (e.g., `premium_*`) to match multiple identifiers. **Permissions:** Requires `ratelimit.*.set_override` or `ratelimit..set_override` # RPC-Style API Source: https://unkey.com/docs/api-reference/v2/rpc Understanding our action-oriented API design We use an RPC (Remote Procedure Call) style API that focuses on *actions* rather than resources. This means endpoints represent specific operations: ``` https://api.unkey.com/v2/{service}.{procedure} ``` For example: * `POST /v2/keys.createKey` - Create a new API key * `POST /v2/ratelimit.limit` - Check or enforce a rate limit We chose this approach because it maps directly to the operations developers want to perform, making the API intuitive to use. ## HTTP Methods We exclusively use POST for all operations. While this deviates from REST conventions, it provides several advantages: 1. **Consistent Request Pattern**: All requests follow the same pattern regardless of operation 2. **Rich Query Parameters**: Complex filtering and querying without URL length limitations 3. **Security and Compatibility**: Avoids issues with proxies or firewalls logging potentially sensitive parameters in the url ## Request Format All requests should: * Use the POST HTTP method * Include a Content-Type header set to application/json * Include an Authorization header (see Authentication documentation) * Send parameters as a JSON object in the request body Example: ```bash curl -X POST "https://api.unkey.com/v2/keys.createKey" \ -H "Authorization: Bearer root_1234567890" \ -H "Content-Type: application/json" \ -d '{ "apiId": "api_1234", "name": "Production API Key" }' ``` ## Service Namespaces Our API is organized into logical service namespaces that group related procedures: * **keys** - API key management (create, verify, revoke) * **apis** - API configuration and settings * **ratelimit** - Rate limiting services * **analytics** - Usage and performance data * **identities** - Identity management * **permissions** - Permission management Each namespace contains multiple procedures that perform specific actions within that domain. ## Benefits of RPC Design We believe our RPC-style approach offers significant benefits: 1. **Clarity of Intent**: Endpoint names clearly communicate the action being performed 2. **Natural Code Mapping**: Endpoints naturally map to code and user intent (`keys.createKey()` instead of `POST /keys`) 3. **Complex Operations**: Supports complex operations that don't map well to REST's resource model 4. **Flexibility**: Allows for more flexible request structures without being constrained by URL parameters # Analytics Source: https://unkey.com/docs/apis/features/analytics Per key and per API analytics Unkey offers both per key and per API analytics that allow you to drive business decisions. ## Per API Analytics Our per API analytics offer a broad overview of the usage for a specific API with total keys, active keys and verifications in the last 30 days. Per API Analytics ## Per Key Analytics Our per key analytics give you a deep dive into each individual key, giving usage data, key data and where the requests originated from. This data can be useful for finding your top users, and where verifications are coming from. Per key analytics ## Tags You can add tags to verification requests to aggregate or filter data when querying. For example you might want to add the path of your API as a tag: `path=/v1/my-path` and then later retrieve a breakdown of api key usage per unique path. Unkey does not parse tags in any special way. The only limitations are: * Tags are strings. * You can specify up to 10 tags per request. * Each tag must be between (including) 1 and 128 characters. That being said, having some structure for your tags could be benefitial for you. A common theme is treating them as key-value pairs and specifying them with either a colon or equal-sign as delimiter. ``` key:value key=value ``` Since Unkey does not know what API routes your user has called or which resources they interact with, we encourage you to record these in the tags. Here's an example tags for a fictional blog API: ```json [ "path=/v1/posts.createPost", "postId=post_1asofijqknslkfqWF", "region=us-east-1", "apiVersion=f8ad21bd", // a git sha of your deployment or semver ] ``` ### Using tags Tags can be added in the request body. ```bash Providing Tags {6-9} curl --request POST \ --url https://api.unkey.com/v2/keys.verifyKey \ --header 'Content-Type: application/json' \ --data '{ "key": "sk_1234", "tags": [ "tag1", "path=/v1/my-resource/123" ] }' ``` ### Querying tags We have only rolled out tag ingestion so far to allow you to start recording data as early as possible. We're working on new query capabilities including filtering and aggregating by tags. # Example Source: https://unkey.com/docs/apis/features/authorization/example RBAC in the almost-real world Let's look at an example app for allowing your users to manage domains. As part of the API, your users will be able to perform CRUD operations against domains or individual dns records. Users of our app can have the following permissions: * `domain.delete_domain` * `domain.dns.create_record` * `domain.dns.read_record` * `domain.dns.update_record` * `domain.dns.delete_record` * `domain.create_domain` * `domain.read_domain` * `domain.update_domain` Create them in your [dashboard](https://app.unkey.com/authorization/permissions). Example permissions And we define the following roles: * `admin`: An admin can do everything * `dns.manager`: Can create, read, update and delete dns records but not access the domain itself * `read-only`: Can read domain or dns record information. Example roles Create them in your [dashboard](https://app.unkey.com/authorization/roles) too. For each role, we need to connect the permissions it should have. Go to [/app/authorization/roles](https://app.unkey.com/authorization/roles) and click on the role to go to the permissions screen. Admin roles dns.manager roles read-only roles Now that we have permissions and roles in place, we can connect them to keys. 1. In the sidebar, click on one of your APIs 2. In the breakcrumb navigation on the top click Reqests and then keys Breadcrumb Navigation 3. Select one of your existing keys by clicking on it 4. Scroll down to the `Roles` section if not visible. You should now be on `/app/keys/key_auth_???/key_???` Unconnected roles and permissions You can connect a role to your key by clicking on the checkbox. Let's give this key the `dns.manager` and `read-only` roles. A toast message should come up in the lower corner when the action has been completed. Unconnected roles and permissions As you can see, now the key now contains 2 `roles` and 5 `permissions` shown just above the Roles section: You can attach roles to a key when creating it by providing the role names as an array: ```bash curl -XPOST \ --url https://api.unkey.com/v2/keys.createKey \ -H "Authorization: Bearer ${ROOT_KEY}" \ -H "Content-Type: application/json" \ -d '{ "apiId": "${API_ID}", "roles": [ "role1", "role2", "role3" ] }' ``` See [here](/api-reference/v2/keys/create-api-key) for details. Now you can verify this key and perform permission checks. [Read more](/apis/features/authorization/verifying) # Overview Source: https://unkey.com/docs/apis/features/authorization/introduction Access Control with Roles and Permissions Role-Based Access Control (RBAC) is a security paradigm that restricts system access to authorized actors. It is based on the principle of assigning roles to actors and defining what actions or resources each role can access. We are taking this one step further and allowing you to attach arbitrary permissions to keys, for more flexibility (Coming in Q2). # Roles and Permissions Source: https://unkey.com/docs/apis/features/authorization/roles-and-permissions In RBAC, roles represent a collection of permissions. Each role defines a set of actions or operations that a user with that role can perform. Permissions can be associated with various resources within your application, such as endpoints, data objects, or functionality. Common roles may include: * `Administrator`: Has full access to all resources and functionality. * `Editor`: Can create, read, update, and delete specific resources. * `Viewer`: Can only view resources but cannot modify them. ## Roles Creating, updating and deleting roles is available in the dashboard. ### Create 1. Go to [/app/authorization/roles](https://app.unkey.com/authorization/roles) 2. Click `Create New Role` 3. Enter a unique name for your role and optionally a human readable description. 4. Click `Create` After the role is created, you are forwarded and can update/delete the role or connect existing permissions. ### Update 1. Go to [/app/authorization/roles](https://app.unkey.com/authorization/roles) 2. Click on the role you want to update 3. Click `Update Role` 4. Make changes to the name, description or both 5. Click `Save` ### Delete 1. Go to [/app/authorization/roles](https://app.unkey.com/authorization/roles) 2. Click on the role you want to delete 3. Click `Delete Role` 4. Enter the name of the role to confirm 5. Click `Delete Role` ## Permissions Creating, updating and deleting permissions is available in the dashboard. ### Create 1. Go to [/app/authorization/permissions](https://app.unkey.com/authorization/permissions) 2. Click `Create New Permission` 3. Enter a unique name for your permissoin and optionally a human readable description. 4. Click `Create New Permission` ### Update 1. Go to [/app/authorization/permissions](https://app.unkey.com/authorization/permissions) 2. Click on the permission you want to update 3. Click `Update Role` 4. Make changes to the name, description or both 5. Click `Save` ### Delete 1. Go to [/app/authorization/permisions](https://app.unkey.com/authorization/permisions) 2. Click on the permission you want to delete 3. Click `Delete` 4. Enter the name of the permission to confirm 5. Click `Delete` ## Connecting roles and permissions After you have created at least 1 role and 1 permission, you can start associating them with each other. Go to [/app/authorization/roles](https://app.unkey.com/authorization/roles) and click on the role to go to the permissions screen. Now you can click the checkboxes to connect the role and permission. A checked box means the role will grant the permission to keys. Read-only roles ## Connecting roles to keys 1. In the sidebar, click on one of your APIs 2. In the breakcrumb navigation on the top click Reqests and then keys Breadcrumb Navigation 3. Select one of your existing keys by clicking on it 4. Scroll down to the `Roles` section if not visible You should now be on `/app/keys/key_auth_???/key_???` Unconnected roles and permissions You can connect a role to your key by clicking on the checkbox. Let's give this key the `dns.manager` and `read-only` roles. A toast message should come up in the lower corner when the action has been completed. Unconnected roles and permissions As you can see, now the key now contains 2 `roles` and 5 `permissions` shown just above the Roles section: ## Creating keys When a user of your app creates a new key, you can attach zero, one or multiple previously created roles to the key. ```bash curl -XPOST \ --url https://api.unkey.com/v2/keys.createKey \ -H "Authorization: Bearer ${ROOT_KEY}" \ -H "Content-Type: application/json" \ -d '{ "apiId": "${API_ID}", "roles": [ "role1", "role2", "role3" ] }' ``` See [here](/api-reference/keys/create) for details. # Verifying Source: https://unkey.com/docs/apis/features/authorization/verifying Verifying permissions through the API Once a key is generated, you can verify it using the [verify](https://www.unkey.com/docs/api-reference/v2/keys/verify-api-key) endpoint. Our system verifies whether the key has the necessary permissions to perform the requested action(s). If the user's role grants the required permissions, the request is allowed to proceed; otherwise, access is denied. This will return valid if the key has the permission: `admin` ```bash Single Permission curl --request POST \ --url https://api.unkey.com/v2/keys.verifyKey \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "key": "sk_1234", "permissions": "admin" }' ``` This will return valid if the key has either `admin` or both `dns.record.read` and `dns.record.update` permissions. ```bash Nested Query curl --request POST \ --url https://api.unkey.com/v2/keys.verifyKey \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "key": "sk_1234", "permissions": "admin OR (dns.record.read OR dns.record.update)" }' ``` Sometimes you just don't know what permissions are required before loading resources from your database. In these cases you can manually check permissions as well. Verify the key and all permissions that you already know before querying your database. If the response is invalid, you can return early. The key is at least valid, so you can query our database to fetch more information. The verification response from step 1 includes all permissions attached to the keys and looks something like this: ```json { "meta": { "requestId": "req_123" }, "data": { "valid": true, "permissions": ["admin"] // rest omited for brevity } } ``` Use the attached permissions and the context loaded from your database to determine if you should proceed handling the request or returning an authorization error. # Disabling Keys Source: https://unkey.com/docs/apis/features/enabled Enable or disable a key. Disabled keys will not validate. This feature is useful for disabling a key temporarily. While disabled the key will act as an invalid key. Example: Suppose you have a customer that has not paid their bill. You may not want to delete the key and wait for the account balance to be current. The key can be disabled temporarily, preventing access until it is enabled. Unkey allows you to disable a key on an individual basis. ### Example Let's disable a key temperarily blocking access with that key. ```bash curl --request POST \ --url https://api.unkey.com/v2/keys.updateKey \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "keyId": "", "enabled": false }' ``` Now, when you verify the updated key it will show as invalid. ```bash curl --request POST \ --url https://api.unkey.com/v2/keys.verifyKey \ --header 'Content-Type: application/json' \ --data '{ "key": "" }' ``` ```json { "meta": { "requestId": "req_abc123def456" }, "data": { "valid": false, "keyId": "", "enabled": false, "code": "DISABLED" } } ``` The returned `enabled` value can be changed with the updateKey endpoint to re-enable the key. # Environments Source: https://unkey.com/docs/apis/features/environments Separate your keys into live and test environments. For many applications it's useful to provide keys for developing and testing to your users. They can not interact with real production resources and may have lower ratelimits but use the exact same flow as your `live` keys. Unkey allows you to set an arbitrary metadata for each key, so you can model your domain however you want. In practice we often see two distinct environments being used a lot: * `live` Live keys are used in production and affect real resources. * `test` Test keys allow you to develop and test your code without modifying real resources. It's great to ensure your code is working manually or can be used in automated tests. You may also want to associate different ratelimits per environment or bill usage differently. Perhaps test keys have lower limits but are free to use. ### Creating a key with an environment Using the prefix to indicate the environment to the user is optional but highly recommended. It can prevent your user from accidentally using keys interchangably and modifying resources unintentionally. ```bash Test curl --request POST \ --url https://api.unkey.com/v2/keys.createKey \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "apiId": "", "prefix": "sk_test", "meta": { "environment": "test" } }' ``` ```bash Live curl --request POST \ --url https://api.unkey.com/v2/keys.createKey \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "apiId": "", "prefix": "sk_live", "meta": { "environment": "production" } }' ``` For more details, see the [API reference](/api-reference/v2/keys/create-api-key) ### Verifying a key When you are using different environments, you obviously need a way to know what environment the used key is in. We provide this as part of the verification response: ```bash curl --request POST \ --url https://api.unkey.com/v2/keys.verifyKey \ --header 'Content-Type: application/json' \ --data '{ "key": "sk_live_1234" }' ``` The response would look something like this: ```json { "meta": { "requestId": "req_abc123def456" }, "data": { "valid": true, "code": "VALID", "keyId": "", "meta": { "environment": "production" } // ... other fields omitted } } ``` # Overview Source: https://unkey.com/docs/apis/features/ratelimiting/overview How rate limiting works in unkey Unkey's ratelimiting system controls the number of requests a key can make within a given timeframe. This prevents abuse, protects your API from being overwhelmed, and enables usage-based billing models. ## Multi-Ratelimit System Unkey supports multiple named ratelimits per key, providing fine-grained control over different aspects of your API usage. You can define separate limits for different operations, time windows, and use cases within a single key. The system offers two types of ratelimits: auto-apply ratelimits that check automatically during verification, and manual ratelimits that require explicit specification in requests. ## Auto-Apply vs Manual Ratelimits Auto-apply ratelimits (`autoApply: true`) are checked automatically during every key verification without needing explicit specification. These work well for general usage limits that should always be enforced. ```bash # Create a key with auto-apply ratelimit curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "apiId": "api_123", "ratelimits": [ { "name": "general-requests", "limit": 100, "duration": 60000, "autoApply": true } ] }' ``` ```bash # Verify key - auto-apply ratelimits are checked automatically curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "key": "your_api_key_here" }' ``` Manual ratelimits (`autoApply: false`) must be explicitly specified in verification requests. Use these for operation-specific limits that only apply to certain endpoints. ```bash # Create a key with manual ratelimit curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "apiId": "api_123", "ratelimits": [ { "name": "expensive-operations", "limit": 5, "duration": 60000 } ] }' ``` ```bash # Verify key - override cost explicitly curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "key": "your_api_key_here", "ratelimits": [ { "name": "expensive-operations", "cost": 2 } ] }' ``` ## Configuration Configure ratelimits on a per-key or per-identity basis through the dashboard or API. Each ratelimit requires a unique name, limit count, and duration in milliseconds. ```bash curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "apiId": "api_123", "ratelimits": [ { "name": "requests", "limit": 100, "duration": 60000, "autoApply": true }, { "name": "daily-quota", "limit": 1000, "duration": 86400000, "autoApply": true } ] }' ``` You can apply different costs to operations by specifying a cost parameter during verification. This allows resource-intensive operations to consume more of the rate limit than simple operations. ## Migration from Legacy System The legacy `ratelimit` field is deprecated. Use the `ratelimits` array instead. Existing keys with the legacy `ratelimit` field are automatically migrated to use a special "default" ratelimit with `autoApply: true`. This maintains backward compatibility without requiring code changes. The legacy format converts automatically: ```bash # Legacy format (deprecated) curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "apiId": "api_123", "ratelimit": { "limit": 100, "duration": 60000 } }' # Automatically becomes curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "apiId": "api_123", "ratelimits": [ { "name": "default", "limit": 100, "duration": 60000, "autoApply": true } ] }' ``` Remove the automatically migrated "default" ratelimit through the key settings in the dashboard or via the updateKey API endpoint if you no longer need it. ## Identity-Level Ratelimits Configure ratelimits at the identity level to share limits across multiple keys. Identity ratelimits work alongside key-level ratelimits, with key-level settings taking precedence for naming conflicts. ```bash curl -X POST https://api.unkey.com/v2/identities.createIdentity \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "externalId": "user_123", "ratelimits": [ { "name": "user-requests", "limit": 1000, "duration": 3600000 } ] }' ``` This approach works well for user-based quotas where multiple API keys should share the same usage limits. # Refill Source: https://unkey.com/docs/apis/features/refill Refill remaining key verifications on a set interval This feature is useful for creating an API key that automatically refills daily or monthly. Daily refills trigger each night at midnight, while monthly refills are triggered on the first day of each month. Example: Suppose you are selling API access, and a customer has purchased 1000 API uses per month. On the first day of the month, the 1000 uses will be refilled for that key. Unkey allows you to set a refill interval and increment on individual keys, and we take care of refilling a key on its set interval. ### Example Let's create a new key that can be used 100 times a day. ```bash curl --request POST \ --url https://api.unkey.com/v2/keys.createKey \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "apiId": "", "credits": { "remaining": 100, "refill": { "interval": "daily", "amount": 100 } } }' ``` Now, when you verify the new key, you will receive the remaining verifications. After all of them are used up, the key becomes invalid. ```bash curl --request POST \ --url https://api.unkey.com/v2/keys.verifyKey \ --header 'Content-Type: application/json' \ --data '{ "key": "", "credits": { "cost": 1 } }' ``` ```json { "meta": { "requestId": "req_abc123def456" }, "data": { "valid": true, "code": "VALID", "keyId": "", "credits": 99 } } ``` The returned `credits` value represents how many verifications are remaining after the current one. A value of 3, means you can verify the key successfully 3 more times. # Usage limited keys Source: https://unkey.com/docs/apis/features/remaining Limiting the usage of keys Sometimes you would like to create an api key and automatically invalidate it after a certain number of uses. Example: You are selling API access and a customer has purchased 1000 api requests from you. Unkey allows you to set/update usage limits on individual keys and we take care of invalidating a key after it has reached its limit. ### Example Let's create a new key, which can be used 100 times. ```bash curl --request POST \ --url https://api.unkey.com/v2/keys.createKey \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "apiId": "", "credits": { "remaining": 100 } }' ``` Now when you verify the new key, you will receive back the remaining verifications and after all of them are used up, the key is invalid. ```bash curl --request POST \ --url https://api.unkey.com/v2/keys.verifyKey \ --header 'Content-Type: application/json' \ --data '{ "key": "" }' ``` ```json { "meta": { "requestId": "req_abc123def456" }, "data": { "valid": true, "code": "VALID", "keyId": "", "credits": 99 } } ``` The returned `credits` value represents how many verifications are remaining after the current one. A value of 3, means you can verify the key successfully 3 more times. ## Custom cost By default we deduct `1` from the remaining verifications, but in some cases you need to deduct more. You can override this in the request body. In this example unkey would deduct `4` for every valid verification. If a key would only have 3 remaining, the request would be rejected, as there is not enough remaining left. There is a special case when you set `cost = 0`. In this case, the request will always be valid, but will not deduct anything. ```bash {6-8} curl --request POST \ --url https://api.unkey.com/v2/keys.verifyKey \ --header 'Content-Type: application/json' \ --data '{ "key": "", "credits": { "cost": 4 } }' ``` # Key Revocation Source: https://unkey.com/docs/apis/features/revocation Keys can be revoked at any time, from the API or the dashboard. In the event that a key is compromised, you can revoke it at any time. Once the key is revoked, it can take up to 60 seconds for the key to be invalidated. Once invalidated, the key will no longer be able to be used to authenticate requests. ## Delete a Key To permanently delete a key, use the `/v2/keys.deleteKey` endpoint: ```bash curl --request POST \ --url https://api.unkey.com/v2/keys.deleteKey \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "keyId": "" }' ``` This will immediately invalidate the key and remove it from your account. ## Disable a Key Temporarily If you want to temporarily disable a key without deleting it, use the `/v2/keys.updateKey` endpoint: ```bash curl --request POST \ --url https://api.unkey.com/v2/keys.updateKey \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "keyId": "", "enabled": false }' ``` This keeps the key in your system but prevents it from being used for authentication. You can re-enable it later by setting `enabled: true`. # Temporary Keys Source: https://unkey.com/docs/apis/features/temp-keys How to create temporary API Keys in Unkey ## What are temporary API Keys? Temporary API Keys are API Keys that expire after a certain amount of time. They are useful for when you want to give a user access to your API for a limited amount of time. For example, you might want to give a user access to your API for 1 hour, or 1 day, or 1 week. You can do this by creating a temporary API Key that expires after a certain amount of time. ## How to create a temporary API Key To create a temporary API Key, you need to make a `POST` request to the `/v2/keys.createKey` endpoint. You can read about all the parameters you can send in the [API Reference](/api-reference/keys/create). ```bash curl --request POST \ --url https://api.unkey.com/v2/keys.createKey \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "apiId":"", "prefix":"xyz", "byteLength":16, "externalId":"USER_ID", "expires": 1718718673000 }' ``` Once the key is created, you can send it to your user. They can then use it to access your API. Once the key expires, they will no longer be able to access your API and the key will be deleted. # IP Whitelisting Source: https://unkey.com/docs/apis/features/whitelist Unkey offers IP whitelisting to restrict requests to a specific set of IP addresses. This is useful for restricting access to your API to a specific set of IP addresses, such as your own servers or a set of trusted partners. This feature is available as an addon or with an Enterprise plan. IP Whitelist example # Overview Source: https://unkey.com/docs/apis/introduction Protect your public APIs Unkey provides a simple feature rich API key management system. You can use Unkey to protect your public APIs with ease. Below is an example of implementing Unkey in your API. ```ts Typescript import { verifyKey } from '@unkey/api'; const { result, error } = await verifyKey({ apiId: "api_123", key: "xyz_123" }) if ( error ) { // handle network error } if ( !result.valid ) { // reject unauthorized request } ``` ```py Python import asyncio import os import unkey async def main() -> None: client = unkey.Client(api_key=os.environ["API_KEY"]) await client.start() result = await client.keys.verify_key("prefix_abc123") if result.is_ok: print(data.valid) else: print(result.unwrap_err()) ``` ```go Golang package main import( "context" unkeygo "github.com/unkeyed/unkey-go" "github.com/unkeyed/unkey-go/models/components" "log" ) func main() { ctx := context.Background() s := unkeygo.New( unkeygo.WithSecurity("UNKEY_ROOT_KEY"), ) res, err := s.Keys.VerifyKey(ctx, components.V1KeysVerifyKeyRequest{ APIID: unkeygo.String("api_1234"), Key: "sk_1234", }) if err != nil { log.Fatal(err) } if res.V1KeysVerifyKeyResponse != nil { // handle response } } ``` ```bash cURL curl --request POST \ --url https://api.unkey.com/v2/keys.verifyKey \ --header 'Content-Type: application/json' \ --data '{ "key": "sk_1234" }' ``` ## Features Below are some of the key features of Unkey API key management system, for you to explore. Key based ratelimiting Set usage limits per key Keys that expire after a set time Refill your remaining keys on a set schedule Rich analytics on your API and keys Separate your keys into live and test environments Access Control with Roles and Permissions # Overview Source: https://unkey.com/docs/audit-log/introduction Audit logs for your workspace, allowing you to see the history of all the resource requests made inside your workspace. We automatically capture all mutations, including key creation, permission changes and configuration changes. ## Accessing Audit Logs To access the Audit Logs, click [here](https://app.unkey.com/audit) or on the **Audit Logs** option in the left navigation bar. # Audit logs in detail The audit logs table displays the following fields: * **Actor**: who was responsible for the change. This can be either a user (for actions taken in the web app) or a key (for actions taken via the API). * **Event**: the type of change. You can filter by type of events using the UI. Click to expand for more detail. * **Location**: the IP address of the actor who made the change. # Feature requests Need additional functionality from audit logs, beyond what is provided currently? [Get in touch](mailto:support@unkey.dev)! # Event Types Source: https://unkey.com/docs/audit-log/types Available audit log event types ## Workspaces Workspace actions that will create a new audit log item. * `workspace.create` - A workspace is created. * `workspace.update` - A workspace is updated. ## API API actions that will create a new audit log item. * `api.create` - An API is created. * `api.update` - An API is updated. * `api.delete` - An API is deleted. ## Keys Key actions that will create a new audit log item. * `key.create` - A key is created. * `key.update` - A key is updated. * `key.delete` - A key is deleted. ## Ratelimit Ratelimit actions that will create a new audit log item. ### Ratelimit Namespace * `ratelimitNamespace.update` - A ratelimit namespace is updated. * `ratelimitNamespace.update` - A ratelimit namespace is updated. * `ratelimitNamespace.delete` - A ratelimit namespace is deleted. ### Ratelimit Override * `ratelimitOverride.create` - A ratelimit override is created. * `ratelimitOverride.update` - A ratelimit override is updated. ## Role Role actions that will create a new audit log item. * `role.create` - A role is created. * `role.update` - A role is updated. * `role.delete` - A role is deleted. ## Permission Permission actions that will create a new audit log item. * `permission.create` - A permission is created. * `permission.update` - A permission is updated. * `permission.delete` - A permission is deleted. ## Authorization Authorization actions that will create a new audit log item. * `authorization.connect_role_and_permission` - A role and permission are connected * `authorization.disconnect_role_and_permissions` - A role and permission are disconnected. * `authorization.connect_role_and_key` - A role and key are connected. * `authorization.disconnect_role_and_key` - A role and key are disconnected. * `authorization.connect_permission_and_key` - A permission and key are connected. * `authorization.disconnect_permission_and_key` - A permission and key are disconnected. # Overview Source: https://unkey.com/docs/concepts/identities/overview Identities are a representations of a user, an org or a machine in your application. Identities are in in public beta, please report any issues you encounter: [support](mailto:support@unkey.dev). Identities are used to map your users, organizations, or even machine-users in Unkey and allow you to share metadata and configuration across multiple keys. An identity is directly linked via a unique identifier from your system and you can associate as many keys as you want with an identity. ## Use cases ### Multiple keys for the same user It is very common to issue multiple keys for the same user, but managing metadata and configuration for each key can be cumbersome and error-prone. By associating all keys with the same identity, you can share metadata and configuration across all keys. Instead of updating each key, you can update the identity and all keys associated with it will automatically get the updated metadata and configuration. ### Sharing ratelimits across multiple keys If you have multiple keys for the same user, you can set ratelimits for the identity and share it across all keys. This way, you can ensure that the user does not exceed the ratelimits across all of their keys. [Read more](/concepts/identities/ratelimits) about how to share ratelimits across multiple keys. ### Aggregated analytics If you are building a multi-tenant product, you can associate all keys of an organization with the organization's identity. This way, you can aggregate analytics across all keys of the organization and provide insights to the organization admin or drive your billing. ## Data model The unique identifier of the identity in Unkey. You may use this to reference the identity in other API calls. The id of the identity in your system. This is used to link the identity in Unkey to your system. For example your user id, or organization id if you are linking an organization. If you are using a 3rd party auth provider like Clerk, WorkOS, or Auth0, they will provide you a unique id for users and organizations. This is what you should use as the externalId. We want to build deeper integrations with them, so if you are using one of these, please let us know so we can prioritize it. A JSON object that can store any additional information about the identity. We do not make assumptions about this data, it's entirely up to you to decide what to store here. # Ratelimits Source: https://unkey.com/docs/concepts/identities/ratelimits Identities can be used to share ratelimits across multiple keys Identities are in in public beta, please report any issues you encounter: [support](mailto:support@unkey.dev). ## Sharing ratelimits Identities allow you to share ratelimits across multiple keys. Let's say you want to limit a specific user to 100 requests per second: If you just set each key to `100 RPS` then the user can make `100 * number of keys` requests per second, which probably isn't what you want. Of course you could limit the number of keys a user can have or do some math to divide 100 by the number of keys they have, but that's not fixing the actual problem, it's just a workaround. Instead, you should create an identity for the user, set the ratelimit on the identity to `100 RPS` and then associate all keys of the user with the identity. This way, the user can only make `100 RPS` across all of their keys. ## Multiple ratelimits You can set multiple ratelimits for an identity and check against some or all of them when verifying a key. Let's say you are building an app that uses AI under the hood and you want to limit your customers to 500 requests per hour, but also ensure that they don't blow up your bill by using too many tokens. In this case you would create an identity for the user and then create two ratelimits: 1. `{name: "requests", limit: 500, duration: 3600000}` -> 500 requests per hour 2. `{name: "tokens", limit: 20000, duration: 86400000}` -> 20k tokens per day Now if either one of those limits is exceeded, the request is denied. API reference for verifying: [/api-reference/keys/verify](https://www.unkey.com/docs/api-reference/keys/verify) API reference for creating identities: [/api-reference/identities/create-identity](https://www.unkey.com/docs/api-reference/identities/create-identity) # Overview Source: https://unkey.com/docs/errors/overview Understanding Unkey's structured error system These errors are only for the v2 API, which is not yet GA. ## Introduction Unkey's error system uses a structured approach to organize and identify errors across the platform. This system makes it easier to understand, debug, and handle errors consistently. ## Error Code Format All Unkey error codes follow a consistent URN-like format: ``` err:system:category:specific ``` For example: `err:unkey:authentication:missing` This format breaks down as follows: * **err**: Standard prefix for all error codes * **system**: The service area or responsibility domain (e.g., unkey, user) * **category**: The error type or classification (e.g., authentication, data) * **specific**: The exact error condition (e.g., missing, malformed) ## Systems The "system" component identifies where the error originated: * **unkey**: Errors originating from Unkey's internal systems * **github**: Errors related to GitHub integration * **aws**: Errors related to AWS integration ## Categories The "category" component provides a second level of classification, for example: * **authentication**: Errors related to the authentication process * **authorization**: Errors related to permissions and access control * **application**: Errors related to application operations and system integrity * **data**: Errors related to data operations and resources * **limits**: Rate limiting or quota-related errors ## Error Response Format When an error occurs, the API returns a consistent JSON response format: ```json { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "Authentication credentials were not provided", "status": 401, "title": "Unauthorized", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authentication/missing" } } ``` Key fields: * **requestId**: Unique identifier for the request (important for support) * **detail**: Human-readable explanation of the error * **status**: HTTP status code * **title**: Short summary of the error type * **type**: URL to detailed documentation about this error ## Documentation Integration All error codes have a corresponding documentation page accessible via the `type` URL in the error response. These pages provide detailed information about: * What caused the error * How to fix the issue * Common mistakes that lead to this error * Related errors you might encounter # assertion_failed Source: https://unkey.com/docs/errors/unkey/application/assertion_failed A runtime assertion or invariant check failed `err:unkey:application:invalid_input` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "A system integrity check failed while processing your request", "status": 500, "title": "Internal Server Error", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/application/assertion_failed" } } ``` ## What Happened? This error occurs when Unkey's internal system detects an inconsistency or violation of its expected invariants during the processing of your request. Unlike validation errors which occur when your input is invalid, assertion failures happen when the system's internal state doesn't match what was expected. Possible causes include: * Data corruption or inconsistency in Unkey's database * Bugs in Unkey's business logic * Race conditions or timing issues * System state that violates core assumptions * Incompatible changes between different parts of the system This type of error is generally not caused by anything you did wrong in your request, but rather indicates an internal issue with Unkey's system integrity. ## How To Fix Since this is an internal system error, there's usually nothing you can directly do to fix it. However, you can try the following: 1. **Retry the request**: Some assertion failures may be due to temporary conditions that resolve themselves 2. **Contact Unkey support**: Report the error with the request ID to help Unkey address the underlying issue 3. **Check for workarounds**: In some cases, using a different API endpoint or approach might avoid the issue When contacting support, be sure to include: * The full error response, including the request ID * The API endpoint you were calling * The request payload (with sensitive information redacted) * Any patterns you've noticed (e.g., if it happens consistently or intermittently) ## Important Notes * Assertion failures indicate bugs or data integrity issues that Unkey needs to fix * Unlike many other errors, changing your request is unlikely to resolve the issue * These errors are typically logged and monitored by Unkey's engineering team * If you encounter this error consistently, there may be an underlying issue with your account data ## 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 # invalid_input Source: https://unkey.com/docs/errors/unkey/application/invalid_input Client provided input that failed validation `err:unkey:application:invalid_input` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The request contains invalid input that failed validation", "status": 400, "title": "Bad Request", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/application/invalid_input", "errors": [ { "location": "body.limit", "message": "must be greater than or equal to 1", "fix": "Provide a limit value of at least 1" } ] } } ``` ## What Happened? This error occurs when your request contains input data that doesn't meet Unkey's validation requirements. This could be due to missing required fields, values that are out of allowed ranges, incorrectly formatted data, or other validation failures. Common validation issues include: * Missing required fields * Values that exceed minimum or maximum limits * Strings that don't match required patterns * Invalid formats for IDs, emails, or other structured data * Type mismatches (e.g., providing a string where a number is expected) Here's an example of a request that would trigger this error: ```bash # Attempting to create a rate limit with an invalid limit value of 0 curl -X POST https://api.unkey.com/v2/ratelimit.limit \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "namespace": "api.requests", "identifier": "user_123", "limit": 0, "duration": 60000 }' ``` ## How To Fix To fix this error, carefully review the error details provided in the response. The `errors` array contains specific information about what failed validation: 1. Check the `location` field to identify which part of your request is problematic 2. Read the `message` field for details about why validation failed 3. Look at the `fix` field (if available) for guidance on how to correct the issue 4. Modify your request to comply with the validation requirements Here's the corrected version of our example request: ```bash # Corrected request with a valid limit value curl -X POST https://api.unkey.com/v2/ratelimit.limit \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "namespace": "api.requests", "identifier": "user_123", "limit": 100, "duration": 60000 }' ``` ## Common Mistakes * **Ignoring schema requirements**: Not checking the API documentation for field requirements * **Range violations**: Providing values outside of allowed ranges (too small, too large) * **Format errors**: Not following the required format for IDs, emails, or other structured data * **Missing fields**: Omitting required fields in API requests * **Type errors**: Sending the wrong data type (e.g., string instead of number) ## Related Errors * [err:unkey:application:assertion\_failed](./assertion_failed) - When a runtime assertion or invariant check fails * [err:unkey:application:protected\_resource](./protected_resource) - When attempting to modify a protected resource # protected_resource Source: https://unkey.com/docs/errors/unkey/application/protected_resource Attempt to modify a protected resource `err:unkey:application:protected_resource` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The resource you are attempting to modify is protected and cannot be changed", "status": 403, "title": "Forbidden", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/application/protected_resource" } } ``` ## What Happened? This error occurs when you attempt to modify or delete a resource that is marked as protected in the Unkey system. Protected resources have a special status that prevents them from being changed or removed, typically because they are system resources, defaults, or otherwise critical to proper system operation. Common scenarios that trigger this error: * Attempting to delete a default API or workspace * Trying to modify system-created roles or permissions * Attempting to change protected settings or configurations * Trying to remove or alter resources that are required for system integrity Here's an example of a request that might trigger this error: ```bash # Attempting to delete a protected default API curl -X DELETE https://api.unkey.com/v2/apis.deleteApi \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "apiId": "api_default_protected" }' ``` ## How To Fix Since protected resources are deliberately shielded from modification, the solution is usually to work with or around them rather than trying to change them: 1. **Work with the protected resource**: Use the resource as-is and build your workflows around it 2. **Create a new resource**: Instead of modifying a protected resource, create a new one with your desired configuration 3. **Use alternatives**: Look for alternative ways to achieve your goal without modifying protected resources 4. **Contact support**: If you believe you have a legitimate need to modify a protected resource, contact Unkey support For example, instead of deleting a protected API, you might create a new one: ```bash curl -X POST https://api.unkey.com/v2/apis.createApi \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "name": "My Custom API" }' ``` ## Important Notes * Protected resources are designated as such for system stability and security reasons * Even with admin or owner permissions, protected resources typically cannot be modified * This protection is separate from permission-based restrictions and applies even to workspace owners * The protection status of a resource is not typically exposed in API responses until you try to modify it ## Related Errors * [err:unkey:authorization:forbidden](../authorization/forbidden) - When an operation is not allowed for policy reasons * [err:unkey:authorization:insufficient\_permissions](../authorization/insufficient_permissions) - When you lack permissions for an operation # service_unavailable Source: https://unkey.com/docs/errors/unkey/application/service_unavailable A service is temporarily unavailable `err:unkey:application:service_unavailable` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The service is temporarily unavailable. Please try again later.", "status": 503, "title": "Service Unavailable", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/application/service_unavailable" } } ``` ## What Happened? This error occurs when a component of the Unkey platform is temporarily unavailable or unable to process your request. Unlike an unexpected error, this is a known state where the system has detected that it cannot currently provide the requested service. Possible causes of this error: * Scheduled maintenance * High load or capacity issues * Dependent service outages * Regional infrastructure problems * Database overload or maintenance Here's an example of a request that might receive this error during a service disruption: ```bash curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "apiId": "api_123abc", "name": "My API Key" }' ``` ## How To Fix Since this is a temporary service issue, the best approach is to wait and retry. Here are some strategies: 1. **Implement retry logic**: Add automatic retries with exponential backoff to your code 2. **Check service status**: Visit the Unkey status page for updates on service availability 3. **Try alternate regions**: If Unkey offers region-specific endpoints, try an alternate region 4. **Wait and retry manually**: If it's a one-time operation, simply try again later Here's an example of a robust retry strategy: ```bash # Bash script with retry logic max_attempts=5 attempt=0 backoff_time=1 while [ $attempt -lt $max_attempts ]; do response=$(curl -s -w "\n%{http_code}" \ -X POST https://api.unkey.com/v2/keys.createKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "apiId": "api_123abc", "name": "My API Key" }') http_code=$(echo "$response" | tail -n1) body=$(echo "$response" | sed '$ d') if [ $http_code -eq 503 ]; then attempt=$((attempt+1)) if [ $attempt -eq $max_attempts ]; then echo "Service still unavailable after $max_attempts attempts" exit 1 fi echo "Service unavailable, retrying in $backoff_time seconds... (Attempt $attempt/$max_attempts)" sleep $backoff_time backoff_time=$((backoff_time*2)) else echo "$body" exit 0 fi done ``` ## Important Notes * This error is temporary, and the service will typically recover automatically * For critical applications, implement circuit breakers to prevent cascading failures * If the service remains unavailable for an extended period, check Unkey's status page or contact support * Include the `requestId` from the error response when contacting support ## Related Errors * [err:unkey:application:unexpected\_error](./unexpected_error) - When an unhandled error occurs * [err:unkey:authorization:workspace\_disabled](../authorization/workspace_disabled) - When the workspace is disabled (a different type of unavailability) # unexpected_error Source: https://unkey.com/docs/errors/unkey/application/unexpected_error An unhandled or unexpected error occurred `err:unkey:application:unexpected_error` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "An unexpected error occurred while processing your request", "status": 500, "title": "Internal Server Error", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/application/unexpected_error" } } ``` ## What Happened? This error occurs when the Unkey system encounters an internal error that wasn't anticipated or couldn't be handled gracefully. This is generally not caused by anything you did wrong in your request, but rather indicates an issue within Unkey's systems. Possible causes of this error: * Temporary infrastructure issues * Database connectivity problems * Bugs in the Unkey service * Resource constraints or timeouts * Unexpected edge cases not handled by the application logic Here's an example of a request that might trigger this error if there's an internal issue: ```bash # A valid request that could trigger an unexpected error if there's an internal issue curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "apiId": "api_123abc", "name": "My API Key" }' ``` ## How To Fix Since this is an internal error, there's usually little you can do to directly fix it, but you can try the following: 1. **Retry the request**: Many unexpected errors are temporary and will resolve on a retry 2. **Check Unkey status**: Visit the Unkey status page to see if there are any ongoing service issues 3. **Contact support**: If the error persists, contact Unkey support with your request ID 4. **Implement retry logic**: For critical operations, implement exponential backoff retry logic in your code Here's an example of implementing retry logic with exponential backoff: ```javascript // Pseudocode for retry with exponential backoff async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 300) { let retries = 0; while (true) { try { return await fn(); } catch (error) { if (error.status !== 500 || retries >= maxRetries) { throw error; // Either not a 500 error or we've exceeded retries } // Exponential backoff with jitter const delay = baseDelay * Math.pow(2, retries) * (0.8 + Math.random() * 0.4); console.log(`Retrying after ${delay}ms (attempt ${retries + 1}/${maxRetries})`); await new Promise(resolve => setTimeout(resolve, delay)); retries++; } } } ``` ## Important Notes * Always include the `requestId` when contacting support about this error * This error may indicate a bug in Unkey's systems that needs to be fixed * Unlike most other errors, this one usually can't be resolved by changing your request * If you encounter this error consistently with a specific API call, there may be an edge case that Unkey's team needs to address ## Related Errors * [err:unkey:application:service\_unavailable](./service_unavailable) - When a service is temporarily unavailable # key_not_found Source: https://unkey.com/docs/errors/unkey/authentication/key_not_found The authentication key was not found `err:unkey:authentication:key_not_found` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The provided API key was not found", "status": 401, "title": "Unauthorized", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authentication/key_not_found" } } ``` ## What Happened? This error occurs when you've provided a properly formatted API key in your request to the Unkey API, but the key doesn't exist in Unkey's system. The key might have been deleted, revoked, or you might be using an incorrect key. Common causes include: * Using an API key that has been deleted * Using an API key from a different workspace or environment * Typographical errors when entering the key * Using a test key in production or vice versa Here's an example of a request with a non-existent API key: ```bash # Request to Unkey API with a non-existent key curl -X POST https://api.unkey.com/v2/keys.listKeys \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_NONEXISTENT_KEY" ``` ## How To Fix To fix this error, you need to use a valid API key when making requests to the Unkey API: 1. **Check your Unkey dashboard**: Verify you're using the correct Unkey API key from the [Unkey dashboard](https://app.unkey.com) 2. **Create a new key if needed**: If your key was deleted, create a new one 3. **Use the correct environment**: Make sure you're using the appropriate key for your environment (development, production, etc.) Here's how to check and use the correct Unkey API key: 1. Log in to your Unkey dashboard 2. Navigate to the API keys section 3. Copy the appropriate API key for your environment 4. Use the key in your request as shown below ```bash curl -X POST https://api.unkey.com/v2/keys.listKeys \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_VALID_API_KEY" ``` ## Common Mistakes * **Using revoked Unkey keys**: API keys that have been revoked will return this error * **Environment mismatch**: Using development keys in production or vice versa * **Workspace confusion**: Using keys from one workspace in another workspace's API calls * **Copy-paste errors**: Inadvertently omitting part of the key when copying * **Expired keys**: Keys that have expired will return this error * **Using demo keys**: Using example keys from documentation ## Related Errors * [err:unkey:authentication:missing](./missing) - When no authentication credentials are provided * [err:unkey:authentication:malformed](./malformed) - When the API key is provided but formatted incorrectly # malformed Source: https://unkey.com/docs/errors/unkey/authentication/malformed Authentication credentials were incorrectly formatted `err:unkey:authentication:malformed` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "Authentication credentials were incorrectly formatted", "status": 401, "title": "Unauthorized", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authentication/malformed" } } ``` ## What Happened? This error occurs when your request includes authentication credentials, but they are not formatted correctly. The Unkey API expects API keys to be provided in a specific format in the Authorization header. Common causes include: * Missing the "Bearer" prefix before your API key * Including extra spaces or characters * Using incorrect casing (e.g., "bearer" instead of "Bearer") * Providing a malformed or truncated API key Here's an example of a request with incorrectly formatted credentials: ```bash # Missing the "Bearer" prefix curl -X POST https://api.unkey.com/v2/keys.listKeys \ -H "Content-Type: application/json" \ -H "Authorization: unkey_YOUR_API_KEY" ``` ## How To Fix To fix this error, ensure your Authorization header follows the correct format: 1. **Use the correct format**: Ensure your Authorization header follows the format `Bearer unkey_YOUR_API_KEY` 2. **Check for extra spaces or characters**: Make sure there are no invisible characters or line breaks 3. **Verify the API key format**: Your Unkey API key should start with `unkey_` Here's the correctly formatted request: ```bash curl -X POST https://api.unkey.com/v2/keys.listKeys \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" ``` When properly authenticated, you'll receive a successful response like this: ```json { "meta": { "requestId": "req_8f7g6h5j4k3l2m1n" }, "data": { "keys": [ { "keyId": "key_123abc456def", "name": "Production API Key" } ] } } ``` ## Common Mistakes * **Authorization header format**: Must be exactly `Bearer unkey_YOUR_API_KEY` with a single space after "Bearer" * **Incorrect casing**: Using "bearer" instead of "Bearer" * **API key format**: Your Unkey API key should start with `unkey_` and contain no spaces * **Using wrong key type**: Ensure you're using a root key for management API calls * **Copying errors**: Check for invisible characters or line breaks that might have been copied * **Extra characters**: Including quotes or other characters around the API key * **Truncated keys**: Accidentally cutting off part of the API key when copying ## Related Errors * [err:unkey:authentication:missing](./missing) - When no authentication credentials are provided * [err:unkey:authentication:key\_not\_found](./key_not_found) - When the provided API key doesn't exist # missing Source: https://unkey.com/docs/errors/unkey/authentication/missing Authentication credentials were not provided in the request `err:unkey:authentication:missing` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "Authentication credentials were not provided", "status": 401, "title": "Unauthorized", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authentication/missing" } } ``` ## What Happened? This error occurs when you make a request to the Unkey API without including your API key in the Authorization header. The Unkey API requires authentication for most endpoints to verify your identity and permissions. Here's an example of a request that would trigger this error: ```bash # Request to Unkey API without an API key curl -X POST https://api.unkey.com/v2/keys.listKeys \ -H "Content-Type: application/json" ``` Authentication is required to: * Verify your identity * Ensure you have permission to perform the requested operation * Track usage and apply appropriate rate limits * Maintain security and audit trails ## How To Fix To fix this error, you need to include your Unkey API key in the Authorization header of your request: 1. **Get your Unkey API key**: Obtain your API key from the [Unkey dashboard](https://app.unkey.com) 2. **Add the Authorization header**: Include your Unkey API key with the format `Bearer unkey_YOUR_API_KEY` Here's the corrected request: ```bash curl -X POST https://api.unkey.com/v2/keys.listKeys \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" ``` When properly authenticated, you'll receive a successful response like this: ```json { "meta": { "requestId": "req_8f7g6h5j4k3l2m1n" }, "data": { "keys": [ { "keyId": "key_123abc456def", "name": "Production API Key" } ] } } ``` ## Common Mistakes * **Missing the `Bearer` prefix**: Unkey requires the format `Bearer unkey_YOUR_API_KEY` with a space after "Bearer" * **Headers lost in proxies**: Some proxy servers or API gateways might strip custom headers * **Expired or revoked keys**: Using keys that are no longer valid * **Wrong environment**: Using development keys in production or vice versa ## Related Errors * [err:unkey:authentication:malformed](./malformed) - When the API key is provided but formatted incorrectly * [err:unkey:authentication:key\_not\_found](./key_not_found) - When the provided API key doesn't exist # forbidden Source: https://unkey.com/docs/errors/unkey/authorization/forbidden The operation is not allowed `err:unkey:authorization:forbidden` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "This operation is not allowed", "status": 403, "title": "Forbidden", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authorization/forbidden" } } ``` ## What Happened? This error occurs when you attempt an operation that is prohibited by Unkey's platform policies, even if your API key has high-level permissions. Unlike the "insufficient\_permissions" error which relates to permission roles, this error indicates that the operation itself is not allowed regardless of permissions. Common scenarios that trigger this error: * Trying to perform operations on protected or system resources * Attempting to modify resources that are in a state that doesn't allow modifications * Trying to exceed account limits or quotas * Performing operations that violate platform policies Here's an example of a request that might trigger this error: ```bash # Attempting to delete a protected system resource curl -X POST https://api.unkey.com/v2/apis.deleteApi \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_ADMIN_KEY" \ -d '{ "apiId": "api_system_protected" }' ``` ## How To Fix This error indicates a fundamental restriction rather than a permission issue. The operation you're trying to perform may be: 1. **Not supported by the Unkey platform**: Some operations are simply not available 2. **Blocked due to your account's current state or limitations**: Your account may not have access to certain features 3. **Prevented by safety mechanisms**: System protections may prevent certain destructive operations Possible solutions include: * **Check Unkey's documentation**: Understand which operations have fundamental restrictions * **Consider your account state**: Some operations may be blocked due to your account state or plan * **Use alternative approaches**: Find supported ways to accomplish similar goals * **If you're trying to modify a resource in a specific state**, check if it needs to be in a different state first * **If you're hitting account limits**, consider upgrading your plan * **Contact Unkey support** if you believe this restriction shouldn't apply to your use case ## Common Mistakes * **Attempting to modify system resources**: Some Unkey resources are protected and cannot be modified * **Order-dependent operations**: Trying to perform operations out of their required sequence * **Plan limitations**: Attempting operations not available on your current plan * **Resource state issues**: Trying to modify resources that are in a state that doesn't allow changes * **Ignoring documentation warnings**: Not reading warnings about restricted operations * **Testing security boundaries**: Deliberately trying to access protected resources * **Outdated documentation**: Following outdated documentation that suggests now-forbidden operations ## Related Errors * [err:unkey:authorization:insufficient\_permissions](./insufficient_permissions) - When the authenticated entity lacks specific permissions * [err:unkey:authorization:key\_disabled](./key_disabled) - When the authentication key is disabled * [err:unkey:authorization:workspace\_disabled](./workspace_disabled) - When the associated workspace is disabled # insufficient_permissions Source: https://unkey.com/docs/errors/unkey/authorization/insufficient_permissions The authenticated entity lacks sufficient permissions for the requested operation `err:unkey:authorization:insufficient_permissions` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The authenticated API key does not have permission to perform this operation", "status": 403, "title": "Forbidden", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authorization/insufficient_permissions" } } ``` ## What Happened? This error occurs when your API key is valid and properly authenticated, but it doesn't have the necessary permissions to perform the requested operation. In Unkey, different API keys can have different permission levels. Common scenarios that trigger this error: * Using a read-only key to perform write operations * Using a key limited to specific resources to access other resources * Attempting to access resources across workspaces with a workspace-scoped key * Using a key with limited permissions to perform administrative actions Here's an example of a request using a key with insufficient permissions: ```bash # Using a read-only key to create a new API key curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_READ_ONLY_KEY" \ -d '{ "apiId": "api_123", "name": "New API Key" }' ``` ## How To Fix You need to use an API key with the appropriate permissions for the operation you're trying to perform. Here are some steps to resolve this issue: 1. **Check permissions**: Verify the permissions of your current Unkey API key in the [Unkey dashboard](https://app.unkey.com) 2. **Create a new key**: If needed, create a new Unkey API key with the required permissions 3. **Use role-based keys**: Consider using separate keys for different operations based on their permission requirements Here's an example using a key with the appropriate permissions: ```bash curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_ADMIN_KEY" \ -d '{ "apiId": "api_123", "name": "New API Key" }' ``` ## Common Mistakes * **Using development keys in production**: Keys may have different permissions across environments * **Mixing key scopes**: Using a key scoped to one resource to access another * **Role misunderstanding**: Not understanding the specific permissions granted to each role * **Workspace boundaries**: Attempting to cross workspace boundaries with a limited key * **Permission level confusion**: Not understanding what operations require elevated permissions * **Expired or downgraded privileges**: Using a key whose permissions have been reduced since it was issued ## Related Errors * [err:unkey:authorization:forbidden](./forbidden) - When the operation is not allowed for policy reasons * [err:unkey:authorization:key\_disabled](./key_disabled) - When the authentication key is disabled # key_disabled Source: https://unkey.com/docs/errors/unkey/authorization/key_disabled The authentication key is disabled `err:unkey:authorization:key_disabled` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The API key used for authentication has been disabled", "status": 403, "title": "Forbidden", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authorization/key_disabled" } } ``` ## What Happened? This error occurs when you try to use a disabled Unkey API key (one that starts with `unkey_`) to authenticate with the Unkey API. The key exists in the system but has been disabled and can no longer be used for authentication. Here's an example of a request that would trigger this error: ```bash # Request to Unkey API with a disabled key curl -X POST https://api.unkey.com/v2/keys.listKeys \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_DISABLED_KEY" ``` API keys can be disabled for various reasons: * Administrative action to revoke access * Security concerns or suspected compromise * Temporary deactivation during maintenance or investigation * Automated disabling due to suspicious activity * Usage policy violations ## How To Fix If you encounter this error when using the Unkey API, you have two options: 1. **Get a new Unkey root key**: If your key was permanently disabled, create a new API key with the appropriate permissions in the [Unkey dashboard](https://app.unkey.com/settings/root-keys) 2. **Re-enable your existing key**: If you have administrative access and the key was temporarily disabled, you can re-enable it through the dashboard To re-enable your Unkey root key: 1. Log in to your Unkey dashboard 2. Navigate to the API keys section 3. Search for the key you want to re-enable 4. Click "Enable" Then update your API calls to use the re-enabled key: ```bash curl -X POST https://api.unkey.com/v2/keys.listKeys \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_REACTIVATED_KEY" ``` ## Common Mistakes * **Using old or archived root keys**: Keys from previous projects or configurations may have been disabled * **Shared root keys**: When keys are shared among team members, they may be disabled by another administrator * **Security triggers**: Unusual usage patterns may automatically disable keys as a security precaution * **Environment confusion**: Using disabled staging/development keys in production environments * **Account status changes**: Keys may be disabled due to billing or account status changes * **Rotation policies**: Keys that should have been rotated according to security policies ## Related Errors * [err:unkey:authorization:insufficient\_permissions](./insufficient_permissions) - When the authenticated entity lacks sufficient permissions * [err:unkey:authorization:workspace\_disabled](./workspace_disabled) - When the associated workspace is disabled * [err:unkey:authentication:key\_not\_found](../authentication/key_not_found) - When the provided API key doesn't exist at all # workspace_disabled Source: https://unkey.com/docs/errors/unkey/authorization/workspace_disabled The associated workspace is disabled `err:unkey:authorization:workspace_disabled` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The workspace associated with this API key has been disabled", "status": 403, "title": "Forbidden", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authorization/workspace_disabled" } } ``` ## What Happened? This error occurs when you attempt to use an Unkey API key that belongs to a disabled workspace. When a workspace is disabled in Unkey, all API keys associated with that workspace stop working, regardless of their individual status. Here's an example of a request that would trigger this error: ```bash # Request to Unkey API with a key from a disabled workspace curl -X POST https://api.unkey.com/v2/keys.listKeys \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_KEY_FROM_DISABLED_WORKSPACE" ``` A workspace might be disabled for various reasons: * Billing issues or unpaid invoices * Administrative action due to terms of service violations * At the workspace owner's request * During investigation of suspicious activity * As part of account closure process * Exceeding usage limits or quotas ## How To Fix If you encounter this error when using the Unkey API, you need to address the workspace issue: 1. **Check billing status**: If the workspace was disabled due to billing issues, settle any outstanding payments in the [Unkey dashboard](https://app.unkey.com/settings/billing) 2. **Contact workspace administrator**: If you're not the workspace administrator, contact them to determine why the workspace was disabled 3. **Contact Unkey support**: If you believe the workspace was disabled in error, or you need assistance resolving the issue, contact [Unkey support](mailto:support@unkey.dev) 4. **Use a key from a different workspace**: If you have access to multiple workspaces, you can temporarily use a key from an active workspace while resolving the issue Once the workspace is re-enabled, all API keys associated with it should become usable again (unless individually disabled). ## Common Mistakes * **Billing oversights**: Missed payment notifications can lead to workspace suspension * **Usage violations**: Excessive usage or pattern violations may trigger workspace disabling * **Administrative changes**: Organizational changes might lead to workspaces being temporarily disabled * **Using old workspaces**: Attempting to use keys from deprecated or archived workspaces * **Plan limitation violations**: Exceeding the limits of your current plan * **Account transfer issues**: Workspaces may be temporarily disabled during ownership transfers ## Related Errors * [err:unkey:authorization:key\_disabled](./key_disabled) - When the specific authentication key is disabled * [err:unkey:authorization:insufficient\_permissions](./insufficient_permissions) - When the authenticated entity lacks sufficient permissions * [err:unkey:data:workspace\_not\_found](../data/workspace_not_found) - When the requested workspace doesn't exist # api_not_found Source: https://unkey.com/docs/errors/unkey/data/api_not_found The requested API was not found err:unkey:data:api\_not\_found ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The requested API could not be found", "status": 404, "title": "Not Found", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/api_not_found" } } ``` ## What Happened? This error occurs when you're trying to perform an operation on an API that doesn't exist in the Unkey system. In Unkey, APIs are resources that you create to organize and manage your keys. Common scenarios that trigger this error: * Using an incorrect API ID in your requests * Referencing an API that has been deleted * Attempting to access an API in a workspace you don't have access to * Typos in API names when using name-based lookups Here's an example of a request that would trigger this error: ```bash # Attempting to create a key for a non-existent API curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "apiId": "api_nonexistent", "name": "hello world" }' ``` ## How To Fix Verify that you're using the correct API ID and that the API still exists in your workspace: 1. List all APIs in your workspace to find the correct ID 2. Check if the API has been deleted and recreate it if necessary 3. Verify you're working in the correct workspace 4. Ensure proper permissions to access the API Here's how to list all APIs in your workspace: ```bash curl -X POST https://api.unkey.com/v2/apis.listApis \ -H "Authorization: Bearer unkey_YOUR_API_KEY" ``` If you need to create a new API, use the `apis.createApi` endpoint: ```bash curl -X POST https://api.unkey.com/v2/apis.createApi \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "name": "My New API" }' ``` ## Common Mistakes * **Copy-paste errors**: Using incorrect API IDs from documentation examples * **Deleted APIs**: Attempting to reference APIs that have been deleted * **Environment confusion**: Looking for an API in production that only exists in development * **Workspace boundaries**: Trying to access an API that exists in another workspace ## Related Errors * [err:unkey:data:workspace\_not\_found](./workspace_not_found) - When the requested workspace doesn't exist * [err:unkey:data:key\_not\_found](./key_not_found) - When the requested key doesn't exist * [err:unkey:authorization:insufficient\_permissions](../authorization/insufficient_permissions) - When you don't have permission to access an API # audit_log_not_found Source: https://unkey.com/docs/errors/unkey/data/audit_log_not_found The requested audit log was not found err:unkey:data:audit\_log\_not\_found ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The requested audit log could not be found", "status": 404, "title": "Not Found", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/audit_log_not_found" } } ``` ## What Happened? This error occurs when you're trying to retrieve or operate on a specific audit log entry that doesn't exist in the Unkey system. Audit logs record important actions and events that occur within your workspace. Common scenarios that trigger this error: * Using an incorrect audit log ID * Requesting an audit log entry that has been deleted or expired * Trying to access audit logs from a different workspace * Typographical errors in audit log identifiers Here's an example of a request that would trigger this error: ```bash # Attempting to get a non-existent audit log entry curl -X POST https://api.unkey.com/v2/audit.getLog \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "logId": "log_nonexistent" }' ``` ## How To Fix Verify that you're using the correct audit log ID and that the log entry still exists in your workspace: 1. Check the audit log ID in your request for typos or formatting errors 2. Use the list audit logs endpoint to find valid log IDs 3. Verify you're working in the correct workspace 4. Consider that audit logs might have a retention period after which they're automatically deleted Here's how to list recent audit logs in your workspace: ```bash curl -X POST https://api.unkey.com/v2/audit.listLogs \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "limit": 10 }' ``` ## Common Mistakes * **Expired logs**: Trying to access audit logs beyond the retention period * **Copy-paste errors**: Using incorrect log IDs from documentation examples * **Workspace boundaries**: Attempting to access logs from another workspace * **Permission issues**: Trying to access logs you don't have permission to view ## Related Errors * [err:unkey:authorization:insufficient\_permissions](../authorization/insufficient_permissions) - When you don't have permission to access audit logs * [err:unkey:data:workspace\_not\_found](./workspace_not_found) - When the requested workspace doesn't exist # identity_already_exists Source: https://unkey.com/docs/errors/unkey/data/identity_already_exists The requested identity already exists err:unkey:data:identity\_already\_exists ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "An identity with this external ID already exists", "status": 409, "title": "Conflict", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/identity_already_exists" } } ``` ## What Happened? This error occurs when you're trying to create an identity with an external ID that already exists in your Unkey workspace. External IDs must be unique within a workspace to avoid confusion and maintain data integrity. Common scenarios that trigger this error: * Creating an identity with an external ID that's already in use * Re-creating a previously deleted identity with the same external ID * Migration or import processes that don't check for existing identities * Duplicate API calls due to retries or network issues Here's an example of a request that would trigger this error: ```bash # Attempting to create an identity with an external ID that already exists curl -X POST https://api.unkey.com/v2/identities.createIdentity \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "externalId": "user_123", "meta": { "name": "John Doe", "email": "john@example.com" } }' ``` ## How To Fix When you encounter this error, you have several options: 1. **Use a different external ID**: If creating a new identity, use a unique external ID 2. **Update the existing identity**: If you want to modify an existing identity, use the update endpoint instead 3. **Get the existing identity**: If you just need the identity information, retrieve it rather than creating it 4. **Implement upsert logic**: Use a get-or-create pattern in your code Here's how to update an existing identity: ```bash curl -X POST https://api.unkey.com/v2/identities.updateIdentity \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "externalId": "user_123", "meta": { "name": "John Doe", "email": "updated_email@example.com" } }' ``` Or implement a get-or-create pattern in your code: ```javascript // Pseudocode for get-or-create pattern async function getOrCreateIdentity(externalId, meta) { try { // Try to create the identity return await createIdentity(externalId, meta); } catch (error) { // If it already exists (409 error), get it instead if (error.status === 409) { return await getIdentity(externalId); } // Otherwise, rethrow the error throw error; } } ``` ## Common Mistakes * **Not checking for existing identities**: Failing to check if an identity already exists before creating it * **Retry loops**: Repeatedly trying to create the same identity after a failure * **Case sensitivity**: Not accounting for case sensitivity in external IDs * **Cross-environment duplication**: Using the same external IDs across development and production environments ## Related Errors * [err:unkey:data:identity\_not\_found](./identity_not_found) - When the requested identity doesn't exist * [err:unkey:authorization:insufficient\_permissions](../authorization/insufficient_permissions) - When you don't have permission to perform operations on identities # identity_not_found Source: https://unkey.com/docs/errors/unkey/data/identity_not_found The requested identity was not found err:unkey:data:identity\_not\_found ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The requested identity could not be found", "status": 404, "title": "Not Found", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/identity_not_found" } } ``` ## What Happened? This error occurs when you're trying to perform an operation on an identity that doesn't exist in the Unkey system. Identities in Unkey are used to represent users or entities that own or use API keys. Common scenarios that trigger this error: * Using an incorrect identity ID or external ID * Referencing an identity that has been deleted * Trying to update or get information about a non-existent identity * Typos in identity identifiers Here's an example of a request that would trigger this error: ```bash # Attempting to get a non-existent identity curl -X POST https://api.unkey.com/v2/identities.getIdentity \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "identityId": "ident_nonexistent" }' ``` ## How To Fix Verify that you're using the correct identity ID or external ID and that the identity still exists in your workspace: 1. Check the identity ID in your request for typos or formatting errors 2. List all identities in your workspace to find the correct ID 3. If the identity has been deleted, you may need to recreate it If you need to create a new identity, use the `identities.createIdentity` endpoint: ```bash curl -X POST https://api.unkey.com/v2/identities.createIdentity \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "externalId": "user_123", "meta": { "name": "John Doe", "email": "john@example.com" } }' ``` ## Common Mistakes * **Incorrect identifiers**: Using wrong identity IDs or external IDs * **Deleted identities**: Attempting to reference identities that have been removed * **Case sensitivity**: External IDs might be case-sensitive * **Workspace boundaries**: Trying to access identities from another workspace ## Related Errors * [err:unkey:data:identity\_already\_exists](./identity_already_exists) - When trying to create an identity that already exists * [err:unkey:data:key\_not\_found](./key_not_found) - When the requested key doesn't exist * [err:unkey:authorization:insufficient\_permissions](../authorization/insufficient_permissions) - When you don't have permission to perform operations on identities # key_auth_not_found Source: https://unkey.com/docs/errors/unkey/data/key_auth_not_found The requested key authentication was not found err:unkey:data:key\_auth\_not\_found ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The requested key authentication could not be found", "status": 404, "title": "Not Found", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/key_auth_not_found" } } ``` ## What Happened? This error occurs when you're trying to perform an operation on a key authentication record that doesn't exist in the Unkey system. Key authentication records contain information about how API keys are authenticated. Common scenarios that trigger this error: * Using an incorrect key authentication ID * Referencing a key authentication record that has been deleted * Attempting to update authentication settings for a non-existent record * Typos in identifiers ## How To Fix Verify that you're using the correct key authentication ID and that the record still exists: 1. Check the key authentication ID in your request for typos or formatting errors 2. Verify the key authentication record exists by looking up the associated key 3. If the record has been deleted, you may need to recreate the key or its authentication settings Here's how to get information about a key's authentication settings: ```bash curl -X POST https://api.unkey.com/v2/keys.getKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "keyId": "key_your_key_id" }' ``` ## Common Mistakes * **Copy-paste errors**: Incorrect IDs due to copy-paste mistakes * **Deleted records**: Attempting to reference authentication records for deleted keys * **Misunderstanding relationships**: Confusing key IDs with key authentication IDs * **Workspace boundaries**: Trying to access authentication records from another workspace ## Related Errors * [err:unkey:data:key\_not\_found](./key_not_found) - When the requested key doesn't exist * [err:unkey:authentication:key\_not\_found](../authentication/key_not_found) - When an API key used for authentication doesn't exist * [err:unkey:authorization:key\_disabled](../authorization/key_disabled) - When the authentication key is disabled # key_not_found Source: https://unkey.com/docs/errors/unkey/data/key_not_found The requested key was not found err:unkey:data:key\_not\_found ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The requested API key could not be found", "status": 404, "title": "Not Found", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/key_not_found" } } ``` ## What Happened? This error occurs when you're trying to perform an operation on a specific API key using its ID, but the key with that ID doesn't exist in the system. This is different from the authentication error `err:unkey:authentication:key_not_found`, which occurs during the authentication process. Common scenarios that trigger this error: * Attempting to update, delete, or get information about a key that has been deleted * Using an incorrect or malformed key ID * Trying to access a key that exists in a different workspace * Reference to a key that hasn't been created yet Here's an example of a request that would trigger this error: ```bash # Attempting to get details for a non-existent key curl -X POST https://api.unkey.com/v2/keys.getKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "keyId": "key_nonexistent" }' ``` ## How To Fix Verify that you're using the correct key ID and that the key still exists in your workspace: 1. Check the key ID in your request for typos or formatting errors 2. Confirm the key exists by listing all keys in your workspace via the [Unkey dashboard](https://unkey.com/dashboard) or the API 3. Verify you're working in the correct workspace 4. If you need to create a new key, use the `keys.createKey` endpoint Here's how to list all keys to find the correct ID: ```bash curl -X POST https://api.unkey.com/v2/apis.listKeys \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "apiId": "api_your_api_id" }' ``` ## Common Mistakes * **Copy-paste errors**: Incorrect key IDs due to copy-paste mistakes * **Deleted keys**: Attempting to reference keys that have been deleted * **Environment confusion**: Looking for a key in production that only exists in development * **Workspace boundaries**: Trying to access a key that exists in another workspace ## Related Errors * [err:unkey:authentication:key\_not\_found](../authentication/key_not_found) - When an API key used for authentication doesn't exist * [err:unkey:data:api\_not\_found](./api_not_found) - When the requested API doesn't exist * [err:unkey:data:workspace\_not\_found](./workspace_not_found) - When the requested workspace doesn't exist # permission_not_found Source: https://unkey.com/docs/errors/unkey/data/permission_not_found The requested permission was not found err:unkey:data:permission\_not\_found ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The requested permission could not be found", "status": 404, "title": "Not Found", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/permission_not_found" } } ``` ## What Happened? This error occurs when you're trying to perform an operation on a permission that doesn't exist in the Unkey system. Permissions in Unkey are used to control access to resources and operations. Common scenarios that trigger this error: * Using an incorrect permission ID or name * Referencing a permission that has been deleted * Trying to assign a permission that doesn't exist in the current workspace * Typos in permission names when using name-based lookups ## How To Fix Verify that you're using the correct permission ID or name and that the permission still exists in your workspace: 1. List all permissions in your workspace to find the correct ID 2. Check if the permission has been deleted and recreate it if necessary 3. Verify you're working in the correct workspace Here's how to list all permissions in your workspace: ```bash curl -X POST https://api.unkey.com/v2/permissions.listPermissions \ -H "Authorization: Bearer unkey_YOUR_API_KEY" ``` If you need to create a new permission, use the appropriate API endpoint: ```bash curl -X POST https://api.unkey.com/v2/permissions.createPermission \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "name": "read:keys", "description": "Allows reading key information" }' ``` ## Common Mistakes * **Incorrect identifiers**: Using wrong permission IDs or names * **Deleted permissions**: Referencing permissions that have been removed * **Case sensitivity**: Permissions names might be case-sensitive * **Workspace boundaries**: Trying to use permissions from another workspace ## Related Errors * [err:unkey:data:role\_not\_found](./role_not_found) - When the requested role doesn't exist * [err:unkey:data:api\_not\_found](./api_not_found) - When the requested API doesn't exist * [err:unkey:authorization:insufficient\_permissions](../authorization/insufficient_permissions) - When you don't have permission to perform operations on permissions # ratelimit_namespace_not_found Source: https://unkey.com/docs/errors/unkey/data/ratelimit_namespace_not_found The requested rate limit namespace was not found err:unkey:data:ratelimit\_namespace\_not\_found ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The requested rate limit namespace could not be found", "status": 404, "title": "Not Found", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found" } } ``` ## What Happened? This error occurs when you're trying to perform an operation on a rate limit namespace that doesn't exist in the Unkey system. Rate limit namespaces are used to organize and manage rate limits for different resources or operations. Common scenarios that trigger this error: * Using an incorrect namespace ID or name * Referencing a namespace that has been deleted * Trying to modify a namespace that doesn't exist in the current workspace * Typos in namespace names when using name-based lookups Here's an example of a request that would trigger this error: ```bash # Attempting to get overrides for a non-existent namespace curl -X POST https://api.unkey.com/v2/ratelimit.listOverrides \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "namespaceName": "nonexistent_namespace" }' ``` ## How To Fix Verify that you're using the correct namespace ID or name and that the namespace still exists in your workspace: 1. Check the namespace ID or name in your request for typos or formatting errors 2. List all namespaces in your workspace to find the correct ID or name 3. If the namespace has been deleted, you may need to recreate it Here's how to use the correct namespace in a rate limit operation: ```bash # Creating a rate limit using a valid namespace curl -X POST https://api.unkey.com/v2/ratelimit.limit \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "namespace": "your_valid_namespace", "identifier": "user_123", "limit": 100, "duration": 60000 }' ``` ## Common Mistakes * **Typos in namespace names**: Small typographical errors in namespace names * **Case sensitivity**: Namespace names might be case-sensitive * **Deleted namespaces**: Referencing namespaces that have been removed * **Workspace boundaries**: Trying to use namespaces from another workspace ## Related Errors * [err:unkey:data:ratelimit\_override\_not\_found](./ratelimit_override_not_found) - When the requested rate limit override doesn't exist * [err:unkey:authorization:insufficient\_permissions](../authorization/insufficient_permissions) - When you don't have permission to perform operations on rate limits * [err:unkey:data:workspace\_not\_found](./workspace_not_found) - When the requested workspace doesn't exist # ratelimit_override_not_found Source: https://unkey.com/docs/errors/unkey/data/ratelimit_override_not_found The requested rate limit override was not found err:unkey:data:ratelimit\_override\_not\_found ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The requested rate limit override could not be found", "status": 404, "title": "Not Found", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_override_not_found" } } ``` ## What Happened? This error occurs when you're trying to perform an operation on a rate limit override that doesn't exist in the Unkey system. Rate limit overrides are used to create custom rate limits for specific identifiers within a namespace. Common scenarios that trigger this error: * Using an incorrect override ID * Referencing an override that has been deleted * Trying to get or modify an override for an identifier that doesn't have one * Using the wrong namespace when looking up an override Here's an example of a request that would trigger this error: ```bash # Attempting to get a non-existent rate limit override curl -X POST https://api.unkey.com/v2/ratelimit.getOverride \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "namespaceId": "ns_123abc", "identifier": "user_without_override" }' ``` ## How To Fix Verify that you're using the correct namespace and identifier, and that the override still exists: 1. Check the namespace ID and identifier in your request for typos 2. List all overrides in the namespace to confirm if the one you're looking for exists 3. If the override has been deleted or never existed, you may need to create it Here's how to list overrides in a namespace: ```bash curl -X POST https://api.unkey.com/v2/ratelimit.listOverrides \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "namespaceId": "ns_123abc" }' ``` If you need to create a new override, use the `ratelimit.setOverride` endpoint: ```bash curl -X POST https://api.unkey.com/v2/ratelimit.setOverride \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "namespaceId": "ns_123abc", "identifier": "user_123", "limit": 200, "duration": 60000 }' ``` ## Common Mistakes * **Wrong identifier**: Using an incorrect user identifier when looking up overrides * **Deleted overrides**: Attempting to reference overrides that have been removed * **Namespace mismatch**: Looking in the wrong namespace for an override * **Assuming defaults are overrides**: Trying to get an override for an identifier that's using default limits ## Related Errors * [err:unkey:data:ratelimit\_namespace\_not\_found](./ratelimit_namespace_not_found) - When the requested rate limit namespace doesn't exist * [err:unkey:authorization:insufficient\_permissions](../authorization/insufficient_permissions) - When you don't have permission to perform operations on rate limit overrides * [err:unkey:data:workspace\_not\_found](./workspace_not_found) - When the requested workspace doesn't exist # role_not_found Source: https://unkey.com/docs/errors/unkey/data/role_not_found The requested role was not found err:unkey:data:role\_not\_found ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The requested role could not be found", "status": 404, "title": "Not Found", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/role_not_found" } } ``` ## What Happened? This error occurs when you're trying to perform an operation on a role that doesn't exist in the Unkey system. Roles in Unkey are collections of permissions that can be assigned to users or API keys. Common scenarios that trigger this error: * Using an incorrect role ID or name * Referencing a role that has been deleted * Trying to assign a role that doesn't exist in the current workspace * Typos in role names when using name-based lookups ## How To Fix Verify that you're using the correct role ID or name and that the role still exists in your workspace: 1. List all roles in your workspace to find the correct ID 2. Check if the role has been deleted and recreate it if necessary 3. Verify you're working in the correct workspace Here's how to list all roles in your workspace: ```bash curl -X POST https://api.unkey.com/v2/permissions.listRoles \ -H "Authorization: Bearer unkey_YOUR_API_KEY" ``` If you need to create a new role, use the appropriate API endpoint: ```bash curl -X POST https://api.unkey.com/v2/permissions.createRole \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "name": "API Reader", "description": "Can read API information" }' ``` ## Common Mistakes * **Incorrect identifiers**: Using wrong role IDs or names * **Deleted roles**: Referencing roles that have been removed * **Case sensitivity**: Role names might be case-sensitive * **Workspace boundaries**: Trying to use roles from another workspace ## Related Errors * [err:unkey:data:permission\_not\_found](./permission_not_found) - When the requested permission doesn't exist * [err:unkey:data:api\_not\_found](./api_not_found) - When the requested API doesn't exist * [err:unkey:authorization:insufficient\_permissions](../authorization/insufficient_permissions) - When you don't have permission to perform operations on roles # workspace_not_found Source: https://unkey.com/docs/errors/unkey/data/workspace_not_found The requested workspace was not found err:unkey:data:workspace\_not\_found ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "The requested workspace could not be found", "status": 404, "title": "Not Found", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/workspace_not_found" } } ``` ## What Happened? This error occurs when you're trying to perform an operation on a workspace that doesn't exist in the Unkey system. This can happen when referencing a workspace by ID or name in API calls. Common scenarios that trigger this error: * Using an incorrect workspace ID * Referencing a workspace that has been deleted * Attempting to access a workspace you don't have permission to see * Typos in workspace names when using name-based lookups ## How To Fix Verify that you're using the correct workspace ID or name and that the workspace still exists: 1. Check your Unkey dashboard to see a list of workspaces you have access to 2. Verify the workspace ID or name in your API calls 3. Ensure you have permission to access the workspace 4. If needed, create a new workspace through the dashboard or API \` ## Common Mistakes * **Deleted workspaces**: Attempting to reference workspaces that have been deleted * **Copy-paste errors**: Using incorrect IDs from documentation examples * **Permission issues**: Trying to access workspaces you've been removed from * **Case sensitivity**: Using incorrect casing in workspace name lookups ## Related Errors * [err:unkey:authorization:workspace\_disabled](../authorization/workspace_disabled) - When the workspace exists but is disabled * [err:unkey:data:api\_not\_found](./api_not_found) - When the requested API doesn't exist * [err:unkey:data:key\_not\_found](./key_not_found) - When the requested key doesn't exist # permissions_query_syntax_error Source: https://unkey.com/docs/errors/user/bad_request/permissions_query_syntax_error Invalid syntax or characters in verifyKey permissions query `err:user:bad_request:permissions_query_syntax_error` ```json Example { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "Syntax error in permission query: unexpected token 'AND' at position 15. Expected permission name or opening parenthesis.", "status": 400, "title": "Bad Request", "type": "https://unkey.com/docs/api-reference/errors-v2/user/bad_request/permissions_query_syntax_error", "errors": [ { "location": "body.permissions", "message": "unexpected token 'AND' at position 15", "fix": "Check your query syntax. AND/OR operators must be between permissions, not at the start or end" } ] } } ``` ## What Happened? This error occurs when the permissions query in your `verifyKey` request contains invalid syntax or characters. This can happen due to: * **Invalid characters** in permission names (lexical errors) * **Incorrect query structure** like missing operands or unmatched parentheses (syntax errors) * **Malformed expressions** that don't follow the expected grammar ## Permissions Query Requirements The `verifyKey` endpoint accepts a permissions query string that must follow these rules: ### Valid Characters The query parser accepts these characters: * **Permissions**: Must follow the permission slug format (alphanumeric, dots, underscores, hyphens) * **Letters**: `a-z`, `A-Z` * **Numbers**: `0-9` * **Dots**: `.` for permission namespacing * **Underscores**: `_` in identifiers * **Hyphens**: `-` in identifiers * **Query operators**: `AND`, `OR` (case insensitive) * **Grouping**: `(` `)` for parentheses * **Whitespace**: Spaces, tabs and new lines for separation (ignored by parser) Everything else is not allowed. ### Query Structure A permissions query can be: 1. **A single permission**: `permission_1` 2. **Multiple permissions with AND**: `permission_1 AND permission_2` 3. **Multiple permissions with OR**: `permission_1 OR permission_2` 4. **Grouped expressions**: `(permission_1 OR permission_2) AND permission_3` **Key rules:** * Permission names must be valid permission slugs (letters, numbers, dots, underscores, hyphens) * Use `AND` when all permissions are required * Use `OR` when any of the permissions is sufficient * Use parentheses `()` to group expressions and control precedence * Operators are case insensitive: `AND`, `AnD`, `and` all work. ## Common Errors and Solutions ### 1. Invalid Characters ```bash # ❌ Invalid - contains special characters curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "permission$1 OR permission@2" }' # ✅ Valid - use underscores or hyphens curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "permission_1 OR permission_2" }' ``` ### 2. Missing Operands ```bash # ❌ Invalid - AND without right operand curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "permission_1 AND" }' # ✅ Valid - complete AND expression curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "permission_1 AND permission_2" }' ``` ### 3. Unmatched Parentheses ```bash # ❌ Invalid - missing closing parenthesis curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "(permission_1 AND permission_2" }' # ✅ Valid - balanced parentheses curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "(permission_1 AND permission_2)" }' ``` ### 4. Empty Parentheses ```bash # ❌ Invalid - empty parentheses curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "permission_1 AND ()" }' # ✅ Valid - parentheses with content curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "permission_1 AND (permission_2 OR permission_3)" }' ``` ### 5. Incorrect Operator Placement ```bash # ❌ Invalid - operator at start curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "OR permission_1" }' # ✅ Valid - operators between permissions curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "permission_1 OR permission_2" }' ``` ## Valid Query Examples ### Simple Permission ```bash curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "permission_1" }' ``` ### AND Operation ```bash curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "permission_1 AND permission_2" }' ``` ### OR Operation ```bash curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "permission_1 OR permission_2" }' ``` ### Complex Expressions ```bash curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "(permission_1 OR permission_2) AND permission_3" }' ``` ### Nested Expressions ```bash curl -X POST https://api.unkey.com/v2/keys.verifyKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "key": "sk_123", "permissions": "((permission_1 OR permission_2) AND permission_3) OR permission_4" }' ``` ## Valid Permission Formats ### Simple Names * `permission_1` * `user_read` * `admin-access` ### Namespaced Permissions * `api.users.read` * `billing.invoices.create` * `workspace.settings.update` ### Mixed Formats * `user_management.create` * `billing-service.view` * `service123.feature_a.read` ## Context This error is specific to the `verifyKey` endpoint's permissions query parsing. The query is validated at the application level to ensure it conforms to the expected permission query language syntax. Basic validation like empty strings and length limits are handled at the OpenAPI level before reaching this parser. # request_body_too_large Source: https://unkey.com/docs/errors/user/bad_request/request_body_too_large Request body exceeds the maximum allowed size limit `err:user:bad_request:request_body_too_large` ```json Example { "meta": { "requestId": "req_4dgzrNP3Je5mU1tD" }, "error": { "detail": "The request body exceeds the maximum allowed size of 100 bytes.", "status": 413, "title": "Request Entity Too Large", "type": "https://unkey.com/docs/errors/user/bad_request/request_body_too_large", "errors": [] } } ``` ## What Happened? Your request was too big! We limit how much data you can send in a single API request to keep everything running smoothly. This usually happens when you're trying to send a lot of data at once - like huge metadata objects or really long strings in your request. ## How to Fix It ### 1. Trim Down Your Request The most common cause is putting too much data in the `meta` field or other parts of your request. ```bash Too Big curl -X POST https://api.unkey.com/v2/keys.create \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "apiId": "api_123", "name": "My Key", "meta": { "userProfile": "... really long user profile data ...", "settings": { /* huge nested object with tons of properties */ } } }' ``` ```bash Just Right curl -X POST https://api.unkey.com/v2/keys.create \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_XXXX" \ -d '{ "apiId": "api_123", "name": "My Key", "meta": { "userId": "user_123", "tier": "premium" } }' ``` ### 2. Store Big Data Elsewhere Instead of cramming everything into your API request: * Store large data in your own database * Only send IDs or references to Unkey * Fetch the full data when you need it ## Need a Higher Limit? **Got a special use case?** If you have a legitimate need to send larger requests, we'd love to hear about it! [Contact our support team](mailto:support@unkey.dev) and include: * What you're building * Why you need to send large requests * An example of the data you're trying to send We'll work with you to find a solution that works for your use case. ``` ``` # Welcome to Unkey Source: https://unkey.com/docs/introduction API management redefined Unkey provides an easy to use API management platform, including API key management, standalone ratelimiting and LLM caching. ## New to Unkey? Start using Unkey with your existing project, or start from scratch. Get started with Unkey with your favorite language. Learn how to use API Keys to protect, and scale your APIs Protect public or private endpoints ## Start building Ready to use Unkey to power your API? Learn about our API that helps you manage APIs, keys, ratelimits and analytical data. Integrate Unkey into your application with one of our SDKs Learn how to work with Unkey through tutorials ## Join the community Join the Unkey community to ask questions, discuss best practices, and share tips. # create_key Source: https://unkey.com/docs/libraries/ex/functions/create_key Create an api key for your users > @spec create\_key(map) :: map() Creates an API key for your users. ## Request Choose an `API` where this key should be created. To make it easier for your users to understand which product an api key belongs to, you can add prefix them. For example Stripe famously prefixes their customer ids with `cus_` or their api keys with `sk_live_`. The underscore is automtically added if you are defining a prefix, for example: `"prefix": "abc"` will result in a key like `abc_xxxxxxxxx` The bytelength used to generate your key determines its entropy as well as its length. Higher is better, but keys become longer and more annoying to handle. The default is `16 bytes`, or 2128 possible combinations Your user's Id. This will provide a link between Unkey and your customer record. When validating a key, we will return this back to you, so you can clearly identify your user from their api key. This is a place for dynamic meta data, anything that feels useful for you should go here Example: ```json { "billingTier": "PRO", "trialEnds": "2023-06-16T17:16:37.161Z" } ``` You can auto expire keys by providing a unix timestamp in milliseconds. Once keys expire they will automatically be deleted and are no longer valid. Unkey comes with per-key ratelimiting out of the box. Either `fast` or `consistent`. Read more [here](/apis/features/ratelimiting) The total amount of burstable requests. How many tokens to refill during each `refillInterval` Determines the speed at which tokens are refilled. In milliseconds Optionally limit the number of times a key can be used. This is different from time-based expiration using `expires`. Example: ```json "remaining": 10 ``` The created key can be verified successfully 10 times, afterwards it is invalidated automatically. Read more [here](/apis/features/remaining) ## Response The newly created api key A unique id to reference this key for updating or revoking. This id can not be used to verify the key. ```elixir try do expiry = DateTime.utc_now() |> DateTime.add(100_000) |> DateTime.to_unix(:millisecond) opts = UnkeyElixirSdk.create_key(%{ "apiId" => "api_7oKUUscTZy22jmVf9THxDA", "prefix" => "xyz", "byteLength" => 16, "ownerId" => "glamboyosa", "meta" => %{"hello" => "world"}, "expires" => expiry, "ratelimit" => %{ "type" => "fast", "limit" => 10, "refillRate" => 1, "refillInterval" => 1000 }, "remaining" => 10 }) Logger.info(opts) catch err -> Logger.error(err) end ``` ```json { "key": "xyz_AS5HDkXXPot2MMoPHD8jnL", "keyId": "key_cm9vdCBvZiBnb29kXa", } ``` # delete_key Source: https://unkey.com/docs/libraries/ex/functions/delete_key delete a key > @spec delete\_key(binary) :: :ok Delete an api key for your users Returns `:ok` ## Request The ID of the key you want to revoke. ## Response Returns an atom `:ok` ```elixir try do :ok = UnkeyElixirSdk.delete_key("xyz_AS5HDkXXPot2MMoPHD8jnL") catch err -> Logger.error(err) end ``` ```elixir :ok ``` # update_key Source: https://unkey.com/docs/libraries/ex/functions/update_key Updates the configuration of a key > @spec update\_key(binary(), map()) :: :ok Updates the configuration of a key. Takes in a `key_id` argument and a map whose members are optional but must have at most 1 member present. To delete a field, set it to `nil`. ## Request The ID of the key you want to modify. Update the name of the key. Update the owner id of the key. Update the metadata of a key. You will have to provide the full metadata object, not just the fields you want to update. Update the expire time of a key. The expire time is a unix timestamp in milliseconds. Unkey comes with per-key ratelimiting out of the box. Either `fast` or `consistent`. Read more [here](/apis/features/ratelimiting) The total amount of burstable requests. How many tokens to refill during each `refillInterval` Determines the speed at which tokens are refilled. In milliseconds Update the expire time of a key. The expire time is a unix timestamp in milliseconds. ## Response Returns an atom `:ok` ```elixir try do :ok = UnkeyElixirSdk.update_key("key_cm9vdCBvZiBnb29kXa", %{ "name" => "my_new_key", "ratelimit" => %{ "type" => "fast", "limit" => 15, "refillRate" => 2, "refillInterval" => 500 }, "remaining" => 3 }) catch err -> Logger.error(err) end ``` ```elixir :ok ``` # update_remaining Source: https://unkey.com/docs/libraries/ex/functions/update_remaining Updates the `remaining` value of a key > @spec update\_remaining(map()) :: :ok Updates the `remaining` value for a specified key. ## Request The ID of the key you want to modify. The operation you want to perform on the remaining count. Available options: "increment" | "decrement" | "set" The value you want to set, add or subtract the remaining count by ## Response The updated `remaining` value. ```elixir try do opts = UnkeyElixirSdk.update_remaining(%{ "keyId": "key_123", "op": "increment", "value": 1 }) catch err -> Logger.error(err) end ``` ```elixir %{"remaining"=> 100} ``` # verify_key Source: https://unkey.com/docs/libraries/ex/functions/verify_key Verify a key > @spec verify\_key(binary, map()) :: map() Verify a key from your users. You only need to send the api key from your user. Optionally, pass in a second param, a map with the key `apiId` which sends the `apiId` along. ## Request The key you want to verify. The `API` of the key you want to verify. ## Response Whether or not this key is valid and has passed the ratelimit. If `false` you should not grant access to whatever the user is requesting If you have set an `ownerId` on this key it is returned here. You can use this to clearly authenticate a user in your system. This is the `meta` data you have set when creating the key. Example: ```json { "billingTier": "PRO", "trialEnds": "2023-06-16T17:16:37.161Z" } ``` ```elixir try do is_verified = UnkeyElixirSdk.verify_key("xyz_AS5HDkXXPot2MMoPHD8jnL", %{ "apiId" => "api_7oKUUscTZy22jmVf9THxDA" }) catch err -> Logger.error(err) end ``` ```ts { valid: true, ownerId: "glamboyosa", meta: { hello: "world" } } ``` # Overview Source: https://unkey.com/docs/libraries/ex/overview Elixir client for unkey [Elixir SDK](https://github.com/glamboyosa/unkey-elixir-sdk) for interacting with the platform programatically. ## Installation The package can be installed from Hex PM by adding `unkey_elixir_sdk` to your list of dependencies in `mix.exs`: > Note: This project uses Elixir version `1.13`. ```elixir def deps do [ {:unkey_elixir_sdk, "~> 0.2.0"} ] end ``` ## Start the GenServer In order to start this package we can either start it under a supervision tree (most common). The GenServer takes a map with two properties. * token: Your [Unkey](https://unkey.com) root key used to make requests. You can create one [here](https://app.unkey.com/settings/root-keys) **required** * base\_url: The base URL endpoint you will be hitting i.e. `https://api.unkey.dev/v1/keys` (optional). ```elixir children = [ {UnkeyElixirSdk, %{token: "yourunkeyrootkey"}} ] # Now we start the supervisor with the children and a strategy {:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one) # After started, we can query the supervisor for information Supervisor.count_children(pid) #=> %{active: 1, specs: 1, supervisors: 0, workers: 1} ``` You can also call the `start_link` function instead. ```elixir {:ok, _pid} = UnkeyElixirSdk.start_link(%{token: "yourunkeyrootkey", base_url: "https://api.unkey.dev/v1/keys"}) ``` # unkey-go Source: https://unkey.com/docs/libraries/go/api Unkey's API provides programmatic access for all resources within our platform. ## SDK Installation To add the SDK as a dependency to your project: ```bash go get github.com/unkeyed/sdks/api/go/v2@latest ``` ## SDK Example Usage ### Example ```go package main import ( "context" unkey "github.com/unkeyed/sdks/api/go/v2" "github.com/unkeyed/sdks/api/go/v2/models/components" "log" "os" ) func main() { ctx := context.Background() s := unkey.New( unkey.WithSecurity(os.Getenv("UNKEY_ROOT_KEY")), ) res, err := s.Apis.CreateAPI(ctx, components.V2ApisCreateAPIRequestBody{ Name: "payment-service-production", }) if err != nil { log.Fatal(err) } if res.V2ApisCreateAPIResponseBody != nil { // handle response } } ``` ## Repository {" "} The full autogenerated documentation can be found on GitHub. # Overview Source: https://unkey.com/docs/libraries/nuxt/overview Nuxt module for unkey If you are using Nuxt, you can benefit from an almost zero-config experience with the `@unkey/nuxt` module. ## Install ```bash bun install @unkey/nuxt ``` ```bash pnpm add @unkey/nuxt ``` ```bash yarn add @unkey/nuxt ``` ```bash npm install @unkey/nuxt ``` ## Configuration `@unkey/nuxt` just requires your root key. Create an `.env` file in your project and add the following: ```env NUXT_UNKEY_TOKEN= ``` This can also be configured at runtime by setting the `NUXT_UNKEY_TOKEN` environment variable. From this point onward, `@unkey/nuxt` will automatically: 1. verify any API requests with an `Authorization: Bearer xxx` header. 2. register a `useUnkey()` helper that allows access to an automatically configured unkey instance. ## Usage ### Automatic verification You can access the automatically-verified `unkey` context on the server with `event.context.unkey` in your server routes or `useRequestEvent().context.unkey` in the Vue part of your app. For example: ```ts export default defineEventHandler(async (event) => { if (!event.context.unkey.valid) { throw createError({ statusCode: 403, message: "Invalid API key" }) } // return authorised information return { // ... }; }); ``` ```html ``` ## Unkey helper For more about how to use the configured helper provided by `useUnkey()`, you can see the API docs for [the TypeScript client](/libraries/ts/sdk/overview). For example: ```ts const unkey = useUnkey(); const created = await unkey.keys.create({ apiId: "api_7oKUUscTZy22jmVf9THxDA", prefix: "xyz", byteLength: 16, ownerId: "chronark", meta: { hello: "world", }, expires: 1686941966471, ratelimit: { async: true, duration: 1000, limit: 10, refillRate: 1, refillInterval: 1000, }, }); console.log(created.key); ``` ### Disable telemetry By default, Unkey collects anonymous telemetry data to help us understand how our SDKs are used. If you wish to disable this, you can do so by passing a boolean flag to the constructor: ```ts const unkey = useUnkey({ disableTelemetry: true }) ``` # unkey.py Source: https://unkey.com/docs/libraries/py/api Unkey's API provides programmatic access for all resources within our platform. ## SDK Installation > \[!NOTE] > **Python version upgrade policy** > > Once a Python version reaches its [official end of life date](https://devguide.python.org/versions/), a 3-month grace period is provided for users to upgrade. Following this grace period, the minimum python version supported in the SDK will be updated. The SDK can be installed with either *pip* or *poetry* package managers. ### PIP *PIP* is the default package installer for Python, enabling easy installation and management of packages from PyPI via the command line. ```bash pip install unkey.py ``` ### Poetry *Poetry* is a modern tool that simplifies dependency management and package publishing by using a single `pyproject.toml` file to handle project metadata and dependencies. ```bash poetry add unkey.py ``` ### Shell and script usage with `uv` You can use this SDK in a Python shell with [uv](https://docs.astral.sh/uv/) and the `uvx` command that comes with it like so: ```shell uvx --from unkey.py python ``` It's also possible to write a standalone Python script without needing to set up a whole project like so: ```python #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.9" # dependencies = [ # "unkey.py", # ] # /// from unkey.py import Unkey sdk = Unkey( # SDK arguments ) # Rest of script here... ``` Once that is saved to a file, you can run it with `uv run script.py` where `script.py` can be replaced with the actual file name. ## SDK Example Usage ### Example ```python # Synchronous Example from unkey.py import Unkey with Unkey( root_key="", ) as unkey: res = unkey.apis.create_api(name="payment-service-production") # Handle response print(res) ``` The same SDK client can also be used to make asynchronous requests by importing asyncio. ```python # Asynchronous Example import asyncio from unkey.py import Unkey async def main(): async with Unkey( root_key="", ) as unkey: res = await unkey.apis.create_api_async(name="payment-service-production") # Handle response print(res) asyncio.run(main()) ``` ## Repository {" "} The full autogenerated documentation can be found on GitHub. # Overview Source: https://unkey.com/docs/libraries/rs/overview Rust client for unkey # Unkey for Rust An asynchronous Rust SDK for the [Unkey API](https://unkey.com/docs/introduction). All the API key management features you love, now with more type safety! ## MSRV The minimum supported Rust verision for the project is `1.63.0`. ## Setup ### Using `cargo` ```bash $ cargo add unkey ``` ### Manually Add the following to your `Cargo.toml` dependencies array: ```toml unkey = "0.4" ``` ## Getting Started ### Examples #### Verifying a key ```rust use unkey::models::VerifyKeyRequest; use unkey::Client; async fn verify_key() { let c = Client::new("unkey_ABC"); let req = VerifyKeyRequest::new("test_DEF", "api_JJJ"); match c.verify_key(req).await { Ok(res) => println!("{res:?}"), Err(err) => eprintln!("{err:?}"), } } ``` #### Creating a key ```rust use unkey::models::CreateKeyRequest; use unkey::Client; async fn create_key() { let c = Client::new("unkey_ABC"); let req = CreateKeyRequest::new("api_123") .set_prefix("test") .set_remaining(100) .set_name("test_name") .set_owner_id("jonxslays"); match c.create_key(req).await { Ok(res) => println!("{res:?}"), Err(err) => eprintln!("{err:?}"), } } ``` #### Updating a key ```rust use unkey::models::{Refill, RefillInterval, UpdateKeyRequest}; use unkey::Client; async fn update_key() { let c = Client::new("unkey_ABC"); let req = UpdateKeyRequest::new("key_XYZ") .set_name(Some("new_name")) // Update the keys name .set_ratelimit(None) // Remove any ratelimit on the key .set_expires(None) // Remove any expiration date .set_refill(Some(Refill::new(100, RefillInterval::Daily))); match c.update_key(req).await { Ok(_) => println!("Success"), // Nothing on success Err(err) => eprintln!("{err:?}"), } } ``` #### Revoking a key ```rust use unkey::models::RevokeKeyRequest; use unkey::Client; async fn revoke_key() { let c = Client::new("unkey_ABC"); let req = RevokeKeyRequest::new("key_XYZ"); match c.revoke_key(req).await { Ok(_) => println!("Success"), // Nothing on success Err(err) => eprintln!("{err:?}"), } } ``` #### Listing api keys ```rust use unkey::models::ListKeysRequest; use unkey::Client; async fn list_keys() { let c = Client::new("unkey_ABC"); let req = ListKeysRequest::new("api_123"); match c.list_keys(req).await { Ok(res) => println!("{res:?}"), Err(err) => eprintln!("{err:?}"), } } ``` #### Getting api information ```rust use unkey::models::GetApiRequest; use unkey::Client; async fn get_api() { let c = Client::new("unkey_ABC"); let req = GetApiRequest::new("api_123"); match c.get_api(req).await { Ok(res) => println!("{res:?}"), Err(err) => eprintln!("{err:?}"), } } ``` #### Getting key details ```rust use unkey::models::GetKeyRequest; use unkey::Client; async fn get_key() { let c = Client::new("unkey_ABC"); let req = GetKeyRequest::new("key_123"); match c.get_key(req).await { Ok(res) => println!("{res:?}"), Err(err) => eprintln!("{err:?}"), } } ``` #### Update remaining verifications ```rust use unkey::models::{UpdateOp, UpdateRemainingRequest}; use unkey::Client; async fn update_remaining() { let c = Client::new("unkey_ABC"); let req = UpdateRemainingRequest::new("key_123", Some(100), UpdateOp::Set); match c.update_remaining(req).await { Ok(res) => println!("{res:?}"), Err(err) => eprintln!("{err:?}"), } } ``` *** ### Project Links * [Documentation](https://docs.rs/unkey) * [Repository](https://github.com/Jonxslays/unkey) * [Crate](https://crates.io/crates/unkey) ### Other useful links * [The Client](https://docs.rs/unkey/latest/unkey/struct.Client.html) * [Models](https://docs.rs/unkey/latest/unkey/models/index.html) # Get API Source: https://unkey.com/docs/libraries/springboot-java/api/get Retrieve information about an API Pass the optional and required parameters as per the official [API docs](https://unkey.com/docs/api-reference/apis/list-keys). See the DTO reference below for more information. ```java package com.example.myapp; import com.unkey.unkeysdk.dto.GetAPIResponse; @RestController public class APIController { private static IAPIService apiService = new APIService(); @GetMapping("/get-api") public GetAPIResponse getAPI( @RequestParam String apiId, @RequestHeader("Authorization") String authToken) { // Delegate the creation of the key to the IAPIService from the SDK return apiService.getAPI(apiId, authToken); } } ``` ### DTOs Reference The DTOs used in the code for a better understanding of request and response bodies. #### Response ```java public class GetAPIResponse { private String id; private String name; private String workspaceId; } ``` # List Keys Source: https://unkey.com/docs/libraries/springboot-java/api/list List API keys Pass the optional and required parameters as per the official [API docs](https://unkey.com/docs/api-reference/apis/list-keys). See the DTO reference below for more information. ```java package com.example.myapp; import com.unkey.unkeysdk.dto.GetAPIResponse; @RestController public class APIController { private static IAPIService apiService = new APIService(); @GetMapping("/keys") public ListKeysResponse listKeys( @RequestParam String apiId, @RequestBody(required = false) ListKeysRequest listKeyRquest, @RequestHeader("Authorization") String authToken) { // Delegate the creation of the key to the IAPIService from the SDK return iapiService.listKeys(listKeyRquest, apiId, authToken); } } ``` ### DTOs Reference The DTOs used in the code for a better understanding of request and response bodies. #### Request ```java public class ListKeysRequest { private String apiId; private Integer limit; private Integer offset; private String ownerId; } ``` #### Response ```java public class ListKeysResponse { private List keys; private Integer total; } ``` ```java public class KeyAttributes { private String id; private String apiId; private String workspaceId; private String start; private String name; private String ownerId; private Meta meta; private Long createdAt; private Long expires; private Integer remaining; private KeyRateLimit ratelimit; } ``` # Create Source: https://unkey.com/docs/libraries/springboot-java/functions/create Create an api key for your users Pass the optional and required parameters as per the official [API docs](https://unkey.com/docs/api-reference/apis/list-keys). See the DTO reference below for more information. ```java package com.example.myapp; import com.unkey.unkeysdk.dto.KeyCreateResponse; import com.unkey.unkeysdk.dto.KeyCreateRequest; @RestController public class APIController { private static IKeyService keyService = new KeyService(); @PostMapping("/createKey") public KeyCreateResponse createKey( @RequestBody KeyCreateRequest keyCreateRequest, @RequestHeader("Authorization") String authToken) { // Delegate the creation of the key to the KeyService from the SDK return keyService.createKey(keyCreateRequest, authToken); } } ``` ### DTOs Reference The DTOs used in the code for a better understanding of request and response bodies. #### Request ```java public class KeyCreateRequest { @NonNull private String apiId; private String prefix; private String name; private Integer byteLength; private String ownerId; private Meta meta; private Integer expires; private Integer remaining; private KeyRateLimit ratelimit; } ``` ```java public class Meta { private Map meta; } ``` ```java public class KeyRateLimit { private String type; private Integer limit; private Integer refillRate; private Integer refillInterval; } ``` #### Response ```java public class KeyCreateResponse { @NonNull private String key; @NonNull private String keyId; } ``` # revoke Source: https://unkey.com/docs/libraries/springboot-java/functions/revoke Revoke an api key Pass the optional and required parameters as per the official [API docs](https://unkey.com/docs/api-reference/apis/list-keys). See the DTO reference below for more information. ```java package com.example.myapp; import com.unkey.unkeysdk.dto.KeyDeleteRequest; @RestController public class APIController { private static IKeyService keyService = new KeyService(); @DeleteMapping("/delete") public ResponseEntity updateKey( @RequestBody KeyDeleteRequest keyId, @RequestHeader("Authorization") String authToken) { // Delegate the creation of the key to the KeyService return keyService.deleteKey(authToken, keyId); } } ``` ### DTOs Reference The DTOs used in the code for a better understanding of request and response bodies. #### Request ```java public class KeyDeleteRequest { private String keyId; } ``` #### Response "OK" # Update Source: https://unkey.com/docs/libraries/springboot-java/functions/update Update an api key Pass the optional and required parameters as per the official [API docs](https://unkey.com/docs/api-reference/apis/list-keys). See the DTO reference below for more information. ```java package com.example.myapp; @RestController public class APIController { private static IKeyService keyService = new KeyService(); @PutMapping("/update") public ResponseEntity updateKey( @RequestParam String keyId, @RequestBody Map keyUpdateRequest, @RequestHeader("Authorization") String authToken ) { // Delegate the creation of the key to the KeyService return keyService.updateKey(keyUpdateRequest, authToken, keyId); } } ``` ### DTOs Reference The DTOs used in the code for a better understanding of request and response bodies. #### Request Take the reference from the official [API docs](https://unkey.com/docs/api-reference/keys/update) for update request parameters. Only pass the parameters you want to update. #### Response "OK" # Verify Source: https://unkey.com/docs/libraries/springboot-java/functions/verify Verify an api key Pass the optional and required parameters as per the official [API docs](https://unkey.com/docs/api-reference/apis/list-keys). See the DTO reference below for more information. ```java package com.example.myapp; import com.unkey.unkeysdk.dto.KeyVerifyRequest; import com.unkey.unkeysdk.dto.KeyVerifyResponse; @RestController public class APIController { private static IKeyService keyService = new KeyService(); @PostMapping("/verify") public KeyVerifyResponse verifyKey( @RequestBody KeyVerifyRequest keyVerifyRequest) { // Delegate the creation of the key to the KeyService from the SDK return keyService.verifyKey(keyVerifyRequest); } } ``` ### DTOs Reference The DTOs used in the code for a better understanding of request and response bodies. ```java public class KeyVerifyResponse { @NonNull private Boolean valid; private String code; private String ownerId; private Long expires; private Object meta; private KeyVerifyRateLimit ratelimit; private Long remaining; } ``` ```java public class KeyVerifyRateLimit { private Integer limit; private Integer remaining; private Long reset; } ``` ```java public class KeyVerifyRequest { @NonNull private String key; private String apiId; } ``` # Overview Source: https://unkey.com/docs/libraries/springboot-java/overview Spring Boot client for unkey ## Configure Build File Add the Unkey SDK dependency to your `build.gradle` file: ```groovy plugins { id 'java' id 'org.springframework.boot' version '2.5.4' id 'io.spring.dependency-management' version '1.0.11.RELEASE' } group = 'com.example' version = '0.0.1-SNAPSHOT' java { sourceCompatibility = 1.8 } repositories { mavenCentral() maven { name = "GitHubPackages" url = uri("https://maven.pkg.github.com/shreyanshtomar/my-registry") } } dependencies { //..other dependencies implementation 'com.unkey:unkey-springboot-sdk:0.0.1-SNAPSHOT' } ``` ## Unkey Root Key When requesting resources, you will need your root key — you can create a new one in the [settings](https://app.unkey.com/settings/root-keys). Always keep your root key safe and reset it if you suspect it has been compromised. # @unkey/api Source: https://unkey.com/docs/libraries/ts/api Unkey's API provides programmatic access for all resources within our platform. ## SDK Installation The SDK can be installed with either [npm](https://www.npmjs.com/), [pnpm](https://pnpm.io/), [bun](https://bun.sh/) or [yarn](https://classic.yarnpkg.com/en/) package managers. ```bash npm npm add @unkey/api ``` ```bash pnpm pnpm add @unkey/api ``` ```bash bun bun add @unkey/api ``` ```bash yarn yarn add @unkey/api zod # Note that Yarn does not install peer dependencies automatically. You will need # to install zod as shown above. ``` {" "} This package is published with CommonJS and ES Modules (ESM) support. ## Requirements For supported JavaScript runtimes, please consult [RUNTIMES.md](https://github.com/unkeyed/sdks/blob/main/api/ts/RUNTIMES.md). ## SDK Example Usage ### Example ```typescript import { Unkey } from "@unkey/api"; const unkey = new Unkey({ rootKey: process.env["UNKEY_ROOT_KEY"], }); async function run() { const result = await unkey.apis.createApi({ name: "payment-service-production", }); console.log(result); } run(); ``` ## Repository {" "} The full autogenerated documentation can be found on GitHub. # @unkey/cache Source: https://unkey.com/docs/libraries/ts/cache/overview Cache middleware with types ## Motivation Everyone needs caching, but it's often poorly implemented. Not from a technical perspective but from a usability perspective. Caching should be easy to use, typesafe, and composable. How caching looks like in many applications: ```ts const cache = new Some3rdPartyCache(...) type User = { email: string }; let user = await cache.get("userId") as User | undefined | null; if (!user){ user = await database.get(...) await cache.set("userId", user, Date.now() + 60_000) } // use user ``` There are a few annoying things about this code: * Manual type casting * No support for stale-while-revalidate * Only checks a single cache Most people would build a small wrapper around this to make it easier to use and so did we: This library is the result of a rewrite of our own caching layer after some developers were starting to replicate it. It's used in production by Unkey any others. ## Features * **Typescript**: Fully typesafe * **Tiered Cache**: Multiple caches in series to fall back on * **Metrics**: Middleware for collecting metrics * **Stale-While-Revalidate**: Async loading of data from your origin * **Encryption**: Middleware for automatic encryption of cache values * **Composable**: Mix and match primitives to build what you need ## Quickstart ```bash npm install @unkey/cache ``` ```bash pnpm add @unkey/cache ``` ```bash yarn add @unkey/cache ``` ```bash bun install @unkey/cache ``` ```ts Hello World import { createCache, DefaultStatefulContext, Namespace } from "@unkey/cache"; import { MemoryStore } from "@unkey/cache/stores"; /** * Define the type of your data, * or perhaps generate the types from your database */ type User = { id: string; email: string; }; /** * In serverless you'd get this from the request handler * See /docs/libraries/ts/cache/overview#context */ const ctx = new DefaultStatefulContext(); const memory = new MemoryStore({ persistentMap: new Map() }); const cache = createCache({ user: new Namespace(ctx, { stores: [memory], fresh: 60_000, // Data is fresh for 60 seconds stale: 300_000, // Data is stale for 300 seconds }) }); await cache.user.set("userId", { id: "userId", email: "user@email.com" }); const user = await cache.user.get("userId") console.log(user) ``` ```ts Tiered Caches import { createCache, DefaultStatefulContext, Namespace } from "@unkey/cache"; import { CloudflareStore, MemoryStore } from "@unkey/cache/stores"; /** * In serverless you'd get this from the request handler * See /docs/libraries/ts/cache/overview#context */ const ctx = new DefaultStatefulContext(); /** * Define the type of your data, or perhaps generate the types from your database */ type User = { id: string; email: string; }; const memory = new MemoryStore({ persistentMap: new Map() }); const cloudflare = new CloudflareStore({ domain: "cache.unkey.dev", zoneId: env.CLOUDFLARE_ZONE_ID!, cloudflareApiKey: env.CLOUDFLARE_API_KEY!, }); const cache = createCache({ user: new Namespace(ctx, { /** * Specifying first `memory`, then `cloudflare` will automatically check both stores * in order. * If a value is found in memory, it is returned, else it will check cloudflare, * and if it's found in cloudflare, the value is backfilled to memory. */ stores: [memory, cloudflare], fresh: 60_000, // Data is fresh for 60 seconds stale: 300_000, // Data is stale for 300 seconds }); }); async function main() { await cache.user.set("userId", { id: "userId", email: "user@email.com" }); const user = await cache.user.get("userId"); console.info(user); } main(); ``` ```ts Multiple Namespaces import { createCache, DefaultStatefulContext, Namespace } from "@unkey/cache"; import { CloudflareStore, MemoryStore } from "@unkey/cache/stores"; /** * In serverless you'd get this from the request handler * See /docs/libraries/ts/cache/overview#context */ const ctx = new DefaultStatefulContext(); /** * Define the type of your data, or perhaps generate the types from your database */ type User = { id: string; email: string; }; const memory = new MemoryStore({ persistentMap: new Map() }); const cloudflare = new CloudflareStore({ domain: "cache.unkey.dev", zoneId: env.CLOUDFLARE_ZONE_ID!, cloudflareApiKey: env.CLOUDFLARE_API_KEY!, }); type ApiKey = { hash: string; ownerId: string; permissions: string[]; }; const cache = createCache({ user: new Namespace(ctx, { stores: [memory, cloudflare], fresh: 60_000, // Data is fresh for 60 seconds stale: 300_000, // Data is stale for 300 seconds }), apiKey:new Namespace(ctx, { stores: [memory], fresh: 10_000, // Data is fresh for 10 seconds stale: 60_000, // Data is stale for 60 seconds }), }); async function main() { await cache.user.set("userId", { id: "userId", email: "user@email.com" }); const user = await cache.user.get("userId"); console.info(user); await cache.apiKey.set("hash", {hash:"hash", ownerId: "me", permissions: ["do_many_things"]}) } main(); ``` ```ts import { Namespace, createCache } from "@unkey/cache"; import { MemoryStore, CloudflareStore} from "@unkey/cache/stores"; /** * Define your data types. * You can hopefully reuse some of these from your database models. */ type User = { email: string; }; type Account = { name: string; }; /** * Configure the swr cache defaults. */ const fresh = 60_000; // fresh for 1 minute const stale = 900_000; // stale for 15 minutes /** * Create your store instances */ const memory = new MemoryStore({ persistentMap: new Map() }); const cloudflare = new CloudflareStore({ cloudflareApiKey: "", zoneId: "", domain: "", }) /** * Create your cache instance */ const cache = createCache({ account: new Namespace(ctx, { stores: [memory], fresh, // use the defaults defined above or a custom value stale, }), user: new Namespace(ctx, { // tiered cache, checking memory first, then cloudflare stores: [memory, cloudflare], fresh, stale, }), }); await cache.account.set("key", { name: "x" }); const user = await cache.user.get("user_123"); // typescript error, because `email` is not a key of `Account` await cache.account.set("key", { email: "x" }); ``` ## Concepts ### Namespaces Namespaces are a way to define the type of data in your cache and apply settings to it. They are used to ensure that you don't accidentally store the wrong type of data in a cache, which otherwise can happen easily when you're changing your data structures. Each namespace requires a type parameter and is instantiated with a set of stores and cache settings. ```ts Constructor new Namespace(ctx, opts) ``` The type of data stored in this namespace, for example: ```ts type User = { email: string; }; ``` An execution context, such as a request or a worker instance. [Read more](/libraries/ts/cache/overview#context) ```ts interface Context { waitUntil: (p: Promise) => void; } ``` On Cloudflare workers or Vercel edge functions, you receive a context from the `fetch` handler. Otherwise you can use this: ```ts import { DefaultStatefulContext } from "@unkey/cache"; const ctx = new DefaultStatefulContext(); ``` An array of stores to use for this namespace. When providing multiple stores, the cache will be checked in order of the array until a value is found or all stores have been checked. You should order the stores from fastest to slowest, so that the fastest store is checked first. The time in milliseconds that a value is considered fresh. Cache hits within this time will return the cached value. Must be less than `stale`. The time in milliseconds that a value is considered stale. Cache hits within this time will return the cached value and trigger a background refresh. Must be greater than `fresh`. ```ts Example namespace with two stores import { Namespace, DefaultStatefulContext, MemoryStore, CloudflareStore } from "@unkey/cache"; type User = { email: string; } const memory = new MemoryStore({ persistentMap: new Map(), }); const cloudflare = new CloudflareStore({ cloudflareApiKey: c.env.CLOUDFLARE_API_KEY, zoneId: c.env.CLOUDFLARE_ZONE_ID, domain: "cache.unkey.dev", }) const ctx = new DefaultStatefulContext() const namespace = new Namespace(ctx, { stores: [memory, cloudflare], fresh: 60_000, stale: 900_000, }); ``` ### Tiered Cache Different caches have different characteristics, some may be fast but volatile, others may be slow but persistent. By using a tiered cache, you can combine the best of both worlds. In almost every case, you want to use a fast in-memory cache as the first tier. There is no reason not to use it, as it doesn't add any latency to your application. The goal of this implementation is that it's invisible to the user. Everything behaves like a single cache. You can add as many tiers as you want. #### Reading from the cache When using a tiered cache, all stores will be checked in order until a value is found or all stores have been checked. If a value is found in a store, it will be backfilled to the previous stores in the list asynchronously. ```mermaid sequenceDiagram autonumber App->>Cache: get key Cache->>+Tier1: get key Tier1->>-Cache: undefined Cache->>+Tier2: get key Tier2->>-Cache: value Cache->>App: value Cache-->>Tier1: async set key value ``` #### Writing to the cache When setting or deleting a key, every store will be updated in parallel. ```mermaid sequenceDiagram autonumber App->>Cache: set key value par Cache->>Tier1: set key value Tier1->>Cache: ack and Cache->>Tier2: set key value Tier2->>Cache: ack end Cache->>App: ack ``` #### Example ```ts import { DefaultStatefulContext, Namespace, createCache } from "@unkey/cache"; import { CloudflareStore, MemoryStore } from "@unkey/cache/stores"; /** * In serverless you'd get this from the request handler * See https://unkey.com/docs/libraries/ts/cache/overview#context */ const ctx = new DefaultStatefulContext(); /** * Define the type of your data, or perhaps generate the types from your database */ type User = { id: string; email: string; }; const memory = new MemoryStore({ persistentMap: new Map() }); /** * @see https://unkey.com/docs/libraries/ts/cache/overview#cloudflare */ const cloudflare = new CloudflareStore({ domain: "cache.unkey.dev", zoneId: env.CLOUDFLARE_ZONE_ID!, cloudflareApiKey: env.CLOUDFLARE_API_KEY!, }); const userNamespace = new Namespace(ctx, { /** * Specifying first `memory`, then `cloudflare` will automatically check both stores in order * If a value is found in memory, it is returned, else it will check cloudflare, and if it's found * in cloudflare, the value is backfilled to memory. */ stores: [memory, cloudflare], fresh: 60_000, // Data is fresh for 60 seconds stale: 300_000, // Data is stale for 300 seconds }); const cache = createCache({ user: userNamespace }); async function main() { await cache.user.set("userId", { id: "userId", email: "user@email.com" }); const user = await cache.user.get("userId"); console.log(user); } main(); ``` ### Stale-While-Revalidate To make data fetching as easy as possible, the cache offers a `swr` method, that acts as a pull through cache. If the data is fresh, it will be returned from the cache, if it's stale it will be returned from the cache and a background refresh will be triggered and if it's not in the cache, the data will be synchronously fetched from the origin. ```ts const user = await cache.user.swr("userId", async (userId) => { return database.exec("SELECT * FROM users WHERE id = ?", userId) }); ``` The cache key to fetch, just like when using `.get(key)` A callback function that will be called to fetch the data from the origin if it's stale or not in the cache. To understand what's happening under the hood, let's look at the different scenarios. `swr` works with tiered caches, but for simplicity, these charts may only show a single store. ```mermaid sequenceDiagram autonumber App->>Cache: swr(key, loadFromOrigin) Cache->>+Tier1: get key Tier1->>-Cache: fresh value Cache->>App: value ``` ```mermaid sequenceDiagram autonumber App->>Cache: swr(key, loadFromOrigin) Cache->>+Tier1: get key Tier1->>-Cache: stale value Cache->>App: value alt async Cache->>Origin: loadFromOrigin Origin->>Cache: value Cache->>Tier1: set key value end ``` ```mermaid sequenceDiagram autonumber App->>Cache: swr(key, loadFromOrigin) Cache->>+Tier1: get key Tier1->>-Cache: undefined Cache->>+Tier2: get key Tier2->>-Cache: undefined Cache->>Origin: loadFromOrigin Origin->>Cache: value Cache->>App: value alt async Cache->>Tier1: set key value Cache->>Tier2: set key value end ``` #### Example ```ts import { DefaultStatefulContext, Namespace, createCache } from "@unkey/cache" import { CloudflareStore, MemoryStore } from "@unkey/cache/stores"; /** * In serverless you'd get this from the request handler * See https://unkey.com/docs/libraries/ts/cache/overview#context */ const ctx = new DefaultStatefulContext(); /** * Define the type of your data, or perhaps generate the types from your database */ type User = { id: string; email: string; }; const memory = new MemoryStore({ persistentMap: new Map() }); /** * @see https://unkey.com/docs/libraries/ts/cache/overview#cloudflare */ const cloudflare = new CloudflareStore({ domain: "cache.unkey.dev", zoneId: env.CLOUDFLARE_ZONE_ID!, cloudflareApiKey: env.CLOUDFLARE_API_KEY!, }); const userNamespace = new Namespace(ctx, { /** * Specifying first `memory`, then `cloudflare` will automatically check both stores in order * If a value is found in memory, it is returned, else it will check cloudflare, and if it's found * in cloudflare, the value is backfilled to memory. */ stores: [memory, cloudflare], fresh: 60_000, // Data is fresh for 60 seconds stale: 300_000, // Data is stale for 300 seconds }); const cache = createCache({ user: userNamespace }); async function main() { await cache.user.set("userId", { id: "userId", email: "user@email.com" }); const user = await cache.user.swr("userId", async (userId)=>{ // @ts-expect-error we don't have a db in this example return db.getUser(userId) }); console.info(user); } main(); ``` ### Context In serverless functions it's not always trivial to run some code after you have returned a response. This is where the context comes in. It allows you to register promises that should be awaited before the function is considered done. Fortunately many providers offer a way to do this. In order to be used in this cache library, the context must implement the following interface: ```ts export interface Context { waitUntil: (p: Promise) => void; } ``` For stateful applications, you can use the `DefaultStatefulContext`: ```ts import { DefaultStatefulContext } from "@unkey/cache"; const ctx = new DefaultStatefulContext() ``` Vendor specific documentation: * [Cloudflare Workers](https://developers.cloudflare.com/workers/runtime-apis/context/) * [Vercel Serverless](https://vercel.com/docs/functions/functions-api-reference#waituntil) * [Vercel Edge and Middleware](https://vercel.com/docs/functions/edge-middleware/middleware-api#waituntil) ## Primitives ### Stores Stores are the underlying storage mechanisms for your cache. They can be in-memory, on-disk, or remote. You can use multiple stores in a namespace to create a tiered cache. The order of stores in a namespace is important. The cache will check the stores in order until it finds a value or all stores have been checked. You can create your own store by implementing the `Store` interface. [Read more.](/libraries/ts/cache/interface/store) Below are the available stores: #### Memory The memory store is an in-memory cache that is fast but only as persistent as your memory. In serverless environments, this means that the cache is lost when the function is cold-started. ```ts import { MemoryStore } from "@unkey/cache/stores"; const memory = new MemoryStore({ persistentMap: new Map(), }); ``` Ensure that the `Map` is instantiated in a persistent scope of your application. For Cloudflare workers or serverless functions in general, this is the global scope. #### Cloudflare The Cloudflare store uses cloudflare's [`Cache` API](https://developers.cloudflare.com/workers/runtime-apis/cache/) to store cache values. This is a remote cache that is shared across all instances of your worker but isolated per datacenter. It's still pretty fast, but needs a network request to access the cache. ```ts import { CloudflareStore } from "@unkey/cache/stores"; const cloudflare = new CloudflareStore({ cloudflareApiKey: "", zoneId: "", domain: "", cacheBuster: "", }) ``` The Cloudflare API key to use for cache purge operations. The api key must have the `Cache Purge` permission. You can create a new API token with this permission in the [Cloudflare dashboard](https://dash.cloudflare.com/profile/api-tokens). The Cloudflare zone ID where the cache is stored. You can find this in the Cloudflare dashboard. The domain to use for the cache. This must be a valid domain within the zone specified by `zoneId`. If the domain is not valid in the specified zone, the cache will not work and cloudflare does not provide an error message. You will just get cache misses. For example, we use `domain: "cache.unkey.dev"` in our API. As your data changes, it is important to keep backwards compatibility in mind. If your cached values are no longer backwards compatible, it can cause problems. For example when a value changes from optional to required. In these cases you should purge the entire cache by setting a new `cacheBuster` value. The `cacheBuster` is used as part of the cache key and changes ensure you are not reading old data anymore. #### Upstash Redis The Upstash Redis store uses the [Serverless Redis](https://upstash.com/docs/redis/overall/getstarted) offering from Upstash to store cache values. This is a serverless database with Redis compatibility. ```ts import { UpstashRedisStore } from "@unkey/cache/stores"; import { Redis } from "@upstash/redis"; const redis = new Redis({ url: , token: , }) const redisStore = new UpstashRedisStore({ redis }) ``` The Upstash Redis client to use for cache operations. #### libSQL (Turso) The libSQL store can use an [embedded SQLite database](https://docs.turso.tech/features/embedded-replicas/introduction), or a remote [Turso](https://turso.tech) database to store cache values. You must create a table in your Turso database with the following schema: ```sql CREATE TABLE IF NOT EXISTS cache ( key TEXT PRIMARY KEY, value TEXT NOT NULL, freshUntil INTEGER NOT NULL, staleUntil INTEGER NOT NULL ); ``` ```ts Remote Only import { LibSQLStore } from "@unkey/cache/stores"; import { createClient } from "@libsql/client"; const client = createClient({ url: "libsql://...", authToken: "...", }); const store = new LibSQLStore({ client, }); ``` ```ts Embedded Replicas import { LibSQLStore } from "@unkey/cache/stores"; import { createClient } from "@libsql/client"; const client = createClient({ url: "file:dev.db", syncUrl: "libsql://...", authToken: "...", }); const store = new LibSQLStore({ client, }); ``` The [libSQL client](https://docs.turso.tech/sdk/ts) to use for cache operations. The name of the database table name to use for cache operations. ### Middlewares #### Metrics The metrics middleware collects metrics about cache hits, misses, and backfills. It's useful for debugging and monitoring your cache usage. Using the metrics middleware requires a metrics sink. You can build your own sink by implementing the `Metrics` interface. For example we are using [axiom](https://axiom.co?ref=unkey). ```ts interface Metrics = Record> { /** * Emit a new metric event * */ emit(metric: TMetric): void; /** * flush persists all metrics to durable storage. * You must call this method before your application exits, metrics are not persisted automatically. */ flush(): Promise; } ``` Wrap your store with the metrics middleware to start collecting metrics. ```ts import { withMetrics } from "@unkey/cache/middleware"; const metricsSink = // your metrics sink const metricsMiddleware = withMetrics(metricsSink); const memory = new MemoryStore({ persistentMap: new Map() }); new Namespace(ctx, { // Wrap the store with the metrics middleware stores: [metricsMiddleware.wrap(memory)], // ... }); ``` The following metrics are emitted: ```ts type Metric = | { metric: "metric.cache.read"; key: string; hit: boolean; status?: "fresh" | "stale"; latency: number; tier: string; namespace: string; } | { metric: "metric.cache.write"; key: string; latency: number; tier: string; namespace: string; } | { metric: "metric.cache.remove"; key: string; latency: number; tier: string; namespace: string; }; ``` #### Encryption When dealing with sensitive data, you might want to encrypt your cache values at rest. You can encrypt a store by wrapping it with the `EncryptedStore`. All you need is a 32 byte base64 encoded key. You can generate one with openssl: ```bash Generate a new encryption key openssl rand -base64 32 ``` ```ts Example import { withEncryption } from "@unkey/cache/middleware"; const encryptionKey = "" const encryptionMiddleware = await withEncryption(encryptionKey) const memory = new Memory({..}) // or any other store const store = encryptionMiddleware.wrap(memory); ``` Values will be encrypted using `AES-256-GCM` and persisted in the underlying store. You can rotate your encryption key at any point, but this will essentially purge the cache. A SHA256 hash of the encryption key is used in the cache key, to allow for rotation without causing decryption errors. ## Contributing If you have a store or middleware you'd like to see in this library, please open an [issue](https://github.com/unkeyed/unkey/issues/new) or a pull request. # @unkey/hono Source: https://unkey.com/docs/libraries/ts/hono Hono.js middleware for authenticating API keys > Hono - \[炎] means flame🔥 in Japanese - is a small, simple, and ultrafast web framework for the Edges. It works on any JavaScript runtime: Cloudflare Workers, Fastly Compute\@Edge, Deno, Bun, Vercel, Netlify, Lagon, AWS Lambda, Lambda\@Edge, and Node.js. `@unkey/hono` offers a middleware for authenticating API keys with [unkey](https://unkey.com). ## Install ```bash npm npm install @unkey/hono ``` ```bash pnpm pnpm add @unkey/hono ``` ```bash yarn yarn add @unkey/hono ``` ```bash bun bun install @unkey/hono ``` Let's dive straight in. The minimal setup looks like this. All you need is your root key with permission to verify keys. Go to [/app/api](https://app.unkey.com/settings/root-keys) and create a key with the `verify_key` permission. By default it tries to grab the api key from the `Authorization` header and then verifies it with unkey. The result of the verification will be written to the context and can be access with `c.get("unkey")`. ```ts import { Hono } from "hono" import { type UnkeyContext, unkey } from "@unkey/hono"; const app = new Hono<{ Variables: { unkey: UnkeyContext } }>(); app.use("*", unkey({ rootKey: "" })); app.get("/somewhere", (c) => { // access the unkey response here to get metadata of the key etc const ... = c.get("unkey") return c.text("yo") }) ``` ## Customizing the middleware ### Header By default the middleware tries to grab the api key from the `Authorization` header. You can change this by passing a custom header name to the middleware. ```ts app.use( "*", unkey({ rootKey: "", getKey: (c) => c.req.header("x-api-key"), }) ); ``` If the header is missing the middleware will return a `401` error response like this ```ts c.json({ error: "unauthorized" }, { status: 401 }); ``` To customize the response in case the header is missing, just return a response from the `getKey` function. ```ts app.use( "*", unkey({ rootKey: "", getKey: (c) => { const key = c.req.header("x-api-key"); if (!key) { return c.text("missing api key", 401); } return key; }, }) ); ``` ### Handle errors ```ts app.use( "*", unkey({ rootKey: process.env.UNKEY_ROOT_KEY!, onError: (c, err) => { // handle error return c.text("unauthorized", 401); }, }) ); ``` ### Handle invalid keys By default the middleware will not do anything with the verification response other than writing it to the context. However you most likely would like to just return a `401` response if the key is invalid and not continue with the request. To do this you can pass a `handleInvalidKey` handler to the middleware. See [here](/api-reference/v2/keys/verify-api-key) for the full `response` object. ```ts app.use( "*", unkey({ rootKey: process.env.UNKEY_ROOT_KEY!, handleInvalidKey: (c, result) => { return c.json( { error: "unauthorized", reason: result.data.code, }, 401 ); }, }) ); ``` ### Pass verification tags You can pass tags to the verification request to help you filter keys later. ```ts (c, next) => unkey({ rootKey: process.env.UNKEY_ROOT_KEY!, tags: [`path=${c.req.path}`], })(c, next); ``` # @unkey/nextjs Source: https://unkey.com/docs/libraries/ts/nextjs Next.js SDK for Unkey The official Next.js SDK for Unkey. Use this within your [route handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) as a simple, type-safe way to verify API keys. ## Install ```bash npm npm install @unkey/nextjs ``` ```bash pnpm pnpm add @unkey/nextjs ``` ```bash yarn yarn add @unkey/nextjs ``` ```bash bun bun add @unkey/nextjs ``` Protecting API routes is as simple as wrapping them with the `withUnkey` handler: ```ts import { NextRequestWithUnkeyContext, withUnkey } from "@unkey/nextjs"; export const POST = withUnkey( async (req: NextRequestWithUnkeyContext) => { // Process the request here // You have access to the verification response using `req.unkey` console.log(req.unkey); return new Response("Your API key is valid!"); }, { rootKey: process.env.UNKEY_ROOT_KEY! } ); ); ``` If you want to customize how `withUnkey` processes incoming requests, you can do so as follows: ### `getKey` By default, withUnkey will look for a bearer token located in the `authorization` header. If you want to customize this, you can do so by passing a getter in the configuration object: ```ts export const GET = withUnkey( async (req) => { // ... }, { rootKey: process.env.UNKEY_ROOT_KEY!, getKey: (req) => new URL(req.url).searchParams.get("key"), } ``` ### `onError` You can specify custom error handling. By default errors will be logged to the console, and `withUnkey` will return a NextResponse with status 500. ```ts export const GET = withUnkey( async (req) => { // ... }, { rootKey: process.env.UNKEY_ROOT_KEY!, onError: async (req, res) => { await analytics.trackEvent(`Error ${res.code}: ${res.message}`); return new NextResponse("Unkey error", { status: 500 }); }, } ); ``` ### `handleInvalidKey` Specify what to do if Unkey reports that your key is invalid. ```ts export const GET = withUnkey( async (req) => { // ... }, { rootKey: process.env.UNKEY_ROOT_KEY!, handleInvalidKey: (req, res) => { return new Response("Unauthorized", { status: 401 }); }, } ); ``` # Delete Override Source: https://unkey.com/docs/libraries/ts/ratelimit/override/delete-override Deletes an override ## Request Identifier of your user, this can be their userId, an email, an ip or anything else. Wildcards ( \* ) can be used to match multiple identifiers, More info can be found at [https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules](https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules) Either `namespaceId` or `namespaceName` is required. Not both. The id of the namespace. Either namespaceId or namespaceName must be provided Namespaces group different limits together for better analytics. You might have a namespace for your public API and one for internal tRPC routes. Wildcards can also be used, more info can be found at [https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules](https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules) ## Response No response, but if no error is returned the override has been deleted successfully. ```ts await unkey.deleteOverride({ identifier: "user_123", namespaceName: "email.outbound", }) ``` ```ts await unkey.deleteOverride({ identifier: "user_123", namespaceId:"rlns_12345", }) ``` # Get Override Source: https://unkey.com/docs/libraries/ts/ratelimit/override/get-override Gets a ratelimit override ## Request Identifier of your user, this can be their userId, an email, an ip or anything else. Wildcards ( \* ) can be used to match multiple identifiers, More info can be found at [https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules](https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules) Either `namespaceId` or `namespaceName` is required. Not both. The id of the namespace. Either namespaceId or namespaceName must be provided Namespaces group different limits together for better analytics. You might have a namespace for your public API and one for internal tRPC routes. Wildcards can also be used, more info can be found at [https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules](https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules) ## Response Identifier of the override requested Identifier of your user, this can be their userId, an email, an ip or anything else. Wildcards ( \* ) can be used to match multiple identifiers, More info can be found at [https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules](https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules) How many requests may pass in a given window. The window duration in milliseconds. ```ts const override = await unkey.getOverride({ identifier:"user.example", namespaceName: "email.outbound" }); ``` ```ts const override = await unkey.getOverride({ identifier:"user.example", namespaceId: "rlns_1234", }); ``` ```ts { result: { id: "rlor_4567", identifier: "user.example", limit: 10, duration: 60000, async: false } } ``` # List Overrides Source: https://unkey.com/docs/libraries/ts/ratelimit/override/list-overrides Lists all overrides ## Request Either `namespaceId` or `namespaceName` is required. Not both. The id of the namespace. Either namespaceId or namespaceName must be provided Namespaces group different limits together for better analytics. You might have a namespace for your public API and one for internal tRPC routes. Wildcards can also be used, more info can be found at [https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules](https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules) ## Response Identifier of the override requested Identifier of your user, this can be their userId, an email, an ip or anything else. Wildcards ( \* ) can be used to match multiple identifiers, More info can be found at [https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules](https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules) How many requests may pass in a given window. The window duration in milliseconds. The total number of overrides The cursor to use for pagination ```ts const overrides = await unkey.listOverrides({ namespaceName: "email.outbound" }); ``` ```ts const overrides = await unkey.listOverrides({ nameSpaceId:"rlns_12345", }); ``` ```ts { result: { overrides: [ { id: 'rlor_1234', identifier: 'customer_123', limit: 10, duration: 50000, async: false } ], total: 1, cursor: 'rlor_1234' } } ``` # Overview Source: https://unkey.com/docs/libraries/ts/ratelimit/override/overview Ratelimit overrides Ratelimit overrides are a way to override the ratelimit for specific users or group using an identifier. ## Configure your override ```ts import { Override } from "@unkey/ratelimit" const unkey = new Override({ rootKey: process.env.UNKEY_ROOT_KEY, }) ``` ## Use it ```ts async function handler(request) { const identifier = request.getUserId() // or ip or anything else you want const override = await unkey.setOverride({ identifier: identifier, limit: 10, duration: 60000, namespaceName: "email.outbound", }) if (override.error){ // handle the error here console.error(override.error.message); return; } // handle the request here } ``` There are four main functions to interact with overrides: * [setOverride](/libraries/ts/ratelimit/override/set-override) Sets an override for a ratelimit. * [getOverride](/libraries/ts/ratelimit/override/get-override) Gets a ratelimit override. * [deleteOverride](/libraries/ts/ratelimit/override/delete-override) Deletes an override. * [listOverrides](/libraries/ts/ratelimit/override/list-overrides) Lists all overrides for a namnespace. # Set Override Source: https://unkey.com/docs/libraries/ts/ratelimit/override/set-override Sets an override for a ratelimit ## Request Identifier of your user, this can be their userId, an email, an ip or anything else. Wildcards ( \* ) can be used to match multiple identifiers, More info can be found at [https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules](https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules) How many requests may pass in a given window. The window duration in milliseconds. Either `namespaceId` or `namespaceName` is required. Not both. The id of the namespace. Either namespaceId or namespaceName must be provided Namespaces group different limits together for better analytics. You might have a namespace for your public API and one for internal tRPC routes. Wildcards can also be used, more info can be found at [https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules](https://www.unkey.com/docs/ratelimiting/overrides#wildcard-rules) ## Response The id of the override that was set. ```ts const override = await unkey.setOverride({ identifier: "user_123", limit: 10, duration: 60000, namespaceName: "email.outbound", async: true }) ``` ```ts const override = await unkey.setOverride({ identifier: "user_123", limit: 5, duration: 50000, namespaceId: "rlns_1234", async: false }) ``` ```ts { result: { overrideId: 'rlor_12345' } } ``` # Ratelimit Source: https://unkey.com/docs/libraries/ts/ratelimit/ratelimit Serverless ratelimiting `@unkey/ratelimit` is a library for fast global ratelimiting in serverless functions. ## Install ```bash npm install @unkey/ratelimit ``` ```bash pnpm add @unkey/ratelimit ``` ```bash yarn add @unkey/ratelimit ``` ```bash bun install @unkey/ratelimit ``` ## Configure your ratelimiter ```ts import { Ratelimit } from "@unkey/ratelimit" const unkey = new Ratelimit({ rootKey: process.env.UNKEY_ROOT_KEY, namespace: "my-app", limit: 10, duration: "30s", }) ``` ## Use it ```ts async function handler(request) { const identifier = request.getUserId() // or ip or anything else you want const ratelimit = await unkey.limit(identifier) if (!ratelimit.success){ return new Response("try again later", { status: 429 }) } // handle the request here } ``` ## Making it bullet proof Everything we do is built for scale and stability. We built on some of the world's most stable platforms ([Planetscale](https://planetscale.com/) and [Cloudflare](https://www.cloudflare.com)) and run an extensive test suite before and after every deployment. Even so, we would be fools if we wouldn't explain how you can put in safe guards along the way. In case of severe network degredations or other unforseen events, you might want to put an upper bound on how long you are willing to wait for a response from unkey. By default the SDK will reject a request if it hasn't received a response from unkey within 5 seconds. You can tune this via the `timeout` config in the constructor (see below). The SDK captures most errors and handles them on its own, but we also encourage you to add a `onError` handler to configure what happens in case something goes wrong. Both `fallback` property of the `timeout` config and `onError` config are callback functions. They receive the original request identifier as one of their parameters, which you can use to determine whether to reject the request. ```ts import { Ratelimit } from "@unkey/ratelimit" // In this example we decide to let requests pass, in case something goes wrong. // But you can of course also reject them if you want. const fallback = (identifier: string) => ({ success: true, limit: 0, reset: 0, remaining: 0 }) const unkey = new Ratelimit({ // ... standard stuff timeout: { ms: 3000, // only wait 3s at most before returning the fallback fallback }, onError: (err, identifier) => { console.error(`${identifier} - ${err.message}`) return fallback(identifier) } }) const { success } = await unkey.limit(identifier) ``` *** ## API ### `new Ratelimit(config: RatelimitConfig)` Create a new instance for ratelimiting by providing the necessary configuration. How many requests may pass in the given duration. How long the window should be. Either a type string literal like `60s`, `20m` or plain milliseconds. The unkey root key. You can create one at [app.unkey.com/settings/root-keys](https://app.unkey.com/settings/root-keys) Make sure the root key has permissions to use ratelimiting. Namespaces allow you to separate different areas of your app and have isolated limits. Make sure the root key has permissions to use ratelimiting. Configure a timeout to prevent network issues from blocking your function for too long. Disable it by setting `timeout: false` Timeouts rely on `Date.now()`. In cloudflare workers time doesn't progress unless there is some io happening, which means the timeout might not work as expected. Other runtimes are working. Time in milliseconds until the response is returned. A custom response to return when the timeout is reached. The important bit is the `success` value, choose whether you want to let requests pass or not. Configure what happens for unforeseen errors Example letting requests pass: ```ts onError: ()=> ({ success: true, limit: 0, remaining: 0, reset: 0}) ``` Example rejecting the request: ```ts onError: ()=> ({ success: true, limit: 0, remaining: 0, reset: 0}) ``` ### `.limit(identifier: string, opts: LimitOptions): Promise` Check whether a specific identifier is currently allowed to do something or if they have currently exceeded their limit. Expensive requests may use up more resources. You can specify a cost to the request and we'll deduct this many tokens in the current window. If there are not enough tokens left, the request is denied. **Example:** 1. You have a limit of 10 requests per second you already used 4 of them in the current window. 2. Now a new request comes in with a higher cost: ```ts const res = await rl.limit("identifier", { cost: 4 }) ``` 3. The request passes and the current limit is now at `8` 4. The same request happens again, but would not be rejected, because it would exceed the limit in the current window: `8 + 4 > 10` ### `RatelimitResponse` Whether the request may pass(true) or exceeded the limit(false). Maximum number of requests allowed within a window. How many requests the user has left within the current window. Unix timestamp in milliseconds when the limits are reset. # Overview Source: https://unkey.com/docs/migrations/introduction Migrate your API to Unkey Migration endpoints are currently only available in our v1 API. Bulk operations for v2 are planned for a future release. Wherever you are coming from, we make it easy to migrate your existing API to Unkey. The challenge with migrating an existing API is that you need to move all of the existing keys and permissions to Unkey. This can be a daunting task, especially if you have a lot of keys. Common migrations can be done entirely via self-serve APIs, while more complex migrations may require some manual intervention. We are here to help you with your migration. Every system is slightly different, please contact us at [support@unkey.dev](mailto:support@unkey.dev) for help with migrating your keys. # Migrate keys to Unkey Source: https://unkey.com/docs/migrations/keys Migration endpoints are currently only available in our v1 API. Bulk operations for v2 are planned for a future release. ## Prerequisites * Created your [Unkey account](https://app.unkey.com/auth/sign-up) * Created an API in the [Unkey dashboard](https://app.unkey.com/apis) * Create a [root key](https://app.unkey.com/settings/root-keys) with the `api.*.create_key` permission If you plan to encrypt your keys, please contact us at [support@unkey.dev](mailto:support@unkey.dev) to flag you in. The root key used for migrating must have the `api.*.encrypt_key` permission to encrypt keys. Extracting keys from your current system is likely the hardest part. It depends on how your keys are stored and how you can access them. Some providers have APIs to list all keys, while others require you to manually export them, some can provide the real key, and some only provide a hashed version. Regardless of how you get your keys, you will need to provide either the plaintext key or the hash, as well as other settings to Unkey via the [migrations.createKeys](/api-reference/migrations/create-keys) endpoint. ## Nodejs Example ```js Hash const { createHash } = require("node:crypto") function hash(key) { return { value: createHash("sha256").update(key).digest("base64"), variant: "sha256_base64", } } const keys = [ { hash: hash("my-secret-key"), ownerId: "hello", apiId: "", // the id of the API you created //... other settings }, { hash: hash("my-other-secret-key"), name: "name", apiId: "", // the id of the API you created //... other settings }, ] fetch("https://api.unkey.dev/v1/migrations.createKeys", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer ", }, body: JSON.stringify(keys) }) .then(res=>res.json()) .then(res=>{console.log(res)}) ``` ```js Plaintext const keys = [ { plaintext: "my_plaintext_key", ownerId: "hello", apiId: "", // the id of the API you created //... other settings }, { plaintext: "my_other_plaintext_key", name: "name", apiId: "", // the id of the API you created //... other settings }, ] fetch("https://api.unkey.dev/v1/migrations.createKeys", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer ", // requires `api.*.encrypt_key` permissions }, body: JSON.stringify(keys) }) .then(res=>res.json()) .then(res=>{console.log(res)}) ``` # Bun Source: https://unkey.com/docs/quickstart/apis/bun Authentication for Bun's http server ## Prerequisites * Created your [Unkey account](https://app.unkey.com/auth/sign-up) * Created an API in the [Unkey dashboard](https://app.unkey.com/apis) Don't want to read the tutorial? Click this to get an example ready to test. ## Creating a bun server protected by Unkey First we need a bun project, so create a new directory and init bun. ```bash mkdir unkey-with-bun cd unkey-with-bun bun init -y ``` Now install the `@unkey/api` package ```bash bun install @unkey/api@0.35 ``` Open up the file called `index.ts` and add the following code ```ts index.ts import { verifyKey } from "@unkey/api"; const server = Bun.serve({ async fetch(req) { const key = req.headers.get("Authorization")?.replace("Bearer ", ""); if (!key) { return new Response("Unauthorized", { status: 401 }); } const { result, error } = await verifyKey(key); if (error) { // This may happen on network errors // We already retry the request 5 times, but if it still fails, we return an error console.error(error); return Response.json("Internal Server Error", { status: 500 }); } if (!result.valid) { return new Response("Unauthorized", { status: 401 }); } return Response.json(result); }, port: 8000, }); console.log(`Listening on ${server.url}`); ``` ```bash bun run index.ts ``` Go to `https://app.unkey.com` and create a new key. Then verify it with our new server: ```bash curl http://localhost:8000 -H "Authorization: Bearer " ``` It should return `{"keyId":"key_id","valid":true,"meta":{},"enabled":true,"permissions":[],"code":"VALID"}` and potentially more information about the key, depending on what you set up in the dashboard. ## What is next? Now that you've seen the power of Unkey, check out some resources below to continue your journey. Join our Discord to chat with us and the community Learn about our API that helps you manage APIs, keys, ratelimits and analytical data. Check out our SDKs and how they fit into your Bun application. # Express Source: https://unkey.com/docs/quickstart/apis/express Authentication for your Express server ## Prerequisites * Created your [Unkey account](https://app.unkey.com/auth/sign-up) * Created an API in the [Unkey dashboard](https://app.unkey.com/apis) Don't want to read the tutorial? Click this to get an example ready to test. ## Creating an express server First run the following: ```bash mkdir unkey-with-express npm init -y npm install cors dotenv express ts-node npm install -D @types/cors @types/express ts-node-dev typescript ``` Then update your package.json to have the following ```json "scripts": { "start": "ts-node ./index.ts", "build": "tsc", "serve": "node dist/index.js" }, ``` Now install the `@unkey/api` package ```bash npm install @unkey/api@0.35 ``` Create a file called `server.ts` and add the following code ```ts server.ts import express, { Request, Response, Application } from 'express'; import dotenv from 'dotenv'; import { verifyKey } from '@unkey/api'; //For env File dotenv.config(); const app: Application = express(); const port = process.env.PORT || 8000; const apiId = process.env.UNKEY_API_ID // copy this from the dashboard app.get('/', (req: Request, res: Response) => { res.send('Welcome to Express & TypeScript Server'); }); // This endpoint is protected by Unkey app.get('/secret', async (req: Request, res: Response) => { const authHeader = req.headers["authorization"] const key = authHeader?.toString().replace("Bearer ", ""); if (!key) { return res.status(401).send("Unauthorized") } const { result, error } = await verifyKey({ key, apiId }); if (error) { // This may happen on network errors // We already retry the request 5 times, but if it still fails, we return an error console.error(error); res.status(500); return res.status(500).send("Internal Server Error") } if (!result.valid) { res.status(401); return res.status(401).send("Unauthorized") } return res.status(200).send(JSON.stringify(result)); }) app.listen(port, () => { console.log(`Server is listening at http://localhost:${port}`); }); ``` ```bash npm run start ``` Go to [https://app.unkey.com](https://app.unkey.com) and create a new key. Then verify it with our new server: ```bash curl 'http://localhost:8000/secret' \ -H 'Authorization:Bearer ' ``` It should return `{"keyId":"key_id","valid":true,"meta":{},"enabled":true,"permissions":[],"code":"VALID"}` and potentially more information about the key, depending on what you set up in the dashboard. ## What is next? Now that you've seen the power of Unkey, check out some resources below to continue your journey. Join our Discord to chat with us and the community Learn about our API that helps you manage APIs, keys, ratelimits and analytical data. Check out our SDKs and how they fit into your Express application. # Hono Source: https://unkey.com/docs/quickstart/apis/hono API Authentication in Hono ## Prerequisites * Created your [Unkey account](https://app.unkey.com/auth/sign-up) * Created an API in the [Unkey dashboard](https://app.unkey.com/apis) Don't want to read the tutorial? Click this to get an example ready to test. Run the following command to create your Hono project ```bash npm create hono@latest ``` ```bash pnpm create hono@latest ``` ```bash yarn create hono@latest ``` ```bash bun create hono@latest ``` Now install the `@unkey/hono` package ```bash npm install @unkey/hono ``` ```bash pnpm add @unkey/hono ``` ```bash yarn add @unkey/hono ``` ```bash bun install @unkey/hono ``` Create a new route and add the following code ```ts /src/index.ts import { Hono } from "hono"; import { unkey, UnkeyContext } from "@unkey/hono"; const app = new Hono<{ Variables: { unkey: UnkeyContext } }>(); app.use("*", unkey()); app.get("/", (c) => { return c.text("Hello Hono!"); }); export default app; ``` ```bash bun run dev ``` ```bash pnpm run dev ``` ```bash yarn run dev ``` ```bash npm run dev ``` Go to [https://app.unkey.com](https://app.unkey.com) and create a new key. Then verify it with our new server: ```bash curl -XPOST 'http://localhost:8787/' \ -H "Authorization: Bearer " ``` It should return `Hello Hono!"` ## What is next? Now that you've seen the power of Unkey, check out some resources below to continue your journey. Join our Discord to chat with us and the community Learn about our API that helps you manage APIs, keys, ratelimits and analytical data. Check out our Hono SDK and how they fit into your API. # Next.js Source: https://unkey.com/docs/quickstart/apis/nextjs API Authentication in Next.js ## Prerequisites * Created your [Unkey account](https://app.unkey.com/auth/sign-up) * Created an API in the [Unkey dashboard](https://app.unkey.com/apis) We also have a [Next.js example](https://github.com/unkeyed/examples/tree/main/nextjs) ready to deploy on Vercel. [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Funkeyed%2Funkey%2Ftree%2Fmain%2Fexamples%2Fnextjs\&project-name=unkey-with-nextjs\&repository-name=unkey-with-nextjs\&integration-ids=oac_D84Ib6K2pS6CDQXxQbzsYxsh) Run the following command to init your Next.js project ```bash npx create-next-app@latest ``` ```bash pnpm create next-app@latest ``` ```bash yarn create-next-app@latest ``` ```bash bunx create-next-app ``` Now install the `@unkey/nextjs` package ```bash npm install @unkey/nextjs ``` ```bash pnpm add @unkey/nextjs ``` ```bash yarn add @unkey/nextjs ``` ```bash bun install @unkey/nextjs ``` Create a new route and add the following code ```ts /app/protected/route.ts import { NextRequestWithUnkeyContext, withUnkey } from '@unkey/nextjs'; import { NextResponse } from 'next/server'; export const POST = withUnkey(async (req) => { if (!req.unkey.valid) { return new NextResponse('unauthorized', { status: 403 }); } // Process the request here // You have access to the verification response using `req.unkey` console.log(req.unkey); return new NextResponse('Your API key is valid!'); }); ``` ```bash bun run dev ``` ```bash pnpm run dev ``` ```bash yarn run dev ``` ```bash npm run dev ``` Go to [https://app.unkey.com](https://app.unkey.com) and create a new key. Then verify it with our new server: ```bash curl -XPOST 'http://localhost:3000/protected' \ -H "Authorization: Bearer " ``` It should return `"Your API key is valid!"` and log out `{"keyId":"key_id","valid":true,"meta":{},"enabled":true,"permissions":[],"code":"VALID"}` and potentially more information about the key, depending on what you set up in the dashboard. ## What is next? Now that you've seen the power of Unkey, check out some resources below to continue your journey. Join our Discord to chat with us and the community Learn about our API that helps you manage APIs, keys, ratelimits and analytical data. Check out our Next.js SDK and how they fit into your Next application. # Quickstart Source: https://unkey.com/docs/quickstart/identities/shared-ratelimits Create your first identity and key with Unkey This quickstart will guide you through creating your first identity with shared ratelimits and a key that is connected to the identity. The example is written in TypeScript, purposefully using the `fetch` API to make requests as transparent as possible. You can use any language or library to make requests to the Unkey API. ### Requirements You will need your api id and root key to make requests to the Unkey API. You can find these in the Unkey dashboard. ```ts const apiId = "api_XXX"; const rootKey = "unkey_XXX"; ``` The root key requires the following permissions: ```ts "identity.*.create_identity" "identity.*.read_identity" "identity.*.update_identity" "api.*.create_key" ``` ### Create an Identity To create an identity, you need to make a request to the `/v2/identities.createIdentity` endpoint. You can specify an `externalId` and `meta` object to store additional information about the identity. Unkey does not care what the `externalId` is, but it must be unique for each identity. Commonly used are user or organization ids. The `meta` object can be used to store any additional information you want to associate with the identity. ```ts const externalId = "user_1234abc"; const createIdentityResponse = await fetch("https://api.unkey.com/v2/identities.createIdentity", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${rootKey}`, }, body: JSON.stringify({ externalId, meta: { stripeCustomerId: "cus_123", }, }), }); const { identityId } = await createIdentityResponse.json<{ identityId: string; }>(); ``` ### Retrieve an Identity Let's retrieve the identity to make sure it got created successfully ```ts const getIdentityResponse = await fetch(`https://api.unkey.com/v2/identities.getIdentity`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${rootKey}`, }, body: JSON.stringify({ identity: identityId, }), }); const identity = await getIdentityResponse.json<{ id: string; externalId: string; meta: unknown; ratelimits: Array<{ name: string; limit: number; duration: number }>; }>(); ``` ### Create a Key Let's create a key and connect it to the identity ```ts const createKeyResponse = await fetch(`https://api.unkey.com/v2/keys.createKey`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${rootKey}`, }, body: JSON.stringify({ apiId: apiId, prefix: "acme", // by providing the same externalId as the identity, we connect the key to the identity externalId: externalId, }), }); const key = await createKeyResponse.json<{ keyId: string; key: string; }>(); ``` ### Verify the Key When you verify the key, you will receive the identity that the key is connected to and can act accordingly in your API handler. ```ts const verifyKeyResponse = await fetch(`https://api.unkey.com/v2/keys.verifyKey`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ apiId: apiId, key: key.key, }), }); const verified = await verifyKeyResponse.json<{ valid: boolean; identity: { id: string; externalId: string; meta: unknown; }; }>(); ``` ### Ratelimits Ratelimits can be set on the identity level. Ratelimits set on the identity level are shared across all keys connected to the identity. ```ts const updateRes = await fetch("https://api.unkey.com/v2/identities.updateIdentity", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${rootKey}`, }, body: JSON.stringify({ identity: identity.id, ratelimits: [ /** * We define a limit that allows 10 requests per day */ { name: "requests", limit: 10, duration: 24 * 60 * 60 * 1000, // 24h }, /** * And a second limit that allows 1000 tokens per minute */ { name: "tokens", limit: 1000, duration: 60 * 1000, // 1 minute }, ], }), }); ``` ### Verify the Key with Ratelimits Now let's verify the key again and specify the limits In this case, we pretend like a user is requesting to use 200 tokens. We specify the `requests` ratelimit to enforce a limit of 10 requests per day and the `tokens` ratelimit to enforce a limit of 1000 tokens per minute. Additionally we specify the cost of the tokens to be 200. ```ts const verifiedWithRatelimitsResponse = await fetch(`https://api.unkey.com/v2/keys.verifyKey`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ apiId: apiId, key: key.key, ratelimits: [ { name: "requests", }, { name: "tokens", cost: 200, }, ], }), }); const verifiedWithRatelimits = await verifiedWithRatelimitsResponse.json<{ valid: boolean; identity: { id: string; externalId: string; meta: unknown; }; }>(); ``` That's it, you have successfully created an identity and key with shared ratelimits. You can now use the key to verify requests and enforce ratelimits in your API handler. # Public API Protection Source: https://unkey.com/docs/quickstart/onboarding/onboarding-api Get started with API keys ## 1. Create your Unkey account The first step to using Unkey is to create an account. You can do this by visiting [app.unkey.com](https://app.unkey.com) or by clicking the Dashboard button in the top right of your screen. Sign Up Page for Unkey ## 2. Choose how you want to use Unkey As soon as you create your account you will be asked to choose how you want to use Unkey. Your options are: * **API Keys**: This is used for protecting your public API. * **Ratelimiting**: This is global low latency ratelimiting for your application. > You can of course use both, but for now we'll just choose API Keys. Choose Unkey usage ## 3. Create your first API Next we will get you to create your first API. This is the API that you will be protecting with Unkey. You can create as many APIs as you like, but for now we'll just create one. Create your API ## 4. Follow the Unkey tutorial Next we will show you the basics of how to use Unkey. You can skip this step if you like, but we recommend you follow along. Unkey tutorial ## 5. Next Steps You should get to know our [API reference](/api-reference/authentication), as you can add additonal fields to your request when issuing a key. You can also check out the [Features](/apis/features/ratelimiting) section for more information on how to use Unkey. # Ratelimiting Source: https://unkey.com/docs/quickstart/onboarding/onboarding-ratelimiting Get started with standalone ratelimiting ## 1. Create your Unkey account The first step to using Unkey is to create an account. You can do this by visiting [app.unkey.com](https://app.unkey.com) or by clicking the Dashboard button in the top right of your screen. Sign Up Page for Unkey ## 2. Choose how you want to use Unkey As soon as you create your account you will be asked to choose how you want to use Unkey. Your options are: * **API Keys**: This is used for protecting your public API. * **Ratelimiting**: This is global low latency ratelimiting for your application. > You can of course use both, but for this choose Ratelimiting. Choose Unkey usage ## 3. Follow the Unkey tutorial Next we will show you the basics of how to use Unkey Ratelimiting. You can skip this step if you like, but we recommend you follow along. Unkey Ratelimit ## 4. Next Steps You should get to know our [API reference](/api-reference/ratelimits/limit), as you can add additonal fields to your request when ratelimiting endpoints. # Bun Source: https://unkey.com/docs/quickstart/ratelimiting/bun Ratelimiting endpoints with Bun's http server ## Prerequisites * Created your [Unkey account](https://app.unkey.com/auth/sign-up) * Created an [Unkey root key](https://app.unkey.com/settings/root-keys) with `ratelimit.*.create_namespace` and `ratelimit.*.limit` permissions. ## Creating a bun server protected by Unkey First we need a bun project, so create a new directory and init bun. ```bash mkdir unkey-with-bun cd unkey-with-bun bun init -y ``` Now install the `@unkey/ratelimit` package ```bash bun install @unkey/ratelimit ``` Add your root key to your `.env` file ```bash UNKEY_ROOT_KEY="YOUR_KEY" ``` Open up the file called `index.ts` and add the following code ```ts index.ts import { Ratelimit } from "@unkey/ratelimit"; /** This can be a seperate util for easy configurable ratelimiting across multiple routes. namespace = The route identifier you would like to ratelimit limit = The amount of requests duration = amount of time to limit against for example "30s" **/ const limiter = new Ratelimit({ namespace: "bun-example", limit: 2, duration: "30s", rootKey: process.env.UNKEY_ROOT_KEY }) const server = Bun.serve({ async fetch(req) { const identifier = req.getUserId() // or ip or anything else you want const ratelimit = await limiter.limit(identifier) if (!ratelimit.success){ return Response("try again later", { status: 429 }) } return return new Response("Success", { status: 200 }); }, port: 8000, }); console.log(`Listening on ${server.url}`); ``` ```bash bun run index.ts ``` ```bash curl http://localhost:8000 ``` You will need to curl a few times to see the ratelimiting error. Once you do, you, you will need to wait to perform the action again. ## What is next? Now that you've seen the power of Unkey, check out some resources below to continue your journey. Join our Discord to chat with us and the community Learn about our API that helps you manage APIs, keys, ratelimits and analytical data. Check out our SDKs and how they fit into your Bun application. # Express Source: https://unkey.com/docs/quickstart/ratelimiting/express Ratelimiting endpoints with Express ## Prerequisites * Created your [Unkey account](https://app.unkey.com/auth/sign-up) * Created an [Unkey root key](https://app.unkey.com/settings/root-keys) with `ratelimit.*.create_namespace` and `ratelimit.*.limit` permissions. ## Creating an express server First run the following: ```bash mkdir unkey-with-express npm init -y npm install cors dotenv express ts-node npm install -D @types/cors @types/express ts-node-dev typescript ``` Then update your package.json to have the following ```json "scripts": { "start": "ts-node ./index.ts", "build": "tsc", "serve": "node dist/index.js" }, ``` Now install the `@unkey/ratelimit` package ```bash npm install @unkey/ratelimit ``` Add your root key to your `.env` file ```bash UNKEY_ROOT_KEY="YOUR_KEY" ``` Create a file called `server.ts` and add the following code ```ts server.ts import express, { Request, Response, Application } from 'express'; import dotenv from 'dotenv'; import { Ratelimit } from '@unkey/ratelimit'; //For env File dotenv.config(); const app: Application = express(); const port = process.env.PORT || 8000; /** This can be a seperate util for easy configurable ratelimiting across multiple routes. namespace = The route identifier you would like to ratelimit limit = The amount of requests duration = amount of time to limit against for example "30s" **/ const limiter = new Ratelimit({ namespace: "express-example", limit: 2, duration: "30s", rootKey: process.env.UNKEY_ROOT_KEY }); app.get('/', (req: Request, res: Response) => { res.send('Welcome to Express & TypeScript Server'); }); // This endpoint is protected by Unkey app.get('/secret', async (req: Request, res: Response) => { const identifier = req.getUserId() // or ip or anything else you want const ratelimit = await limiter.limit(identifier) if (!ratelimit.success){ res.status(429).send("Please try again later") } return res.status(200).send("ok"); }) app.listen(port, () => { console.log(`Server is listening at http://localhost:${port}`); }); ``` ```bash npm run start ``` ```bash curl 'http://localhost:8000/secret' ``` You will need to curl a few times to see the ratelimiting error. Once you do, you, you will need to wait to perform the action again. ## What is next? Now that you've seen the power of Unkey, check out some resources below to continue your journey. Join our Discord to chat with us and the community Learn about our API that helps you manage APIs, keys, ratelimits and analytical data. Check out our SDKs and how they fit into your Express application. # Hono Source: https://unkey.com/docs/quickstart/ratelimiting/hono Ratelimiting endpoints with Hono ## Prerequisites * Created your [Unkey account](https://app.unkey.com/auth/sign-up) * Created an [Unkey root key](https://app.unkey.com/settings/root-keys) with `ratelimit.*.create_namespace` and `ratelimit.*.limit` permissions. Run the following command to create your Hono project ```bash npm create hono@latest ``` ```bash pnpm create hono@latest ``` ```bash yarn create hono@latest ``` ```bash bun create hono@latest ``` Now install the `@unkey/ratelimit` package ```bash npm install @unkey/ratelimit ``` ```bash pnpm add @unkey/ratelimit ``` ```bash yarn add @unkey/ratelimit ``` ```bash bun install @unkey/ratelimit ``` Add your root key to your `.env` file ```bash UNKEY_ROOT_KEY="YOUR_KEY" ``` Create a new route and add the following code ```ts /src/index.ts import { Hono } from "hono"; import { Ratelimit } from "@unkey/ratelimit"; const app = new Hono(); const limiter = new Ratelimit({ namespace: "hono-example", limit: 2, duration: "30s", rootKey: process.env.UNKEY_ROOT_KEY }); app.get("/", (c) => { const identifier = getUserId(); // or ip or anything else you want const ratelimit = await limiter.limit(identifier) if (!ratelimit.success){ return c.status(429).text("Please try again later") } return c.text("Hello Hono!"); }); export default app; ``` ```bash npm run dev ``` ```bash pnpm run dev ``` ```bash yarn run dev ``` ```bash bun run dev ``` ```bash curl -XPOST 'http://localhost:8787/' ``` You will need to curl a few times to see the ratelimiting error. Once you do, you, you will need to wait to perform the action again. ## What is next? Now that you've seen the power of Unkey, check out some resources below to continue your journey. Join our Discord to chat with us and the community Learn about our API that helps you manage APIs, keys, ratelimits and analytical data. Check out our Hono SDK and how they fit into your API. # Next.js Source: https://unkey.com/docs/quickstart/ratelimiting/nextjs Ratelimiting endpoints with Next.js ## Prerequisites * Created your [Unkey account](https://app.unkey.com/auth/sign-up) * Created an [Unkey root key](https://app.unkey.com/settings/root-keys) with `ratelimit.*.create_namespace` and `ratelimit.*.limit` permissions. Run the following command to init your Next.js project ```bash npx create-next-app@latest ``` ```bash pnpm create next-app@latest ``` ```bash yarn create-next-app@latest ``` ```bash bunx create-next-app ``` Now install the `@unkey/ratelimit` package ```bash npm install @unkey/ratelimit ``` ```bash pnpm add @unkey/ratelimit ``` ```bash yarn add @unkey/ratelimit ``` ```bash bun install @unkey/ratelimit ``` Add your root key to your `.env` file ```bash UNKEY_ROOT_KEY="YOUR_KEY" ``` Create a new route and add the following code ```ts /app/protected/route.ts import { NextResponse } from 'next/server'; import { Ratelimit } from "@unkey/ratelimit"; const limiter = new Ratelimit({ namespace: "next-example", limit: 2, duration: "30s", rootKey: process.env.UNKEY_ROOT_KEY }); export const POST = (async (req) => { const identifier = getUserId(); // or ip or anything else you want const ratelimit = await limiter.limit(identifier) if (!ratelimit.success){ return new NextResponse("Please try again later", {status: 429}); } return new NextResponse('Hello!'); }); ``` ```bash npm run dev ``` ```bash pnpm run dev ``` ```bash yarn run dev ``` ```bash bun run dev ``` Go to [https://app.unkey.com](https://app.unkey.com) and create a new key. Then verify it with our new server: ```bash curl -XPOST 'http://localhost:3000/protected' ``` You will need to curl a few times to see the ratelimiting error. Once you do, you, you will need to wait to perform the action again. ## What is next? Now that you've seen the power of Unkey, check out some resources below to continue your journey. Join our Discord to chat with us and the community Learn about our API that helps you manage APIs, keys, ratelimits and analytical data. Check out our Next.js SDK and how they fit into your Next application. # Automated Overrides Source: https://unkey.com/docs/ratelimiting/automated-overrides Manage dynamic overrides programmatically Unkey's ratelimit override API allows you to manage dynamic overrides in response to events in your system. For example when your customer upgrades to an enterprise plan, you might want to create overrides for them to give them higher quotas. Let's look at common scenarios and how to implement them using our [@unkey/api SDK](https://www.unkey.com/docs/libraries/ts/sdk/overview). Our application has a ratelimit namespace called `email.send`, which ratelimits users from sending OTP emails during login. As identifier we're using their email address. ## Set Override In this example, we'll set an override for all users of our fictional customer `calendso.com`. How you detect a change is up to you, typically it's either through a user or admin action, or some form of incoming webhook from your billing or auth provider. ```ts import { Unkey } from "@unkey/api"; const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY!, }); await unkey.ratelimits.setOverride({ namespaceName: "email.send", // set the override for all users with this domain identifier: "*@calendso.com", limit: 10, duration: 60_000, // 1 minute async: true }) ``` [API Reference ->](/api-reference/ratelimits/set-override) Now, when we're ratelimiting `tim@calendso.com`, it will use the override settings and ratelimit them to 10 per minute. ## Get Override Retrieve a single override for an identifier within a namespace. ```ts import { Unkey } from "@unkey/api"; const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY!, }); const override = await unkey.ratelimits.getOverride({ namespaceName: "email.send", identifier: "*@customer.com", }) console.log(override) { "result": { "id": "rlor_123", "identifier": "*@calendso.com", "limit": 10, "duration": 60000, "async": true } } ``` [API Reference ->](/api-reference/ratelimits/get-override) ## List Overrides You can list all of the configured overirdes for a namespace to build your own dashboards. ```ts import { Unkey } from "@unkey/api"; const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY!, }); const res = await unkey.ratelimits.listOverrides({ namespaceName: "email.send", }) console.log(res) { "result": { "overrides": [ { "id": "rlor_123", "identifier": "*@calendso.com", "limit": 10, "duration": 60000, "async": true } ], "cursor": "eyJrZXkiOiJrZXlfMTIzNCJ9", "total": 1 } } ``` [API Reference ->](/api-reference/ratelimits/list-overrides) ## Delete Override Once they downgrade their plan, we can revoke any overrides: ```ts import { Unkey } from "@unkey/api"; const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY!, }); await unkey.ratelimits.deleteOverride({ namespaceName: "email.send", identifier: "*@customer.com", }) ``` [API Reference ->](/api-reference/ratelimits/delete-override) # Overview Source: https://unkey.com/docs/ratelimiting/introduction Ratelimit your serverless functions. Serverless functions offer a ton of benefits but unfortunately make some things much harder too. Ratelimiting is one of these things as it requires some persistent state to coordinate different limits. Unkey's ratelimit API provides a global low latency solution to limit your functions and protect upstream services. ## Get your API key Visit [/app/settings-root-keys/new](https://app.unkey.com/settings/root-keys/new) You can give your key a name to make it easier to find later, but that's optional. Your key needs at least these permissions: * `create_namespace` * `limit` After selecting both permissions, click `Create New Key` at the bottom of the page and copy the generated key to your `.env` file. For security reasons, this key can not be shown again. In case you lose it, you need to create a new one. ## Ratelimit your functions We're currently only offering a Typescript SDK but you can check out the [api reference](/api-reference/ratelimits/limit) and do the network call manually if you are using another language. Also please let us know what language you're using, so we can prioritize adding support for it. Please follow the [@unkey/ratelimit](/libraries/ts/ratelimit) documentation for setup and configuration options. # Consistency vs Latency Source: https://unkey.com/docs/ratelimiting/modes Optimize for consistency or latency with different ratelimiting modes. ## Synchronous The default ratelimiting mode is synchronous, which means that the request will be blocked until the current limit has been confirmed by the origin source of truth. The problem with this is that it can introduce more latency. We constantly relocate the origin dynamically to the closest edge location of the caller, but there's still a roundtrip to the origin to confirm the limit. And if the same identifier is being used in multiple geographic regions, only one of them is the closest, the remaining ones are likely doing longer network requests across the globe. The benefit of this mode is that it's absolutely correct, as every single request is checked against the same source of truth. ## Asynchronous Asynchronous mode is a tradeoff between correctness and latency. It's faster because we don't need to confirm the limit with the origin before returning a decision, but it's also less correct because the limit is checked against the local edge cache and then updated asynchronously in the background. This means that if the same identifier is being used in multiple geographic regions, they might not be aware of each other's usage and could potentially exceed the limit until everything is synchronized to the edge. As you might notice, the `remaining` response value is not always accurate in async mode for low frequency requests. You should always use the returned `success` value to determine if the request was allowed or not. ## What should I use? With every request we measure whether our async `success` respones would have matched a synchronous `success` response and we observe 98% accuracy across all of our users. If you have strict requirements for correctness, you should use the synchronous mode. In all other cases, we recommend using the asynchronous mode to optimize for latency as that will give you the best performance and lowest latency for your users. Most of the time, your ratelimits will not get reached, so the small tradeoff in correctness is usually worth it for the performance gain. In case your API gets abused, the async mode will sync quickly due to the high frequency of requests and will stop the abuse quickly. If you're still unsure which mode to use, please join our [Discord](https://unkey.com/discord) and we'll help you decide. # Custom overrides Source: https://unkey.com/docs/ratelimiting/overrides Override limits for identifiers without code changes. The problem with code-defined ratelimits is that managing different limits per user is not really feasable. You'd need to keep a list of identifiers hardcoded in your code and redeploy your application whenever one of them changes. With unkey you can add custom overrides dynamically and we'll roll them out to every edge location quickly. ## Overriding limits for an identifier Click "Ratelimit" in the sidebar > select your namespace > Overrides Enter the identifier to override, a custom limit and custom duration. Click **Create** to roll out the override globally. It may take up to 60s at most but is usually done in just a few seconds. From now on, when the specified identifier gets ratelimited, it'll use the custom limits, rather than what is defined in your code. ## Wildcard Rules You can use wildcards (`*`) in your override to match multiple identifiers. Exact override matches take precedence over wildcard matches, but there's no guarantee of order between multiple wildcard matches yet. **Example:** Given a base limit of 10/60s and these overrides: * `*@domain.com`: 20/60s * `hello@domain.com`: 100/s This would result in the following applied ratelimits: | requested identifier | match | used ratelimit | | -------------------- | -------- | -------------- | | `abc@domain.com` | wildcard | 20/60s | | `hello@domain.com` | exact | 100/s | | `xyz` | - | 10/60s | Here's an example of setting higher limits for all emails from the `customer.com` domain. ### Examples | override | matches | | --------------- | ---------------------------------- | | `*@domain.com` | `1@domain.com`, `hello@domain.com` | | `invite:*` | `invite:abc`, `invite:hello world` | | `prefix*suffix` | `prefixhelloworldsuffix` | # Delete Protection Source: https://unkey.com/docs/security/delete-protection Prevents an resource from being deleted when enabled. # Delete Protection Delete Protection is a safety feature that prevents accidental deletion of a resource. When enabled, it blocks all deletion attempts through both the dashboard and API. ## Overview **Delete Protection** adds an extra layer of security by: * Preventing accidental deletion of critical resources * Requiring explicit confirmation to disable protection * Blocking deletion attempts through both UI and API ## Supported Resources Currently, Delete Protection is available for: * APIs: Protect your API configurations and settings from accidental deletion More resources will be added in future updates. ## Enabling API Delete Protection 1. Navigate to your API settings in the dashboard 2. Click "Enable Delete Protection" 3. Type the API name to confirm 4. Click "Enable API Delete Protection" to add protection Once enabled, the API cannot be deleted until protection is explicitly disabled. ## Disabling API Delete Protection 1. Navigate to your API settings in the dashboard 2. Click "Disable Delete Protection" 3. Type the API name to confirm 4. Click "Disable API Delete Protection" to remove protection ## Resource Behavior When Delete Protection is enabled: * All deletion attempts are blocked * The resource returns a `DELETE_PROTECTED` error * The error includes a link to this documentation ### Example API Error Response ```json { "error": { "code": "DELETE_PROTECTED", "docs": "https://unkey.com/docs/api-reference/errors/code/DELETE_PROTECTED", "message": "api [apiId] is protected from deletions", "requestId": "req_1234" } } ``` ## Best Practices 1. **Enable for Production resources**: Always enable Delete Protection for resources in production 2. **Use for Critical resources**: Protect resources that contain important data or configurations 3. **Regular Review**: Periodically review protection status of your resources 4. **Team Communication**: Inform team members about protected resources 5. **Documentation**: Document which resources are protected and why ## Limitations * Delete Protection only prevents deletion of the resource * It does not prevent modifications to the resource * It does not prevent deletion of children of a resource, like keys or ratelimits that belong to a protected resource * Protection can be disabled by any user with appropriate permissions ## Related Errors * [DELETE\_PROTECTED](https://www.unkey.com/docs/api-reference/errors/code/DELETE_PROTECTED) - Returned when attempting to delete a protected API # GitHub Secret Scanning Source: https://unkey.com/docs/security/github-scanning How Unkey protects you from leaked root keys Unkey has partnered with GitHub to scan repositories for leaked keys. GitHub Secret Scanning uses regular expressions to scan repositories for keys matching Unkey root keys. If a key is found, GitHub will notify Unkey, and we will validate the key and notify users via email. To ensure the production environment remains up and running, we do not disable the key. This is a service that is automatic and requires nothing from you to function. However, outside of GitHub, we will not be able to inform you if a key has leaked. Learn more: [GitHub Secret Scanning](https://docs.github.com/en/code-security/secret-scanning) ## Sources Scanned * content * commit * pull\_request\_title * pull\_request\_description * pull\_request\_comment * issue\_title * issue\_description * issue\_comment * discussion\_title * discussion\_body * discussion\_comment * commit\_comment * gist\_content * gist\_comment * npm * unknown # Overview Source: https://unkey.com/docs/security/overview How does Unkey work? What security measures are in place? ## Why is Unkey secure? What makes it secure? Unkey is secure because we never store the API Key in our database. Instead we store a hash of the API Key. This means that even if our database is compromised, your API Keys are safe. You should follow a similar pattern in your own applications by providing the API key to your user and not storing it in your database either. We manage the keys by using the unique id that each key is given when it is created. This id allows you as developer to update or delete the key, but this id cannot be used to verify the key. ## Where are API keys stored? We don't store API Keys, we store a hash of the API Key (sha256). This is then stored in our database. When you attempt to verify the API Key, we hash the API Key you provide and compare it to the hash we have stored. If they match, then the API Key is valid. # Recovering Keys Source: https://unkey.com/docs/security/recovering-keys Show keys again after they are created Best practice is to create a key, show it to your users and never store it yourself. If the user loses the key, they can create a new one. This way you don't have to worry about storing the key securely. Without recovery, we would generate a new key and only store a hash of it. This way we can check if the key is correct but nobody, not even someone with access to the database, can recover the key. However there are some reasons why you might want to recover keys and show them again. * API playgrounds that need the key to call an API * Better DX for your users, it's annoying to create a new key and update it everywhere ## Vault Vault is our secure storage for secrets, such as keys. It follows a few principles: * Secrets are encrypted at rest * A leak of vaults data does not expose secrets * A leak of the main database does not expose secrets * A leak of the main encryption keys does not expose secrets An attacker would need access to the vault, the main database and the main encryption keys to decrypt the secrets. In order to make this even harder, we rotate the encryption keys regularly and do not run vault on the same servers as the main database to prevent an attacker from getting access to all the required information at once. To learn more about how it works under the hood, you can head over to our [engineering docs](https://engineering.unkey.dev/services/vault). ## Opting in By default we only store key hashes, not encrypted keys. If you want us to store keys in a way that we can recover them, you need to opt in: When creating new keys, your root key must have permission to encrypt. Head over to the [dashboard](https://app.unkey.com/settings/root-keys) and make sure the `encrypt_key` permission is enabled. Do not skip this step. Otherwise your root key will get rejected when trying to create new keys. To opt in to recovery, send us an email at [support@unkey.dev](mailto:support@unkey.dev?subject=Recovery%20Opt%20In). Send us the email from the email address associated with your workspace and include the `API ID` that you want to enable recovery for. Please note that this is not retroactively applied. Existing keys were never stored and cannot be recovered. Only keys created after opting in to recovery can be recovered. ## Creating keys When creating a key, you can set the `recoverable` field to `true`. This will store the key in a way that we can recover it later. ```shell curl --request POST \ --url https://api.unkey.com/v2/keys.createKey \ --header 'Authorization: Bearer {ROOT_KEY}' \ --header 'Content-Type: application/json' \ -d '{ "apiId": "{API_ID}", "recoverable": true }' ``` ## Recovering plaintext keys Both the [getKey](/api-reference/keys/get) and [listKeys](/api-reference/apis/list-keys) endpoints accept a `decrypt` query parameter. If you set this to `true`, the key will be decrypted and returned in the response as `plaintext`. When recovering keys, your root key must have permission to decrypt. Head over to the [dashboard](https://app.unkey.com/settings/root-keys) and make sure the `decrypt_key` permission is enabled. ```shell curl --request POST \ --url https://api.unkey.com/v2/keys.getKey \ --header 'Authorization: Bearer {ROOT_KEY}' \ --header 'Content-Type: application/json' \ -d '{ "keyId": "{KEY_ID}", "decrypt": true }' ``` ```json { // ... "plaintext": "your-key-here" } ``` If you have any questions about recovery, please reach out to us at [support@unkey.dev](mailto:support@unkey.dev). For security concerns, please disclose them responsibly by emailing [security@unkey.dev](mailto:security@unkey.dev) instead. # Root Keys Source: https://unkey.com/docs/security/root-keys Learn how root keys in the Unkey API work To interact with the Unkey API to manage resources such as APIs or keys, you need a `root key`. `Root keys` are scoped per workspace and you can fine tune their access permissions when creating a key or update later on the fly. It's a good practice to provide as few permissions as possible, to minimize the potential impact of a leaked key. Go to [https://app.unkey.com/settings/root-keys/new](https://app.unkey.com/settings/root-keys/new) 1. Optionally enter a name. This is internal only and not customer facing. 2. Add your workspace-wide permissions. These permissions affect and override the per-api permissions below. 3. For each API in your workspace, you can enable fine grained permissions. 4. Click `Create New Key` at the bottom Be sure to copy the key before closing the window. There is no way to recover it later ## What should I do if a root key is leaked? If you leak a root key - for instance, by accidentally checking it in to version control - you should immediately revoke the root key and replace it with a new, secure key. Root keys are secrets, and should never be exposed publicly.