# 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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} 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 theme={"system"} # 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} { "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/v2/auth) * [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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} export UNKEY_ROOT_KEY="your_root_key_here" ``` 3. **Restart Windsurf** Restart Windsurf to load the MCP server configuration. # Getting Started Source: https://unkey.com/docs/analytics/getting-started Request access and run your first analytics query ## Request Access **Analytics is currently in private beta and available by request only.** To get started: 1. **Find your workspace ID** in the Unkey dashboard settings 2. **Email us** at [support@unkey.dev](mailto:support@unkey.dev) with: * Your workspace ID * Your use case (billing, dashboards, reporting, etc.) * Expected query volume We'll enable analytics for your workspace and send you confirmation. ## Authentication Analytics queries require a root key with analytics permissions. Create one in your dashboard: 1. Go to **Settings** → **Root Keys** 2. Click **Create New Root Key** 3. Select permissions: `api.*.read_analytics` OR `api..read_analytics` 4. Copy and securely store your root key Root keys have powerful permissions. Store them securely and never commit them to version control. ## Your First Query Once you have access, execute your first analytics query using the `/v2/analytics.getVerifications` endpoint. ### Count Total Verifications Count the total number of key verifications in the last 7 days across all your APIs to get a high-level view of your overall usage volume. ```sql SQL theme={"system"} SELECT SUM(count) as total FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 7 DAY ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT SUM(count) as total FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 7 DAY" }' ``` ### Break Down by Outcome Group verifications by their outcome (`VALID`, `RATE_LIMITED`, `USAGE_EXCEEDED`, etc.) over the last 24 hours to understand the distribution of successful vs. failed requests. ```sql SQL theme={"system"} SELECT outcome, SUM(count) as count FROM key_verifications_per_hour_v1 WHERE time >= now() - INTERVAL 24 HOUR GROUP BY outcome ORDER BY count DESC ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT outcome, SUM(count) as count FROM key_verifications_per_hour_v1 WHERE time >= now() - INTERVAL 24 HOUR GROUP BY outcome ORDER BY count DESC" }' ``` ### Top Users by Usage Identify your most active users by counting their total verifications over the last 30 days to spot power users or potential abuse patterns. ```sql SQL theme={"system"} SELECT external_id, SUM(count) as verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY external_id ORDER BY verifications DESC LIMIT 10 ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT external_id, SUM(count) as verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY external_id ORDER BY verifications DESC LIMIT 10" }' ``` **Performance tip:** For longer time ranges, use pre-aggregated tables instead of the raw table: * `key_verifications_per_minute_v1` - For queries spanning hours * `key_verifications_per_hour_v1` - For queries spanning days * `key_verifications_per_day_v1` - For queries spanning weeks/months * `key_verifications_per_month_v1` - For queries spanning years Use `SUM(count)` instead of `COUNT(*)` with aggregated tables. They scan far fewer rows and are much faster. Check out the [Query Examples](/analytics/query-examples) page for 30+ ready-to-use queries covering billing, monitoring, and analytics use cases. ## Understanding the Response Analytics queries return data as an array of objects: ```json theme={"system"} { "meta": { "requestId": "req_xxx" }, "data": [ { "outcome": "VALID", "count": 1234 }, { "outcome": "RATE_LIMITED", "count": 56 }, { "outcome": "USAGE_EXCEEDED", "count": 12 } ] } ``` Each object in the `data` array contains fields from your SELECT clause. The field names match the column names or aliases you specified in your query. ## Filtering by API or User You can filter queries to specific APIs or users. Use `key_space_id` to filter by API (find this identifier in your API settings) and `external_id` to filter by user. These fields support standard SQL operators: `=`, `!=`, `IN`, `NOT IN`, `<`, `>`, etc. Queries are subject to resource limits (execution time, memory, result size, and quota). See [Query Restrictions](/analytics/query-restrictions) for complete details on limits and error codes. # Overview Source: https://unkey.com/docs/analytics/overview Query your verification data with SQL **Analytics is currently in private beta and available by request only.** See [Getting Started](/analytics/getting-started) for access instructions. ## What is Unkey Analytics? Unkey Analytics provides a powerful SQL interface to query your API key verification data. Instead of building your own analytics pipeline, you can leverage Unkey's built-in data warehouse to: * **Build custom dashboards** for internal teams or end-users * **Power usage-based billing** by querying verification counts per user/organization * **Generate reports** on API usage patterns, top users, and performance metrics * **Monitor and alert** on verification outcomes, rate limits, and errors ## How it Works Every key verification request is automatically stored and aggregated across multiple time-series tables: ```mermaid theme={"system"} graph LR A[Verify Key Request] --> B[Raw Events Table] B --> C[Minute Aggregates] C --> D[Hour Aggregates] D --> E[Day Aggregates] E --> F[Month Aggregates] ``` You can query these tables using standard SQL to: * Aggregate verification counts by time period * Group by API, user, or outcome * Filter by region, tags, or custom criteria * Calculate metrics for billing or monitoring ## Available Data Every verification event contains: | Field | Type | Description | | --------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `request_id` | String | Unique identifier for each request | | `time` | Int64 | Unix millisecond timestamp | | `workspace_id` | String | Your workspace identifier (automatically filtered) | | `key_space_id` | String | Your KeySpace identifier (e.g., `ks_1234`). Find this in your API settings. | | `external_id` | String | Your user's identifier (e.g., `user_abc`) | | `key_id` | String | Individual key identifier | | `outcome` | String | Verification result: `VALID`, `RATE_LIMITED`, `INVALID`, `EXPIRED`, `DISABLED`, `INSUFFICIENT_PERMISSIONS`, `FORBIDDEN`, `USAGE_EXCEEDED` | | `region` | String | Unkey region that handled the verification | | `tags` | Array(String) | Custom tags added during verification | | `spent_credits` | Int64 | Number of credits spent on this verification (0 if no credits were spent) | ## Use Cases Usage-based billing and credit tracking API health and performance monitoring User behavior and engagement insights # Query Examples Source: https://unkey.com/docs/analytics/query-examples Common SQL patterns for analytics and billing This guide provides SQL query examples for common analytics scenarios covering all the use cases from the legacy API and more. All examples use ClickHouse SQL syntax and work with the `/v2/analytics.getVerifications` endpoint. ## Using Queries in API Requests When making API requests, you need to format the SQL query as a JSON string on a single line. Here's how: ```sql SQL theme={"system"} SELECT COUNT(*) as total FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT COUNT(*) as total FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY" }' ``` Each example below shows both the readable multi-line SQL and the single-line JSON format you can copy directly into your API requests. ## Usage Analytics **Use this for:** High-level usage metrics, health monitoring, and trend analysis. **Key patterns:** Total counts, outcome breakdowns, time series analysis. ### Total verifications in the last 7 days Count total verifications across all APIs in the last 7 days. ```sql SQL theme={"system"} SELECT SUM(count) as total_verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 7 DAY ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT SUM(count) as total_verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 7 DAY" }' ``` ### Verifications by outcome Break down verifications by outcome to understand success vs failure rates. ```sql SQL theme={"system"} SELECT outcome, SUM(count) as count FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY outcome ORDER BY count DESC ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT outcome, SUM(count) as count FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY outcome ORDER BY count DESC" }' ``` ### Daily verification trend Track daily verification patterns over the last 30 days. ```sql SQL theme={"system"} SELECT time as date, SUM(count) as verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY date ORDER BY date ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT time as date, SUM(count) as verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY date ORDER BY date" }' ``` ### Hourly breakdown for today Analyze hourly verification patterns for today with outcome breakdown. ```sql SQL theme={"system"} SELECT time as hour, outcome, SUM(count) as verifications FROM key_verifications_per_hour_v1 WHERE time >= toStartOfDay(now()) GROUP BY time, outcome ORDER BY time, outcome ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT time as hour, outcome, SUM(count) as verifications FROM key_verifications_per_hour_v1 WHERE time >= toStartOfDay(now()) GROUP BY time, outcome ORDER BY time, outcome" }' ``` ## Usage by User **Use this for:** Understanding user behavior, identifying power users, tracking user activity over time. **Key patterns:** User ranking, activity trends, specific user analysis. ### All users ranked by usage Rank all users by their total verification usage over the last 30 days. ```sql SQL theme={"system"} SELECT external_id, SUM(count) as total_verifications, SUM(CASE WHEN outcome = 'VALID' THEN count ELSE 0 END) as successful, SUM(CASE WHEN outcome = 'RATE_LIMITED' THEN count ELSE 0 END) as rate_limited FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY AND external_id != '' GROUP BY external_id ORDER BY total_verifications DESC LIMIT 100 ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT external_id, SUM(count) as total_verifications, SUM(CASE WHEN outcome = '\''VALID'\'' THEN count ELSE 0 END) as successful, SUM(CASE WHEN outcome = '\''RATE_LIMITED'\'' THEN count ELSE 0 END) as rate_limited FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY AND external_id != '\'''\'' GROUP BY external_id ORDER BY total_verifications DESC LIMIT 100" }' ``` ### Usage for a specific user Analyze usage patterns for a specific user over the last 30 days. ```sql SQL theme={"system"} SELECT SUM(count) as total_verifications, SUM(CASE WHEN outcome = 'VALID' THEN count ELSE 0 END) as successful, SUM(CASE WHEN outcome = 'RATE_LIMITED' THEN count ELSE 0 END) as rate_limited FROM key_verifications_per_day_v1 WHERE external_id = 'user_123' AND time >= now() - INTERVAL 30 DAY ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT SUM(count) as total_verifications, SUM(CASE WHEN outcome = '\''VALID'\'' THEN count ELSE 0 END) as successful, SUM(CASE WHEN outcome = '\''RATE_LIMITED'\'' THEN count ELSE 0 END) as rate_limited FROM key_verifications_per_day_v1 WHERE external_id = '\''user_123'\'' AND time >= now() - INTERVAL 30 DAY" }' ``` ### Top 10 users by API usage Identify your most active users by verification count. ```sql SQL theme={"system"} SELECT external_id, SUM(count) as total_verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY AND external_id != '' GROUP BY external_id ORDER BY total_verifications DESC LIMIT 10 ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT external_id, SUM(count) as total_verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY AND external_id != '\'''\'' GROUP BY external_id ORDER BY total_verifications DESC LIMIT 10" }' ``` ### Daily usage per user Track daily verification patterns for each user over 30 days. ```sql SQL theme={"system"} SELECT external_id, time as date, SUM(count) as verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY external_id, date ORDER BY external_id, date ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT external_id, time as date, SUM(count) as verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY external_id, date ORDER BY external_id, date" }' ``` ## API Analytics **Use this for:** Comparing API performance, usage across different APIs, API-specific analysis. **Key patterns:** API comparison, success rates, per-API breakdowns. ### Usage per API Compare usage across all APIs to identify most active endpoints. ```sql SQL theme={"system"} SELECT key_space_id, SUM(count) as total_verifications, SUM(CASE WHEN outcome = 'VALID' THEN count ELSE 0 END) as successful FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY key_space_id ORDER BY total_verifications DESC ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT key_space_id, SUM(count) as total_verifications, SUM(CASE WHEN outcome = '\''VALID'\'' THEN count ELSE 0 END) as successful FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY key_space_id ORDER BY total_verifications DESC" }' ``` ### Usage for a specific API Analyze detailed usage patterns for a specific API over 30 days. ```sql SQL theme={"system"} SELECT SUM(count) as total_verifications, SUM(CASE WHEN outcome = 'VALID' THEN count ELSE 0 END) as successful, SUM(CASE WHEN outcome = 'RATE_LIMITED' THEN count ELSE 0 END) as rate_limited, SUM(CASE WHEN outcome = 'INVALID' THEN count ELSE 0 END) as invalid FROM key_verifications_per_day_v1 WHERE key_space_id = 'ks_1234' AND time >= now() - INTERVAL 30 DAY ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT SUM(count) as total_verifications, SUM(CASE WHEN outcome = '\''VALID'\'' THEN count ELSE 0 END) as successful, SUM(CASE WHEN outcome = '\''RATE_LIMITED'\'' THEN count ELSE 0 END) as rate_limited, SUM(CASE WHEN outcome = '\''INVALID'\'' THEN count ELSE 0 END) as invalid FROM key_verifications_per_day_v1 WHERE key_space_id = '\''ks_1234'\'' AND time >= now() - INTERVAL 30 DAY" }' ``` ### Compare multiple APIs Calculate success rates for multiple APIs to compare performance. ```sql SQL theme={"system"} SELECT key_space_id, SUM(count) as verifications, round(SUM(CASE WHEN outcome = 'VALID' THEN count ELSE 0 END) / SUM(count) * 100, 2) as success_rate FROM key_verifications_per_day_v1 WHERE key_space_id IN ('ks_1234', 'ks_5678') AND time >= now() - INTERVAL 7 DAY GROUP BY key_space_id ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT key_space_id, SUM(count) as verifications, round(SUM(CASE WHEN outcome = '\''VALID'\'' THEN count ELSE 0 END) / SUM(count) * 100, 2) as success_rate FROM key_verifications_per_day_v1 WHERE key_space_id IN ('\''ks_1234'\'', '\''ks_5678'\'') AND time >= now() - INTERVAL 7 DAY GROUP BY key_space_id" }' ``` ## Key Analytics **Use this for:** Individual API key analysis, identifying problematic keys, key-specific usage patterns. **Key patterns:** Key ranking, error analysis, specific key monitoring. ### Usage per key Identify your most frequently used API keys over the last 30 days. ```sql SQL theme={"system"} SELECT key_id, SUM(count) as total_verifications, SUM(CASE WHEN outcome = 'VALID' THEN count ELSE 0 END) as successful FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY key_id ORDER BY total_verifications DESC LIMIT 100 ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT key_id, SUM(count) as total_verifications, SUM(CASE WHEN outcome = '\''VALID'\'' THEN count ELSE 0 END) as successful FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY key_id ORDER BY total_verifications DESC LIMIT 100" }' ``` ### Usage for a specific key Analyze detailed usage patterns for a specific API key. ```sql SQL theme={"system"} SELECT SUM(count) as total_verifications, SUM(CASE WHEN outcome = 'VALID' THEN count ELSE 0 END) as successful, SUM(CASE WHEN outcome = 'RATE_LIMITED' THEN count ELSE 0 END) as rate_limited FROM key_verifications_per_day_v1 WHERE key_id = 'key_1234' AND time >= now() - INTERVAL 30 DAY ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT SUM(count) as total_verifications, SUM(CASE WHEN outcome = '\''VALID'\'' THEN count ELSE 0 END) as successful, SUM(CASE WHEN outcome = '\''RATE_LIMITED'\'' THEN count ELSE 0 END) as rate_limited FROM key_verifications_per_day_v1 WHERE key_id = '\''key_1234'\'' AND time >= now() - INTERVAL 30 DAY" }' ``` ### Keys with most errors Find API keys that are generating the most errors. ```sql SQL theme={"system"} SELECT key_id, SUM(count) as total_errors, groupArray(DISTINCT outcome) as error_types FROM key_verifications_per_day_v1 WHERE outcome != 'VALID' AND time >= now() - INTERVAL 7 DAY GROUP BY key_id ORDER BY total_errors DESC LIMIT 20 ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT key_id, SUM(count) as total_errors, groupArray(DISTINCT outcome) as error_types FROM key_verifications_per_day_v1 WHERE outcome != '\''VALID'\'' AND time >= now() - INTERVAL 7 DAY GROUP BY key_id ORDER BY total_errors DESC LIMIT 20" }' ``` ## Tag-Based Analytics **Use this for:** Custom metadata filtering, endpoint analysis, user segmentation using tags. **Key patterns:** Tag filtering, endpoint breakdowns, custom attribute analysis. Tags allow you to add custom metadata to verification requests for filtering and aggregation. ### Filter by single tag Count verifications for requests with a specific tag. ```sql SQL theme={"system"} SELECT SUM(count) as total FROM key_verifications_per_day_v1 WHERE has(tags, 'path=/api/v1/users') AND time >= now() - INTERVAL 7 DAY ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT SUM(count) as total FROM key_verifications_per_day_v1 WHERE has(tags, '\''path=/api/v1/users'\'') AND time >= now() - INTERVAL 7 DAY" }' ``` ### Filter by multiple tags (OR) Count verifications matching any of multiple tags. ```sql SQL theme={"system"} SELECT SUM(count) as total FROM key_verifications_per_day_v1 WHERE hasAny(tags, ['path=/api/v1/users', 'path=/api/v1/posts']) AND time >= now() - INTERVAL 7 DAY ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT SUM(count) as total FROM key_verifications_per_day_v1 WHERE hasAny(tags, ['\''path=/api/v1/users'\'', '\''path=/api/v1/posts'\'']) AND time >= now() - INTERVAL 7 DAY" }' ``` ### Filter by multiple tags (AND) Count verifications matching all specified tags. ```sql SQL theme={"system"} SELECT SUM(count) as total FROM key_verifications_per_day_v1 WHERE hasAll(tags, ['environment=production', 'team=backend']) AND time >= now() - INTERVAL 7 DAY ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT SUM(count) as total FROM key_verifications_per_day_v1 WHERE hasAll(tags, ['\''environment=production'\'', '\''team=backend'\'']) AND time >= now() - INTERVAL 7 DAY" }' ``` ### Group by tag Aggregate verifications by individual tags to see usage patterns. ```sql SQL theme={"system"} SELECT arrayJoin(tags) as tag, SUM(count) as verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 7 DAY GROUP BY tag ORDER BY verifications DESC LIMIT 20 ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT arrayJoin(tags) as tag, SUM(count) as verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 7 DAY GROUP BY tag ORDER BY verifications DESC LIMIT 20" }' ``` ### Breakdown by endpoint (using path tag) Analyze request volume by API endpoint over the last 24 hours. This query uses the raw table for detailed tag analysis. For longer time ranges, consider using aggregated tables and pre-filtered tags. ```sql SQL theme={"system"} SELECT arrayJoin(arrayFilter(x -> startsWith(x, 'path='), tags)) as endpoint, COUNT(*) as requests FROM key_verifications_v1 WHERE time >= now() - INTERVAL 24 HOUR GROUP BY endpoint ORDER BY requests DESC ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT arrayJoin(arrayFilter(x -> startsWith(x, '\''path='\''), tags)) as endpoint, COUNT(*) as requests FROM key_verifications_v1 WHERE time >= now() - INTERVAL 24 HOUR GROUP BY endpoint ORDER BY requests DESC" }' ``` ## Billing & Usage-Based Pricing **Use this for:** Usage-based billing implementation, credit tracking, user tier calculation. **Key patterns:** Credit aggregation, billing cycles, tier determination, cost analysis. ### Monthly credits per user Calculate monthly credit consumption per user for billing. ```sql SQL theme={"system"} SELECT external_id, toStartOfMonth(time) as month, SUM(spent_credits) as total_credits FROM key_verifications_per_day_v1 WHERE external_id != '' AND time >= toStartOfMonth(now()) GROUP BY external_id, month ORDER BY total_credits DESC ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT external_id, toStartOfMonth(time) as month, SUM(spent_credits) as total_credits FROM key_verifications_per_day_v1 WHERE external_id != '\'''\'' AND time >= toStartOfMonth(now()) GROUP BY external_id, month ORDER BY total_credits DESC" }' ``` ### Current billing period credits Calculate credit usage for a specific billing period. ```sql SQL theme={"system"} SELECT external_id, SUM(spent_credits) as credits_this_period FROM key_verifications_per_day_v1 WHERE external_id = 'user_123' AND time >= 1704067200000 -- Start of billing period (Unix millis) AND time < 1706745600000 -- End of billing period (Unix millis) GROUP BY external_id ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT external_id, SUM(spent_credits) as credits_this_period FROM key_verifications_per_day_v1 WHERE external_id = '\''user_123'\'' AND time >= 1704067200000 AND time < 1706745600000 GROUP BY external_id" }' ``` ### Credit-based tier calculation Determine user tiers based on monthly credit consumption. ```sql SQL theme={"system"} SELECT external_id, SUM(spent_credits) as total_credits, CASE WHEN total_credits <= 1000 THEN 'free' WHEN total_credits <= 10000 THEN 'starter' WHEN total_credits <= 100000 THEN 'pro' ELSE 'enterprise' END as tier FROM key_verifications_per_day_v1 WHERE time >= toStartOfMonth(now()) AND external_id = 'user_123' GROUP BY external_id ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT external_id, SUM(spent_credits) as total_credits, CASE WHEN total_credits <= 1000 THEN '\''free'\'' WHEN total_credits <= 10000 THEN '\''starter'\'' WHEN total_credits <= 100000 THEN '\''pro'\'' ELSE '\''enterprise'\'' END as tier FROM key_verifications_per_day_v1 WHERE time >= toStartOfMonth(now()) AND external_id = '\''user_123'\'' GROUP BY external_id" }' ``` ### Daily credit usage and cost Track daily credit consumption and calculate estimated costs. ```sql SQL theme={"system"} SELECT time as date, SUM(spent_credits) as credits_used, credits_used * 0.001 as estimated_cost -- $0.001 per credit FROM key_verifications_per_day_v1 WHERE external_id = 'user_123' AND time >= now() - INTERVAL 30 DAY GROUP BY date ORDER BY date ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT time as date, SUM(spent_credits) as credits_used, credits_used * 0.001 as estimated_cost FROM key_verifications_per_day_v1 WHERE external_id = '\''user_123'\'' AND time >= now() - INTERVAL 30 DAY GROUP BY date ORDER BY date" }' ``` ## Advanced Queries **Use this for:** Complex analytical patterns, cohort analysis, moving averages, advanced insights. **Key patterns:** User retention, trend smoothing, complex joins, window functions. ### Cohort analysis: New vs returning users Perform cohort analysis to understand user retention patterns. ```sql SQL theme={"system"} WITH first_seen AS ( SELECT external_id, min(time) as first_verification FROM key_verifications_per_day_v1 WHERE external_id != '' GROUP BY external_id ) SELECT toDate(kv.time) as date, SUM(CASE WHEN kv.time = fs.first_verification THEN kv.count ELSE 0 END) as new_users, SUM(CASE WHEN kv.time > fs.first_verification THEN kv.count ELSE 0 END) as returning_users FROM key_verifications_per_day_v1 kv JOIN first_seen fs ON kv.external_id = fs.external_id WHERE kv.time >= now() - INTERVAL 30 DAY GROUP BY date ORDER BY date ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "WITH first_seen AS ( SELECT external_id, min(time) as first_verification FROM key_verifications_per_day_v1 WHERE external_id != '\'''\'' GROUP BY external_id ) SELECT toDate(kv.time) as date, SUM(CASE WHEN kv.time = fs.first_verification THEN kv.count ELSE 0 END) as new_users, SUM(CASE WHEN kv.time > fs.first_verification THEN kv.count ELSE 0 END) as returning_users FROM key_verifications_per_day_v1 kv JOIN first_seen fs ON kv.external_id = fs.external_id WHERE kv.time >= now() - INTERVAL 30 DAY GROUP BY date ORDER BY date" }' ``` ### Moving average (7-day) Calculate 7-day moving average to smooth out daily fluctuations. ```sql SQL theme={"system"} SELECT date, verifications, avg(verifications) OVER ( ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW ) as moving_avg_7d FROM ( SELECT time as date, SUM(count) as verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 60 DAY GROUP BY date ) ORDER BY date ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT date, verifications, avg(verifications) OVER ( ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW ) as moving_avg_7d FROM ( SELECT time as date, SUM(count) as verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 60 DAY GROUP BY date ) ORDER BY date" }' ``` ## Using Aggregated Tables For better performance on large time ranges, use pre-aggregated tables: ### Hourly aggregates Query hourly verification counts for the last 7 days. ```sql SQL theme={"system"} SELECT time, SUM(count) as total FROM key_verifications_per_hour_v1 WHERE time >= toStartOfHour(now() - INTERVAL 7 DAY) GROUP BY time ORDER BY time ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT time, SUM(count) as total FROM key_verifications_per_hour_v1 WHERE time >= toStartOfHour(now() - INTERVAL 7 DAY) GROUP BY time ORDER BY time" }' ``` ### Daily aggregates Query daily verification counts for the last 30 days. ```sql SQL theme={"system"} SELECT time, SUM(count) as total FROM key_verifications_per_day_v1 WHERE time >= toStartOfDay(now() - INTERVAL 30 DAY) GROUP BY time ORDER BY time ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT time, SUM(count) as total FROM key_verifications_per_day_v1 WHERE time >= toStartOfDay(now() - INTERVAL 30 DAY) GROUP BY time ORDER BY time" }' ``` ### Monthly aggregates Query monthly verification counts for the last year. ```sql SQL theme={"system"} SELECT time, SUM(count) as total FROM key_verifications_per_month_v1 WHERE time >= toStartOfMonth(now() - INTERVAL 12 MONTH) GROUP BY time ORDER BY time ``` ```bash cURL theme={"system"} curl -X POST https://api.unkey.com/v2/analytics.getVerifications \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT time, SUM(count) as total FROM key_verifications_per_month_v1 WHERE time >= toStartOfMonth(now() - INTERVAL 12 MONTH) GROUP BY time ORDER BY time" }' ``` ## Tips for Efficient Queries 1. **Always filter by time** - Use indexes by including time filters 2. **Use aggregated tables** - Hourly/daily/monthly tables for longer ranges 3. **Add LIMIT clauses** - Prevent returning too much data 4. **Filter before grouping** - Use WHERE instead of HAVING when possible # Query Restrictions Source: https://unkey.com/docs/analytics/query-restrictions Limits, quotas, and permissions for analytics queries This page explains the restrictions, resource limits, and permissions for analytics queries. ### Only SELECT Allowed Only `SELECT` queries are permitted. All other SQL statement types return a `query_not_supported` error. **Allowed query patterns:** * `SELECT` statements * `WITH` (Common Table Expressions) * `UNION` * Subqueries * Joins * Aggregations * Window functions **Not allowed:** `INSERT`, `UPDATE`, `DELETE`, `DROP`, `ALTER`, `CREATE`, `TRUNCATE`, `GRANT`, `REVOKE` ### Table Access Control Only explicitly allowed analytics tables are accessible. Any attempt to access tables not on the allow list (including `system.*` or `information_schema.*`) will return an `invalid_table` error. ### Function Allow List Only explicitly approved functions are allowed. Any function not on this list will be rejected with an `invalid_function` error. #### Allowed Functions `count`, `sum`, `avg`, `min`, `max`, `any`, `groupArray`, `groupUniqArray`, `uniq`, `uniqExact`, `quantile`, `countIf` `now`, `now64`, `today`, `toDate`, `toDateTime`, `toDateTime64`, `toStartOfDay`, `toStartOfWeek`, `toStartOfMonth`, `toStartOfYear`, `toStartOfHour`, `toStartOfMinute`, `date_trunc`, `formatDateTime`, `fromUnixTimestamp64Milli`, `toUnixTimestamp64Milli`, `toIntervalDay`, `toIntervalWeek`, `toIntervalMonth`, `toIntervalYear`, `toIntervalHour`, `toIntervalMinute`, `toIntervalSecond`, `toIntervalMillisecond`, `toIntervalMicrosecond`, `toIntervalNanosecond`, `toIntervalQuarter` `lower`, `upper`, `substring`, `concat`, `length`, `trim`, `startsWith`, `endsWith` `round`, `floor`, `ceil`, `abs` `if`, `case`, `coalesce` `toString`, `toInt32`, `toInt64`, `toFloat64` `has`, `hasAny`, `hasAll`, `arrayJoin`, `arrayFilter` If you need a function that's not listed, please contact us at [support@unkey.dev](mailto:support@unkey.dev) and we'll review it for inclusion. ## Resource Limits To ensure fair usage and prevent abuse, queries are subject to resource limits: ### Execution Limits | Resource | Limit | Purpose | | ------------------------------- | --------------------- | ----------------------------- | | Max execution time | 30 seconds | Prevent long-running queries | | Max execution time (per window) | 1800 seconds (30 min) | Total execution time per hour | | Max memory usage | 1 GB | Prevent memory exhaustion | | Max result rows | 10 million | Limit result set size | ### Query Quotas | Quota | Limit | Window | | --------------------- | ----- | -------- | | Queries per workspace | 1000 | Per hour | If you need higher limits for your use case, please contact us at [support@unkey.dev](mailto:support@unkey.dev) with details about your specific requirements and expected query volume. ### Error Codes When limits are exceeded, you'll receive specific error codes: | Error Code | Description | Solution | | ----------------------------- | --------------------------------- | ---------------------------------------------------------- | | `query_execution_timeout` | Query took longer than 30 seconds | Add more filters, reduce time range, use aggregated tables | | `query_memory_limit_exceeded` | Query used more than 1GB memory | Reduce result set size, add LIMIT clause, use aggregation | | `query_result_rows_exceeded` | Query returned more than 10M rows | Add LIMIT clause, use aggregation, reduce time range | | `query_quota_exceeded` | Exceeded 1000 queries per hour | Wait for quota to reset, optimize query frequency | # Quick Reference Source: https://unkey.com/docs/analytics/quick-reference Fast lookup for common analytics query patterns and table selection # Analytics Quick Reference ## Essential Query Patterns ### Usage Analytics **Use for**: High-level usage metrics and health monitoring ```sql theme={"system"} -- Total verifications (last 7 days) SELECT SUM(count) as total FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 7 DAY -- Verifications by outcome (last 30 days) SELECT outcome, SUM(count) as count FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY outcome ORDER BY count DESC -- Daily usage trend (last 30 days) SELECT time as date, SUM(count) as verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY date ORDER BY date ``` ### User Analytics **Use for**: Understanding user behavior and identifying power users ```sql theme={"system"} -- Top users by usage (last 30 days) SELECT external_id, SUM(count) as total_verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY AND external_id != '' GROUP BY external_id ORDER BY total_verifications DESC LIMIT 10 -- Specific user activity (last 30 days) SELECT SUM(count) as total_verifications, SUM(CASE WHEN outcome = 'VALID' THEN count ELSE 0 END) as successful FROM key_verifications_per_day_v1 WHERE external_id = 'user_123' AND time >= now() - INTERVAL 30 DAY ``` ### API Analytics **Use for**: Comparing API performance and usage ```sql theme={"system"} -- Usage per API (last 30 days) SELECT key_space_id, SUM(count) as total_verifications FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY key_space_id ORDER BY total_verifications DESC -- API success rate comparison (last 7 days) SELECT key_space_id, SUM(count) as verifications, round(SUM(CASE WHEN outcome = 'VALID' THEN count ELSE 0 END) / SUM(count) * 100, 2) as success_rate FROM key_verifications_per_day_v1 WHERE key_space_id IN ('ks_1234', 'ks_5678') AND time >= now() - INTERVAL 7 DAY GROUP BY key_space_id ``` ### Billing Queries **Use for**: Usage-based billing and credit tracking ```sql theme={"system"} -- Monthly credits per user SELECT external_id, toStartOfMonth(time) as month, SUM(spent_credits) as total_credits FROM key_verifications_per_day_v1 WHERE external_id != '' AND time >= toStartOfMonth(now()) GROUP BY external_id, month ORDER BY total_credits DESC -- User tier calculation (current month) SELECT external_id, SUM(spent_credits) as total_credits, CASE WHEN total_credits <= 1000 THEN 'free' WHEN total_credits <= 10000 THEN 'starter' WHEN total_credits <= 100000 THEN 'pro' ELSE 'enterprise' END as tier FROM key_verifications_per_day_v1 WHERE time >= toStartOfMonth(now()) AND external_id = 'user_123' GROUP BY external_id ``` ### Tag-Based Filtering **Use for**: Custom metadata filtering and endpoint analysis ```sql theme={"system"} -- Filter by single tag SELECT SUM(count) as total FROM key_verifications_per_day_v1 WHERE has(tags, 'path=/api/v1/users') AND time >= now() - INTERVAL 7 DAY -- Filter by multiple tags (OR) SELECT SUM(count) as total FROM key_verifications_per_day_v1 WHERE hasAny(tags, ['path=/api/v1/users', 'path=/api/v1/posts']) AND time >= now() - INTERVAL 7 DAY -- Group by endpoint (using path tags) SELECT arrayJoin(arrayFilter(x -> startsWith(x, 'path='), tags)) as endpoint, COUNT(*) as requests FROM key_verifications_v1 WHERE time >= now() - INTERVAL 24 HOUR GROUP BY endpoint ORDER BY requests DESC ``` ## Table Selection Guide Choose the right table based on your time range: | Time Range | Recommended Table | When to Use | | --------------- | --------------------------------- | ----------------------------------------- | | **\< 1 hour** | `key_verifications_v1` | Real-time analysis, detailed debugging | | **\< 24 hours** | `key_verifications_per_minute_v1` | Hourly/daily trends, recent activity | | **\< 30 days** | `key_verifications_per_hour_v1` | Daily/weekly analysis, user behavior | | **\< 1 year** | `key_verifications_per_day_v1` | Monthly/quarterly reports, billing cycles | | **> 1 year** | `key_verifications_per_month_v1` | Annual trends, long-term analytics | **Performance Tips:** * Always filter by time first (uses indexes) * Use `SUM(count)` with aggregated tables, not `COUNT(*)` * Add `LIMIT` clauses to prevent large result sets * Filter before grouping when possible ## Common Filters ### Time Ranges ```sql theme={"system"} -- Relative time ranges WHERE time >= now() - INTERVAL 7 DAY -- Last 7 days WHERE time >= now() - INTERVAL 24 HOUR -- Last 24 hours WHERE time >= toStartOfDay(now()) -- Today WHERE time >= toStartOfMonth(now()) -- This month ``` ### User & API Filters ```sql theme={"system"} -- Specific user WHERE external_id = 'user_123' -- Multiple users WHERE external_id IN ('user_123', 'user_456') -- Specific API WHERE key_space_id = 'ks_1234' -- Multiple APIs WHERE key_space_id IN ('ks_1234', 'ks_5678') ``` ### Tag Filters ```sql theme={"system"} -- Has specific tag WHERE has(tags, 'environment=production') -- Has any of multiple tags WHERE hasAny(tags, ['team=backend', 'team=frontend']) -- Has all specified tags WHERE hasAll(tags, ['environment=prod', 'tier=premium']) ``` ### Outcome Filters ```sql theme={"system"} -- Only successful verifications WHERE outcome = 'VALID' -- Only errors WHERE outcome != 'VALID' -- Specific error types WHERE outcome IN ('RATE_LIMITED', 'USAGE_EXCEEDED') ``` ## Need More Functions? → [ClickHouse Function Reference](https://clickhouse.com/docs/en/sql-reference/functions)\ → [ClickHouse SQL Documentation](https://clickhouse.com/docs/en/sql-reference) # Schema Reference Source: https://unkey.com/docs/analytics/schema-reference Tables, columns, and data types in Unkey Analytics Unkey Analytics stores verification events across multiple time-series tables for efficient querying. This reference documents all available tables and their columns. Use aggregated tables (`per_hour`, `per_day`, `per_month`) for queries spanning long time periods to improve performance. ## Raw Events Table The `key_verifications_v1` table contains individual verification events as they occur. ### Columns | Column | Type | Description | | --------------- | ------------- | ------------------------------------------------------------------------------------------------------- | | `request_id` | String | Unique identifier for each verification request | | `time` | Int64 | Unix timestamp in milliseconds when verification occurred | | `workspace_id` | String | Workspace identifier (automatically filtered to your workspace) | | `key_space_id` | String | Your KeySpace identifier (e.g., `ks_1234`) - use this to filter by API. Find this in your API settings. | | `external_id` | String | Your user's identifier (e.g., `user_abc`) - use this to filter by user | | `key_id` | String | Individual API key identifier | | `outcome` | String | Verification result (see [Outcome Values](#outcome-values)) | | `region` | String | Unkey region that handled the verification | | `tags` | Array(String) | Custom tags added during verification | | `spent_credits` | Int64 | Number of credits spent on this verification (0 if no credits were spent) | ### Outcome Values The `outcome` column contains one of these values: | Outcome | Description | | -------------------------- | --------------------------------------- | | `VALID` | Key is valid and verification succeeded | | `RATE_LIMITED` | Verification exceeded rate limit | | `INVALID` | Key not found or malformed | | `EXPIRED` | Key has expired | | `DISABLED` | Key is disabled | | `INSUFFICIENT_PERMISSIONS` | Key lacks required permissions | | `FORBIDDEN` | Operation not allowed for this key | | `USAGE_EXCEEDED` | Key has exceeded usage limit | ## Aggregated Tables Pre-aggregated tables provide better query performance for long time ranges. Each aggregated table includes outcome counts. ### Per Minute Table `key_verifications_per_minute_v1` - Aggregated by minute | Column | Type | Description | | --------------- | -------- | ---------------------------------------------------------- | | `time` | DateTime | Timestamp (DateTime for minute/hour, Date for day/month) | | `workspace_id` | String | Workspace identifier | | `key_space_id` | String | API identifier | | `external_id` | String | Your user identifier | | `key_id` | String | API key identifier | | `outcome` | String | Verification outcome (VALID, RATE\_LIMITED, INVALID, etc.) | | `tags` | Array | Tags associated with verifications | | `count` | UInt64 | Total verification count for this aggregation | | `spent_credits` | UInt64 | Total credits spent | ### Per Hour Table `key_verifications_per_hour_v1` - Aggregated by hour. Same columns as per-minute table. ### Per Day Table `key_verifications_per_day_v1` - Aggregated by day. Same columns as per-minute table. ### Per Month Table `key_verifications_per_month_v1` - Aggregated by month. Same columns as per-minute table. ## Filtering by API and User You can use your familiar identifiers directly in queries: * **`key_space_id`** - Your API identifier (e.g., `ks_1234`). Find this in your API settings. * **`external_id`** - Your user identifiers (e.g., `user_abc123`) from your application All standard comparison operators are supported: `=`, `!=`, `<`, `>`, `<=`, `>=`, `IN`, `NOT IN` ### Filter by API ```sql theme={"system"} SELECT COUNT(*) FROM key_verifications_v1 WHERE key_space_id = 'ks_1234' ``` ### Filter by User ```sql theme={"system"} SELECT COUNT(*) FROM key_verifications_v1 WHERE external_id = 'user_abc123' ``` ### Multiple Values ```sql theme={"system"} SELECT COUNT(*) FROM key_verifications_v1 WHERE key_space_id IN ('ks_1234', 'ks_5678') AND external_id IN ('user_abc', 'user_xyz') ``` ## Working with Tags Tags are stored as `Array(String)` and require array functions to query. ### Check if tag exists ```sql theme={"system"} SELECT COUNT(*) FROM key_verifications_v1 WHERE has(tags, 'path=/api/users') ``` ### Check if any tag exists ```sql theme={"system"} SELECT COUNT(*) FROM key_verifications_v1 WHERE hasAny(tags, ['environment=prod', 'environment=staging']) ``` ### Check if all tags exist ```sql theme={"system"} SELECT COUNT(*) FROM key_verifications_v1 WHERE hasAll(tags, ['environment=production', 'team=backend']) ``` ### Extract and group by tags ```sql theme={"system"} SELECT arrayJoin(tags) as tag, COUNT(*) as count FROM key_verifications_v1 WHERE time >= now() - INTERVAL 24 HOUR GROUP BY tag ORDER BY count DESC ``` ### Filter tags with pattern ```sql theme={"system"} -- Get all tags starting with "path=" SELECT arrayJoin(arrayFilter(x -> startsWith(x, 'path='), tags)) as path, COUNT(*) as requests FROM key_verifications_v1 WHERE time >= now() - INTERVAL 24 HOUR GROUP BY path ``` ## Time Functions Timestamps are stored differently depending on the table: * **Raw table (`key_verifications_v1`)**: `time` is `Int64` (Unix milliseconds) * **Aggregated tables**: `time` is `DateTime` ### Current Time ```sql theme={"system"} SELECT now() as current_datetime SELECT toUnixTimestamp(now()) * 1000 as current_millis ``` ### Time Ranges (Raw Table) For the raw `key_verifications_v1` table, compare `time` with millisecond timestamps: ```sql theme={"system"} -- Last hour WHERE time >= toUnixTimestamp(now() - INTERVAL 1 HOUR) * 1000 -- Last 24 hours WHERE time >= toUnixTimestamp(now() - INTERVAL 24 HOUR) * 1000 -- Last 7 days WHERE time >= toUnixTimestamp(now() - INTERVAL 7 DAY) * 1000 -- Last 30 days WHERE time >= toUnixTimestamp(now() - INTERVAL 30 DAY) * 1000 -- This month WHERE time >= toUnixTimestamp(toStartOfMonth(now())) * 1000 -- Today WHERE time >= toUnixTimestamp(toStartOfDay(now())) * 1000 ``` ### Time Ranges (Aggregated Tables) For aggregated tables, use DateTime comparisons directly: ```sql theme={"system"} -- Last 7 days WHERE time >= now() - INTERVAL 7 DAY -- This month WHERE time >= toStartOfMonth(now()) -- Today WHERE time >= toStartOfDay(now()) ``` ### Time Rounding (Raw Table) ```sql theme={"system"} -- Round to start of hour SELECT toStartOfHour(toDateTime(time / 1000)) as hour -- Round to start of day SELECT toStartOfDay(toDateTime(time / 1000)) as day -- Round to start of month SELECT toStartOfMonth(toDateTime(time / 1000)) as month -- Convert to date SELECT toDate(toDateTime(time / 1000)) as date ``` ### Specific Date Ranges ```sql theme={"system"} -- Between specific dates (Unix milliseconds) WHERE time >= 1704067200000 -- Jan 1, 2024 00:00:00 UTC AND time < 1735689600000 -- Jan 1, 2025 00:00:00 UTC ``` ## Common ClickHouse Functions ### Aggregate Functions | Function | Description | Example | | ----------- | ----------------- | ----------------------------------------------------------- | | `COUNT()` | Count rows | `SELECT COUNT(*) FROM key_verifications_v1` | | `SUM()` | Sum values | `SELECT SUM(valid_count) FROM key_verifications_per_day_v1` | | `AVG()` | Average | `SELECT AVG(spent_credits) FROM key_verifications_v1` | | `MIN()` | Minimum value | `SELECT MIN(time) FROM key_verifications_v1` | | `MAX()` | Maximum value | `SELECT MAX(time) FROM key_verifications_v1` | | `countIf()` | Conditional count | `SELECT countIf(outcome = 'VALID')` | | `uniq()` | Count distinct | `SELECT uniq(key_id) FROM key_verifications_v1` | ### String Functions | Function | Description | Example | | -------------- | -------------------- | ------------------------------------- | | `lower()` | Convert to lowercase | `WHERE lower(outcome) = 'valid'` | | `upper()` | Convert to uppercase | `WHERE upper(region) = 'US-EAST-1'` | | `concat()` | Concatenate strings | `SELECT concat(region, '-', outcome)` | | `substring()` | Extract substring | `SELECT substring(key_id, 1, 8)` | | `startsWith()` | Check prefix | `WHERE startsWith(key_id, 'key_')` | ### Array Functions | Function | Description | Example | | --------------- | ------------------ | ---------------------------------------------------- | | `has()` | Check element | `WHERE has(tags, 'environment=production')` | | `hasAny()` | Check any element | `WHERE hasAny(tags, ['team=backend', 'team=api'])` | | `hasAll()` | Check all elements | `WHERE hasAll(tags, ['environment=prod', 'tier=1'])` | | `arrayJoin()` | Expand array | `SELECT arrayJoin(tags) as tag` | | `arrayFilter()` | Filter array | `arrayFilter(x -> startsWith(x, 'path='), tags)` | | `length()` | Array length | `WHERE length(tags) > 0` | ### Math Functions | Function | Description | Example | | --------- | -------------- | ---------------------------------------------------------- | | `round()` | Round number | `SELECT round(AVG(spent_credits), 2)` | | `floor()` | Round down | `SELECT floor(spent_credits / 100) * 100 as credit_bucket` | | `ceil()` | Round up | `SELECT ceil(spent_credits)` | | `abs()` | Absolute value | `SELECT abs(difference)` | ### Conditional Functions | Function | Description | Example | | -------- | --------------- | --------------------------------------------------------------- | | `if()` | If-then-else | `SELECT if(outcome = 'VALID', 1, 0)` | | `CASE` | Multi-condition | `CASE WHEN outcome = 'VALID' THEN 'success' ELSE 'failure' END` | ## Performance Tips 1. **Always filter by time** - Use time-based WHERE clauses to leverage indexes 2. **Use aggregated tables** - Query hourly/daily/month tables for long ranges 3. **Limit result sets** - Add LIMIT clauses to prevent large results 4. **Filter before grouping** - Use WHERE instead of HAVING when possible 5. **Avoid SELECT \*** - Only select columns you need ## Query Limits | Resource | Limit | Error Code | | ---------------- | ---------- | ----------------------------- | | Execution time | 30 seconds | `query_execution_timeout` | | Memory usage | 1 GB | `query_memory_limit_exceeded` | | Rows to read | 10 million | `query_rows_limit_exceeded` | | Queries per hour | 1000 | `query_quota_exceeded` | See [Query Restrictions](/analytics/query-restrictions) for more details on query limits and restrictions. # Troubleshooting Source: https://unkey.com/docs/analytics/troubleshooting Common issues and solutions for analytics queries # Analytics Troubleshooting ## Getting Empty Results? **Check these common causes:** ### Time Range Issues * Your workspace might be new with no verification data yet * Try a shorter time range: `WHERE time >= now() - INTERVAL 1 HOUR` * Verify your time filters are working: `SELECT MAX(time) as latest FROM key_verifications_v1` ### Filter Problems * Wrong `key_space_id` - Find your API ID in dashboard settings * Empty `external_id` values - Filter them out: `WHERE external_id != ''` * Case sensitivity - Check exact values: `SELECT DISTINCT external_id FROM key_verifications_v1 LIMIT 10` ### Data Availability * Analytics may have a few minutes delay * Check recent data: `SELECT COUNT(*) FROM key_verifications_v1 WHERE time >= now() - INTERVAL 1 HOUR` ## Queries Timing Out? **Try these optimizations:** ### Use Aggregated Tables ```sql theme={"system"} -- Instead of raw table for long ranges: -- Slow for 7+ days SELECT COUNT(*) FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY -- Fast for 7+ days SELECT SUM(count) FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 7 DAY ``` ### Add Time Filters First ```sql theme={"system"} -- Always filter by time early (uses indexes) WHERE time >= now() - INTERVAL 7 DAY AND external_id = 'user_123' -- Additional filters after time ``` ### Limit Result Size ```sql theme={"system"} -- Prevent large result sets SELECT external_id, SUM(count) as usage FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY external_id ORDER BY usage DESC LIMIT 100 -- Add LIMIT for large datasets ``` ## API Errors? **Common fixes:** ### Missing Property 'query' ```json theme={"system"} // Wrong field name {"sql": "SELECT COUNT(*) FROM key_verifications_v1"} // Correct field name {"query": "SELECT COUNT(*) FROM key_verifications_v1"} ``` See [invalid\_input](/errors/unkey/application/invalid_input) for API request format issues. ### Permission Denied * Ensure your root key has `api.*.read_analytics` or `api..read_analytics` * Check key is not expired or revoked * Verify workspace ID matches your key's permissions See [forbidden](/errors/unkey/authorization/forbidden) for permission issues. ### Invalid Function Error * Check [Query Restrictions](/analytics/query-restrictions#function-allow-list) for allowed functions * Some ClickHouse functions are blocked for security * Use alternative approaches from [Quick Reference](/analytics/quick-reference) * See [invalid\_analytics\_function](/errors/user/bad_request/invalid_analytics_function) for details ### Invalid Table Error * Only analytics tables are accessible (no `system.*` or `information_schema.*`) * Use table names from [Schema Reference](/analytics/schema-reference) * See [invalid\_analytics\_table](/errors/user/bad_request/invalid_analytics_table) for details ### Query Not Supported * Only SELECT queries are allowed in analytics * INSERT, UPDATE, DELETE, etc. are blocked * See [invalid\_analytics\_query\_type](/errors/user/bad_request/invalid_analytics_query_type) for details ## Performance Problems? **Optimization tips:** ### Choose Right Table * `< 1 hour`: `key_verifications_v1` (raw) * `< 24 hours`: `key_verifications_per_minute_v1` * `< 30 days`: `key_verifications_per_hour_v1` * `< 1 year`: `key_verifications_per_day_v1` * `> 1 year`: `key_verifications_per_month_v1` ### Filter Before Grouping ```sql theme={"system"} -- Less efficient SELECT external_id, SUM(count) as usage FROM key_verifications_per_day_v1 GROUP BY external_id HAVING SUM(count) > 1000 -- More efficient SELECT external_id, SUM(count) as usage FROM key_verifications_per_day_v1 WHERE time >= now() - INTERVAL 30 DAY -- Filter first GROUP BY external_id HAVING SUM(count) > 1000 ``` ### Avoid SELECT \* * Only select columns you need * Reduces memory usage and network transfer For complete error reference, see the [Error Documentation](/errors/overview). If you continue having issues, contact us at [support@unkey.dev](mailto:support@unkey.dev) with your query and error details. # 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 theme={"system"} Authorization: Bearer unkey_1234567890 ``` Example request: ```bash theme={"system"} 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 theme={"system"} { "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 theme={"system"} { "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" theme={"system"} { "name": "Production API" } ``` ```json title="Create API Response Diff" icon="database" expandable theme={"system"} // 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" theme={"system"} # 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" theme={"system"} # 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 theme={"system"} // 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" theme={"system"} # 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 theme={"system"} // 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 theme={"system"} // 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" theme={"system"} // 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" theme={"system"} # 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" theme={"system"} { "apiId": "api_123" } ``` ```json title="Delete API Response Diff" icon="check-circle" theme={"system"} // 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" theme={"system"} # 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" theme={"system"} 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" theme={"system"} # 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" theme={"system"} 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" theme={"system"} // 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" theme={"system"} // 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 theme={"system"} // 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" theme={"system"} // 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" theme={"system"} 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 theme={"system"} // ❌ 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 theme={"system"} // 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} // 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 theme={"system"} // v1: Direct response { "identityId": "id_abc123def456" } // v2: Structured response with meta wrapper { "meta": { "requestId": "req_createidentity123" }, "data": { "identityId": "id_abc123def456" } } ``` ```bash title="Migration Examples" theme={"system"} # 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" theme={"system"} # 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 theme={"system"} // 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" theme={"system"} # 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 theme={"system"} // 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 theme={"system"} // 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 theme={"system"} // 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" theme={"system"} # 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 theme={"system"} // 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 theme={"system"} // v1: Empty object response {} // v2: Structured response with meta wrapper { "meta": { "requestId": "req_deleteidentity999" } } ``` ```bash title="Migration Examples" theme={"system"} # 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 theme={"system"} npm install @unkey/api@latest ``` ```bash theme={"system"} pip install unkey.py@^2.0.4 ``` ```bash theme={"system"} 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 theme={"system"} { "valid": true, "keyId": "key_123", "name": "Production API Key" } ``` #### v2 Response Format ```json theme={"system"} { "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 theme={"system"} { "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 theme={"system"} // 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} { "key": "sk_1234abcdef" } ``` ```bash v1 cURL icon=terminal theme={"system"} 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 theme={"system"} 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 theme={"system"} // 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 theme={"system"} // 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} // 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} // 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 theme={"system"} curl -X GET "https://api.unkey.dev/v1/keys.getKey?keyId=key_123" \ -H "Authorization: Bearer " ``` ```bash v2 cURL {1,3} icon=terminal theme={"system"} 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 theme={"system"} // v1 Request { "keyId": "key_123" } // v2 Request (enhanced) { "keyId": "key_123", "permanent": false // [!code ++] } ``` ```json Delete Key Response Diff icon=check-circle theme={"system"} // v1 Response (direct empty response) {} // [!code --] // v2 Response { "meta": { // [!code ++] "requestId": "req_deletekey789" // [!code ++] }, // [!code ++] "data": {} // [!code ++] } ``` ```bash v1 cURL icon=terminal theme={"system"} 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 theme={"system"} 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 theme={"system"} // 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 theme={"system"} // v1 Response (direct empty response) {} // [!code --] // v2 Response { "meta": { // [!code ++] "requestId": "req_updatekey456" // [!code ++] }, // [!code ++] "data": {} } ``` ```bash v1 cURL icon=terminal theme={"system"} 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 theme={"system"} 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 theme={"system"} // 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 theme={"system"} // 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} // v1 & v2 Request (unchanged) { "key": "your_api_key_here" } ``` ```json Whoami Response Diff expandable icon=database theme={"system"} // 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} { "keyId": "key_123", "expiration": 0 // This expires the key directly } ``` ```json expandable theme={"system"} { "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 theme={"system"} // 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 theme={"system"} // 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} // 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 theme={"system"} // 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} // 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 theme={"system"} // 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} // 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 theme={"system"} // 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} // 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 theme={"system"} // v1 Response (direct empty response) {} // [!code --] // v2 Response { "meta": { // [!code ++] "requestId": "req_removeroles456" // [!code ++] }, // [!code ++] "data": [] } ``` ```bash v1 cURL icon=terminal theme={"system"} 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 theme={"system"} 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 theme={"system"} // 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 theme={"system"} // 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} // 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 theme={"system"} // 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" theme={"system"} # 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 theme={"system"} { "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" theme={"system"} # 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 theme={"system"} { "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" theme={"system"} # 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 theme={"system"} { "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" theme={"system"} # 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" theme={"system"} { "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" theme={"system"} # 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 theme={"system"} { "meta": { "requestId": "req_createrole123" }, "data": { "roleId": "role_abc123def456" } } ``` ```bash title="Assign Role to Key" icon="key" theme={"system"} # 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" theme={"system"} # 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 theme={"system"} { "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" theme={"system"} # 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 theme={"system"} { "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" theme={"system"} # 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" theme={"system"} { "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" theme={"system"} // 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" theme={"system"} // 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" theme={"system"} // 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" theme={"system"} # 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" theme={"system"} // 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" theme={"system"} // 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" theme={"system"} { "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" theme={"system"} { "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" theme={"system"} { "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" theme={"system"} # 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" theme={"system"} { "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 theme={"system"} // 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" theme={"system"} 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" theme={"system"} 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 theme={"system"} { "namespace": "api_requests", "identifier": "premium_user_456", "limit": 10000, "duration": 3600000, "async": false } ``` ```json title="Set Override Response Diff" icon="check-circle" expandable theme={"system"} // 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" theme={"system"} 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" theme={"system"} 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" theme={"system"} { "namespace": "api_requests", "identifier": "premium_user_456" } ``` ```json title="Get Override Response Diff" icon="database" expandable theme={"system"} // 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" theme={"system"} 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" theme={"system"} 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" theme={"system"} { "namespace": "api_requests", "limit": 100, "cursor": "optional_cursor" } ``` ```json title="List Overrides Response Diff" icon="database" expandable theme={"system"} // 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" theme={"system"} curl -X GET "https://api.unkey.dev/v1/ratelimits.listOverrides?namespaceName=api_requests&limit=100" \ -H "Authorization: Bearer " ``` ```bash title="v2 cURL" icon="terminal" theme={"system"} 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" theme={"system"} { "namespace": "api_requests", "identifier": "premium_user_456" } ``` ```json title="Delete Override Response Diff" icon="check-circle" theme={"system"} // v1 Response (direct empty response) {} // [!code --] // v2 Response { "meta": { // [!code ++] "requestId": "req_deleteoverride999" // [!code ++] }, // [!code ++] "data": {} // [!code ++] } ``` ```bash title="v1 cURL" icon="terminal" theme={"system"} 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" theme={"system"} 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 theme={"system"} // 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 theme={"system"} // 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 theme={"system"} // 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" theme={"system"} // 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" theme={"system"} // 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" theme={"system"} // 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" theme={"system"} // 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 theme={"system"} { "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" theme={"system"} { "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" theme={"system"} // 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} Authorization: Bearer unkey_1234567890 ``` Example request: ```bash theme={"system"} 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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} 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 theme={"system"} { "meta": { "requestId": "req_abc123xyz789" }, "data": { // Operation-specific response data } } ``` For paginated responses, we include a pagination object: ```json theme={"system"} { "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 theme={"system"} // 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 theme={"system"} 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. We're currently working on a v2 API for analytics with advanced SQL querying capabilities. This feature is opt-in only and not yet publicly available. ## 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 theme={"system"} [ "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} theme={"system"} 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 Tags can be used to filter and aggregate data in your analytics. # 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 theme={"system"} 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 theme={"system"} 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. # 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} 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 theme={"system"} curl --request POST \ --url https://api.unkey.com/v2/keys.verifyKey \ --header 'Content-Type: application/json' \ --data '{ "key": "" }' ``` ```json theme={"system"} { "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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} # 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 theme={"system"} # 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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} curl --request POST \ --url https://api.unkey.com/v2/keys.verifyKey \ --header 'Content-Type: application/json' \ --data '{ "key": "", "credits": { "cost": 1 } }' ``` ```json theme={"system"} { "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 theme={"system"} 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 theme={"system"} curl --request POST \ --url https://api.unkey.com/v2/keys.verifyKey \ --header 'Content-Type: application/json' \ --data '{ "key": "" }' ``` ```json theme={"system"} { "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} theme={"system"} 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 theme={"system"} 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 theme={"system"} 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/v2/keys/create-api-key). ```bash theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} # 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 # precondition_failed Source: https://unkey.com/docs/errors/unkey/application/precondition_failed PreconditionFailed indicates a precondition check failed. `err:unkey:application:precondition_failed` ```json Example theme={"system"} { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "Vault hasn't been set up.", "status": 412, "title": "Precondition Failed", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/application/precondition_failed" } } ``` ## What Happened? This error occurs when your request is valid, but a precondition required to fulfill it is not met. Unlike validation errors where your input is invalid, precondition failures indicate that the system or resource is not configured correctly to handle your request. Common scenarios that trigger this error: * **API not configured for keys**: The API you're trying to create keys for doesn't have key authentication set up * **Vault not configured**: You're trying to create recoverable keys or decrypt keys, but the vault service isn't set up for your workspace * **Encryption not enabled**: You're requesting key encryption/decryption on an API that doesn't have encryption enabled * **Rate limit configuration missing**: You're checking a rate limit that doesn't exist for the key or its associated identity Here's an example of a request that would trigger this error: ```bash theme={"system"} # Attempting to create a recoverable key when vault isn't configured curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_ROOT_KEY" \ -d '{ "apiId": "api_1234567890", "prefix": "test", "recoverable": true }' ``` ## How To Fix The fix depends on which precondition failed. Check the error's `detail` field for specific information: ### Vault Not Set Up If you see "Vault hasn't been set up", you have two options: 1. **Configure the vault service** for your workspace (contact Unkey support if you need assistance) 2. **Remove the encryption requirement** from your request: ```bash theme={"system"} # Create a non-recoverable key instead curl -X POST https://api.unkey.com/v2/keys.createKey \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_ROOT_KEY" \ -d '{ "apiId": "api_1234567890", "prefix": "test", "recoverable": false }' ``` ### API Not Set Up for Keys If you see "The requested API is not set up to handle keys", you need to: 1. Verify the API ID is correct 2. Ensure the API has key authentication configured in your Unkey dashboard 3. Create or configure the key authentication settings for your API ### API Not Set Up for Encryption If you see "This API does not support key encryption", either: 1. Enable encrypted key storage for the API in your Unkey dashboard settings 2. Remove the `recoverable: true` option or `decrypt: true` parameter from your request ### Rate Limit Not Found If you see a message about a requested rate limit not existing: 1. Verify the rate limit name is correct 2. Create the rate limit configuration for the key or its identity 3. Ensure the rate limit is associated with the correct resource ## Common Mistakes * **Assuming features are enabled by default**: Features like vault encryption and rate limits require explicit configuration * **Wrong API configuration**: Trying to use encryption features on an API that wasn't set up for it * **Missing rate limit setup**: Referencing rate limits that haven't been created yet * **Workspace-level configuration issues**: Some features need to be enabled at the workspace level before they can be used ## Related Errors * [err:unkey:application:invalid\_input](./invalid_input) - When your request input fails validation * [err:unkey:data:api\_not\_found](../data/api_not_found) - When the API itself doesn't exist * [err:unkey:application:service\_unavailable](./service_unavailable) - When a required service is temporarily unavailable # 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} 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 theme={"system"} # 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} // 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} # 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 # analytics_connection_failed Source: https://unkey.com/docs/errors/unkey/data/analytics_connection_failed ConnectionFailed indicates the connection to the analytics database failed. `err:unkey:data:analytics_connection_failed` # analytics_not_configured Source: https://unkey.com/docs/errors/unkey/data/analytics_not_configured NotConfigured indicates analytics is not configured for the workspace. `err:unkey:data:analytics_not_configured` # 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} // 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} 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 # key_space_not_found Source: https://unkey.com/docs/errors/unkey/data/key_space_not_found NotFound indicates the requested key space was not found. `err:unkey:data:key_space_not_found` # permission_already_exists Source: https://unkey.com/docs/errors/unkey/data/permission_already_exists A permission with this slug already exists err:unkey:data:permission\_already\_exists ```json Example theme={"system"} { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "A permission with slug \"admin\" already exists in this workspace", "status": 409, "title": "Conflict", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/permission/duplicate" } } ``` ## What Happened? This error occurs when you're trying to create a permission with a name that already exists in your Unkey workspace. Permission names must be unique within a workspace to avoid confusion and maintain proper access control. Common scenarios that trigger this error: * Creating a permission with a name that's already in use * Re-creating a previously deleted permission with the same name * Migration or import processes that don't check for existing permissions * Duplicate API calls due to retries or network issues Here's an example of a request that would trigger this error: ```bash theme={"system"} # Attempting to create a permission with a name that already exists curl -X POST https://api.unkey.com/v2/permissions.createPermission \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "name": "admin", "slug": "admin-access", "description": "Administrator access" }' ``` ## How To Fix When you encounter this error, you have several options: 1. **Use a different name**: If creating a new permission, use a unique name 2. **Get the existing permission**: If you just need the permission information, retrieve it rather than creating it 3. **List existing permissions**: Check what permissions already exist before creating new ones 4. **Implement idempotent creation**: Use a get-or-create pattern in your code Here's how to list existing permissions: ```bash theme={"system"} curl -X POST https://api.unkey.com/v2/permissions.listPermissions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{}' ``` Or implement a get-or-create pattern in your code: ```javascript theme={"system"} // Pseudocode for get-or-create pattern async function getOrCreatePermission(name, slug, description) { try { // Try to create the permission return await createPermission(name, slug, description); } catch (error) { // If it already exists (409 error), get it instead if (error.status === 409) { // Extract the permission name from the error message and get it const permissions = await listPermissions(); return permissions.find( (p) => p.name.toLowerCase() === name.toLowerCase() ); } // Otherwise, rethrow the error throw error; } } ``` ## Common Mistakes * **Not checking for existing permissions**: Failing to check if a permission already exists before creating it * **Case sensitivity**: Permission names are case-insensitive - "Admin" and "admin" are the same * **Retry loops**: Repeatedly trying to create the same permission after a failure * **Cross-environment duplication**: Using the same permission names across development and production without proper namespacing ## Related Errors * [err:unkey:data:permission\_not\_found](./permission_not_found) - When the requested permission doesn't exist * [err:unkey:authorization:insufficient\_permissions](../authorization/insufficient_permissions) - When you don't have permission to manage permissions # 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 theme={"system"} { "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 theme={"system"} 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 theme={"system"} 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 Gone Source: https://unkey.com/docs/errors/unkey/data/ratelimit_namespace_gone ## Description This error occurs when you attempt to use a ratelimit namespace that has been deleted. Once a namespace is deleted, it cannot be restored through the API or dashboard. ## Error Code `unkey/data/ratelimit_namespace_gone` ## HTTP Status Code `410 Gone` ## Cause The ratelimit namespace you're trying to access was previously deleted and is no longer available through the API or dashboard. ## Resolution Contact [support@unkey.dev](mailto:support@unkey.dev) with your workspace ID and namespace name if you need this namespace restored. ## Prevention To avoid accidentally deleting namespaces: * Restrict namespace deletion via workspace permissions * Carefully review namespace-deletion requests before confirming ## Related * [Ratelimit Namespace Not Found](/errors/unkey/data/ratelimit_namespace_not_found) * [Rate Limiting Documentation](/ratelimiting/introduction) # 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} # 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 theme={"system"} { "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 theme={"system"} # 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 theme={"system"} 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 theme={"system"} 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_already_exists Source: https://unkey.com/docs/errors/unkey/data/role_already_exists A role with this name already exists err:unkey:data:role\_already\_exists ```json Example theme={"system"} { "meta": { "requestId": "req_2c9a0jf23l4k567" }, "error": { "detail": "A role with name \"admin\" already exists in this workspace", "status": 409, "title": "Conflict", "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/role/duplicate" } } ``` ## What Happened? This error occurs when you're trying to create a role with a name that already exists in your Unkey workspace. Role names must be unique within a workspace to avoid confusion and maintain proper access control. Common scenarios that trigger this error: * Creating a role with a name that's already in use * Re-creating a previously deleted role with the same name * Migration or import processes that don't check for existing roles * Duplicate API calls due to retries or network issues Here's an example of a request that would trigger this error: ```bash theme={"system"} # Attempting to create a role with a name that already exists curl -X POST https://api.unkey.com/v2/permissions.createRole \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{ "name": "admin", "description": "Administrator role with full access" }' ``` ## How To Fix When you encounter this error, you have several options: 1. **Use a different name**: If creating a new role, use a unique name 2. **Get the existing role**: If you just need the role information, retrieve it rather than creating it 3. **List existing roles**: Check what roles already exist before creating new ones 4. **Implement idempotent creation**: Use a get-or-create pattern in your code Here's how to list existing roles: ```bash theme={"system"} curl -X POST https://api.unkey.com/v2/permissions.listRoles \ -H "Content-Type: application/json" \ -H "Authorization: Bearer unkey_YOUR_API_KEY" \ -d '{}' ``` Or implement a get-or-create pattern in your code: ```javascript theme={"system"} // Pseudocode for get-or-create pattern async function getOrCreateRole(name, description) { try { // Try to create the role return await createRole(name, description); } catch (error) { // If it already exists (409 error), get it instead if (error.status === 409) { // Extract the role name from the error message and get it const roles = await listRoles(); return roles.find((r) => r.name.toLowerCase() === name.toLowerCase()); } // Otherwise, rethrow the error throw error; } } ``` ## Common Mistakes * **Not checking for existing roles**: Failing to check if a role already exists before creating it * **Case sensitivity**: Role names are case-insensitive - "Admin" and "admin" are the same * **Retry loops**: Repeatedly trying to create the same role after a failure * **Cross-environment duplication**: Using the same role names across development and production without proper namespacing ## Related Errors * [err:unkey:data:role\_not\_found](./role_not_found) - When the requested role doesn't exist * [err:unkey:authorization:insufficient\_permissions](../authorization/insufficient_permissions) - When you don't have permission to manage roles # 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 theme={"system"} { "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 theme={"system"} 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 theme={"system"} 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 theme={"system"} { "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 # client_closed_request Source: https://unkey.com/docs/errors/user/bad_request/client_closed_request Client cancelled the request before the server could complete processing `err:user:bad_request:client_closed_request` ```json Example theme={"system"} { "meta": { "requestId": "req_4dgzrNP3Je5mU1tD" }, "error": { "detail": "Client closed request", "status": 499, "title": "Client Closed Request", "type": "https://unkey.com/docs/errors/user/bad_request/client_closed_request", "errors": [] } } ``` ## What Happened? Your client cancelled the request before our server finished processing it. This happens when: * Your client timeout is shorter than the processing time * Network connection was lost during the request * Your application cancelled the request (user navigated away, etc.) * Load balancer or proxy terminated the connection **HTTP Status 499** is a non-standard but widely used status code that means "Client Closed Request". It was originally defined by nginx and is now used by many web servers to indicate when a client disconnects before receiving the full response. ## Common Causes ### 1. Client Timeout Too Short Your HTTP client may have a timeout that's shorter than our processing time. Make sure your client timeout allows enough time for the operation to complete (typically 30+ seconds). ### 2. Command Line Tools Tools like `curl` or `timeout` commands may have short default timeouts. Use `--max-time 30` with curl or longer timeout values with other tools. ### 3. User Interface Cancellations In web applications, users might navigate away or close tabs before requests complete. This is normal user behavior and should be handled gracefully in your application. ## How to Fix It ### 1. Increase Client Timeouts Make sure your client timeout is reasonable for the operation (typically 30+ seconds). Check your HTTP client documentation for timeout configuration options. ### 2. Handle Network Issues If you're experiencing network connectivity problems that cause your client to disconnect, check your network stability and consider increasing timeout values. ### 3. Check Your Infrastructure If you're using proxies, load balancers, or CDNs: * Verify their timeout settings * Check if they're terminating long-running requests * Ensure they're configured to handle the expected request duration ## When You See This Error ### It's Usually Not Our Fault Status 499 means your client cancelled the request, so the issue is typically: * Your client timeout settings * Network connectivity problems * User behavior (closing browser, etc.) ### Check Your Logs Look for patterns: * Is it happening at specific times? * Are certain operations more affected? * Are there network errors in your client logs? ### You Won't See 499 Errors in Your Client **Important:** Since the client cancels the request before getting a response, your application typically won't receive a 499 status code. Instead, you'll see: * Network timeout errors * Request cancelled/aborted errors * Connection reset errors You can only see 499 errors in server logs, not in your client application. This is why this error mainly helps server operators understand why requests failed. ## Difference from Other Errors * **408 Request Timeout**: Server took too long (server-side issue) * **499 Client Closed Request**: Client cancelled early (client-side issue) * **504 Gateway Timeout**: Upstream service timeout (infrastructure issue) If you're seeing mostly 499 errors, focus on your client configuration and network connectivity rather than server performance. # invalid_analytics_function Source: https://unkey.com/docs/errors/user/bad_request/invalid_analytics_function Your query uses a function that isn't allowed for security reasons. `err:user:bad_request:invalid_analytics_function` ```json Example theme={"system"} { "meta": { "requestId": "req_4dgzrNP3Je5mU1tD" }, "error": { "detail": "Function 'file' is not allowed", "status": 400, "title": "Bad Request", "type": "https://unkey.com/docs/errors/user/bad_request/invalid_analytics_function" } } ``` ## What Happened? Your query tried to use a function that's blocked for security reasons! For security, only safe functions are allowed in analytics queries. Functions that could: * Read files from the server (`file`, `executable`) * Make network requests (`url`, `remote`) * Access external systems (`mysql`, `postgresql`, `s3`, `hdfs`) * Modify data or system state ...are all blocked. ## How to Fix It ### 1. Use Allowed Functions Stick to standard analytics functions: ```sql Wrong - Blocked function theme={"system"} SELECT file('/etc/passwd') FROM key_verifications_v1 ``` ```sql Correct - Safe analytics functions theme={"system"} SELECT toStartOfHour(time) as hour, COUNT(*) as total, AVG(response_time) as avg_response FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY GROUP BY hour ``` ### 2. Common Safe Functions These are examples of allowed functions: **Aggregate functions:** * `COUNT()`, `SUM()`, `AVG()`, `MIN()`, `MAX()` * `uniq()`, `groupArray()` **Date/time functions:** * `now()`, `today()`, `yesterday()` * `toStartOfHour()`, `toStartOfDay()`, `toStartOfWeek()` * `toDate()`, `toDateTime()` **String functions:** * `concat()`, `substring()`, `lower()`, `upper()` * `length()`, `position()` **Mathematical functions:** * `round()`, `floor()`, `ceil()` * `abs()`, `sqrt()`, `pow()` **Conditional functions:** * `if()`, `multiIf()` * `CASE WHEN ... THEN ... END` ### 3. Remove Dangerous Functions ```sql Blocked - File access theme={"system"} SELECT file('/path/to/file') FROM key_verifications_v1 ``` ```sql Blocked - Network access theme={"system"} SELECT * FROM url('http://example.com/data') ``` ```sql Blocked - External DB theme={"system"} SELECT * FROM mysql('host:port', 'db', 'table', 'user', 'pass') ``` ```sql Safe Alternative - Use only your analytics data theme={"system"} SELECT api_id, COUNT(*) as verifications FROM key_verifications_v1 WHERE time >= now() - INTERVAL 1 DAY GROUP BY api_id ``` ## Commonly Blocked Functions These functions are blocked for security: | Function | Why Blocked | | -------------------------------------- | ------------------------ | | `file()`, `executable()` | File system access | | `url()`, `remote()` | Network requests | | `mysql()`, `postgresql()`, `mongodb()` | External database access | | `s3()`, `hdfs()`, `azureBlobStorage()` | External storage access | | `dictGet()`, `dictGetOrDefault()` | Dictionary access | Need a specific function that's blocked? [Contact support](mailto:support@unkey.dev) to discuss your use case - we may be able to safely enable it! # invalid_analytics_query Source: https://unkey.com/docs/errors/user/bad_request/invalid_analytics_query Your SQL query has a syntax error. `err:user:bad_request:invalid_analytics_query` ```json Example theme={"system"} { "meta": { "requestId": "req_4dgzrNP3Je5mU1tD" }, "error": { "detail": "Syntax error: Expected identifier, got 'FROM' at position 15", "status": 400, "title": "Bad Request", "type": "https://unkey.com/docs/errors/user/bad_request/invalid_analytics_query" } } ``` ## What Happened? Your SQL query has a syntax error! The query parser found invalid SQL syntax that prevents it from being executed. Common causes include: * Missing or extra commas * Unclosed quotes or parentheses * Typos in SQL keywords * Invalid column or table names ## How to Fix It ### 1. Check for Missing Commas ```sql Wrong - Missing comma theme={"system"} SELECT api_id COUNT(*) as total FROM key_verifications_v1 ``` ```sql Correct theme={"system"} SELECT api_id, COUNT(*) as total FROM key_verifications_v1 ``` ### 2. Match Quotes and Parentheses ```sql Wrong - Unclosed quote theme={"system"} SELECT * FROM key_verifications_v1 WHERE api_id = 'api_123 ``` ```sql Correct theme={"system"} SELECT * FROM key_verifications_v1 WHERE api_id = 'api_123' ``` ### 3. Use Correct SQL Keywords ```sql Wrong - Typo in SELECT theme={"system"} SELCT * FROM key_verifications_v1 ``` ```sql Correct theme={"system"} SELECT * FROM key_verifications_v1 ``` ### 4. Verify Column Names Make sure you're using valid column names from your analytics tables: ```sql theme={"system"} -- ✓ Valid columns SELECT time, api_id, outcome, key_id FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY ``` ## Need Help? If you're stuck with a syntax error: 1. **Check the error message** - It usually tells you exactly where the problem is 2. **Test incrementally** - Start with a simple `SELECT *` and add complexity step by step 3. **Use a SQL validator** - Many online tools can help spot syntax errors # invalid_analytics_query_type Source: https://unkey.com/docs/errors/user/bad_request/invalid_analytics_query_type Only SELECT queries are allowed for analytics - you tried to use INSERT, UPDATE, DELETE, or another unsupported operation. `err:user:bad_request:invalid_analytics_query_type` ```json Example theme={"system"} { "meta": { "requestId": "req_4dgzrNP3Je5mU1tD" }, "error": { "detail": "Only SELECT queries are allowed", "status": 400, "title": "Bad Request", "type": "https://unkey.com/docs/errors/user/bad_request/invalid_analytics_query_type" } } ``` ## What Happened? You tried to run a query that modifies data or isn't a SELECT! Analytics queries are read-only - you can only SELECT data, not modify it. Blocked query types: * `INSERT` - Adding new data * `UPDATE` - Modifying existing data * `DELETE` - Removing data * `DROP` - Deleting tables * `CREATE` - Creating tables * `ALTER` - Modifying table structure * `TRUNCATE` - Clearing tables ## How to Fix It ### 1. Use SELECT Instead Analytics is for querying data, not modifying it: ```sql Wrong - INSERT not allowed theme={"system"} INSERT INTO key_verifications_v1 VALUES (...) ``` ```sql Wrong - UPDATE not allowed theme={"system"} UPDATE key_verifications_v1 SET outcome = 'VALID' ``` ```sql Wrong - DELETE not allowed theme={"system"} DELETE FROM key_verifications_v1 WHERE time < now() - INTERVAL 30 DAY ``` ```sql Correct - SELECT queries only theme={"system"} SELECT api_id, outcome, COUNT(*) as total FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY GROUP BY api_id, outcome ``` ### 2. Query Your Data The analytics endpoint is for analyzing your verification data: ```sql theme={"system"} -- ✓ Count verifications by outcome SELECT outcome, COUNT(*) as total FROM key_verifications_v1 WHERE time >= now() - INTERVAL 1 DAY GROUP BY outcome -- ✓ Get hourly verification rates SELECT toStartOfHour(time) as hour, COUNT(*) as verifications FROM key_verifications_v1 WHERE time >= now() - INTERVAL 24 HOUR GROUP BY hour ORDER BY hour -- ✓ Find most active APIs SELECT api_id, COUNT(*) as requests FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY GROUP BY api_id ORDER BY requests DESC LIMIT 10 ``` ### 3. Use the Correct API for Data Modification If you need to modify data, use the appropriate Unkey API endpoints: | What You Want | Use This API | | ------------------ | ------------------------------ | | Create a key | `POST /v2/keys.createKey` | | Update a key | `PATCH /v2/keys.updateKey` | | Delete a key | `POST /v2/keys.deleteKey` | | Modify permissions | `POST /v2/keys.addPermissions` | ## Why Read-Only? Analytics queries are read-only for several reasons: 1. **Data integrity** - Verification history should never be modified 2. **Performance** - Read-only queries can be heavily optimized 3. **Security** - Prevents accidental or malicious data corruption 4. **Audit trail** - Preserves accurate historical records Analytics is for understanding your data, not changing it. Use the main Unkey API for creating, updating, or deleting resources. # invalid_analytics_table Source: https://unkey.com/docs/errors/user/bad_request/invalid_analytics_table Your query references a table that doesn't exist or isn't allowed. `err:user:bad_request:invalid_analytics_table` ```json Example theme={"system"} { "meta": { "requestId": "req_4dgzrNP3Je5mU1tD" }, "error": { "detail": "Access to table 'system.tables' is not allowed", "status": 400, "title": "Bad Request", "type": "https://unkey.com/docs/errors/user/bad_request/invalid_analytics_table" } } ``` ## What Happened? Your query tried to access a table that either doesn't exist or isn't allowed for security reasons. For security, only specific analytics tables are accessible: * `key_verifications_v1` - Raw key verification events * `key_verifications_per_minute_v1` - Minute-level aggregates * `key_verifications_per_hour_v1` - Hour-level aggregates * `key_verifications_per_day_v1` - Day-level aggregates * `key_verifications_per_month_v1` - Month-level aggregates System tables (like `system.*`) and other database tables are blocked. ## How to Fix It ### 1. Use the Correct Table Name ```sql Wrong - System table theme={"system"} SELECT * FROM system.tables ``` ```sql Correct - Analytics table theme={"system"} SELECT * FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY ``` ### 2. Fix Typos in Table Names ```sql Wrong - Typo theme={"system"} SELECT * FROM key_verification WHERE time >= now() - INTERVAL 1 DAY ``` ```sql Correct theme={"system"} SELECT * FROM key_verifications_v1 WHERE time >= now() - INTERVAL 1 DAY ``` ## Available Tables | Table Name | Description | | --------------------------------- | ----------------------- | | `key_verifications_v1` | Raw verification events | | `key_verifications_per_minute_v1` | Minute-level aggregates | | `key_verifications_per_hour_v1` | Hour-level aggregates | | `key_verifications_per_day_v1` | Day-level aggregates | | `key_verifications_per_month_v1` | Month-level aggregates | # 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 theme={"system"} { "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 theme={"system"} # ❌ 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 theme={"system"} # ❌ 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 theme={"system"} # ❌ 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 theme={"system"} # ❌ 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 theme={"system"} # ❌ 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} 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 theme={"system"} 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. ``` ``` # request_timeout Source: https://unkey.com/docs/errors/user/bad_request/request_timeout Request took too long to process and exceeded the server timeout `err:user:bad_request:request_timeout` ```json Example theme={"system"} { "meta": { "requestId": "req_4dgzrNP3Je5mU1tD" }, "error": { "detail": "Request timeout", "status": 408, "title": "Request Timeout", "type": "https://unkey.com/docs/errors/user/bad_request/request_timeout", "errors": [] } } ``` ## What Happened? Your request took too long to process and was automatically terminated by the server. This typically happens when: * Database queries are running slowly * External services are responding slowly * The API is under heavy load * Network connectivity issues are causing delays ## How to Fix It ### 1. Retry the Request Most timeout errors are temporary. Simply retry your request after a short delay with exponential backoff (wait longer after each failed attempt). ### 2. Check Your Network Slow or unstable network connections can cause timeouts: * Test from a different network or location * Check if you're experiencing high latency to our servers ## When This Happens Often If you're seeing frequent timeout errors: ### Check Our Status Page Visit [status.unkey.com](https://status.unkey.com) to see if we're experiencing any service issues. ### Contact Support **Still having issues?** We're here to help! [Contact our support team](mailto:support@unkey.dev) and include: * Your request IDs from the error responses * The approximate time the errors occurred * Your typical request patterns and volume We can investigate what might be causing the slowdowns and help optimize your integration. ## Difference from Other Timeout Errors * **408 Request Timeout**: The server took too long to process your request (this error) * **499 Client Closed Request**: Your client cancelled the request before the server finished * **504 Gateway Timeout**: An upstream service (like a database) timed out If you're seeing 408 errors, the issue is usually on our side or with network connectivity. # query_quota_exceeded Source: https://unkey.com/docs/errors/user/too_many_requests/query_quota_exceeded You've exceeded your workspace's analytics query quota for the current time window. `err:user:too_many_requests:query_quota_exceeded` ```json Example theme={"system"} { "meta": { "requestId": "req_4dgzrNP3Je5mU1tD" }, "error": { "detail": "Workspace has exceeded the analytics query quota of 1000 queries per hour", "status": 429, "title": "Too Many Requests", "type": "https://unkey.com/docs/errors/user/too_many_requests/query_quota_exceeded" } } ``` ## What Happened? Your workspace has made too many analytics queries in a short period! We limit the number of queries you can run per hour to keep the analytics service fast and reliable for everyone. This is a rate limit on the **number of queries**, not about individual query complexity. ## How to Fix It ### 1. Wait and Retry The quota resets every hour. Wait a bit and try your query again. ### 2. Cache Your Results Instead of running the same query repeatedly, cache the results in your application: ```typescript Bad - Queries on Every Request theme={"system"} app.get('/dashboard', async (req, res) => { // This runs a query EVERY time someone loads the dashboard const stats = await fetch('https://api.unkey.com/v1/analytics', { method: 'POST', headers: { 'Authorization': 'Bearer unkey_XXX' }, body: JSON.stringify({ query: 'SELECT COUNT(*) FROM key_verifications' }) }) res.json(stats) }) ``` ```typescript Better - Cache for 5 Minutes theme={"system"} import { Cache } from 'your-cache-library' const cache = new Cache() app.get('/dashboard', async (req, res) => { // Check cache first let stats = cache.get('dashboard-stats') if (!stats) { // Only query if cache is empty stats = await fetch('https://api.unkey.com/v1/analytics', { method: 'POST', headers: { 'Authorization': 'Bearer unkey_XXX' }, body: JSON.stringify({ query: 'SELECT COUNT(*) FROM key_verifications' }) }) // Cache for 5 minutes cache.set('dashboard-stats', stats, { ttl: 300 }) } res.json(stats) }) ``` ### 3. Batch Your Queries If you're making multiple queries, try to combine them into a single query with JOINs or subqueries. ### 4. Use Webhooks Instead For real-time updates, consider using webhooks instead of polling the analytics API repeatedly. ## Default Quota | Plan | Queries per Hour | | ---------- | ---------------- | | Free | 1,000 | | Pro | 10,000 | | Enterprise | Custom | ## Need a Higher Quota? **Running into limits often?** We can increase your quota! [Contact our support team](mailto:support@unkey.dev) and tell us: * What you're building * Why you need more queries per hour * Your current usage patterns We'll work with you to find the right quota for your needs, or help optimize your query patterns. # query_execution_timeout Source: https://unkey.com/docs/errors/user/unprocessable_entity/query_execution_timeout Your query took longer than the maximum execution time allowed. `err:user:unprocessable_entity:query_execution_timeout` ```json Example theme={"system"} { "meta": { "requestId": "req_4dgzrNP3Je5mU1tD" }, "error": { "detail": "Query exceeded the maximum execution time of 30 seconds", "status": 422, "title": "Unprocessable Entity", "type": "https://unkey.com/docs/errors/user/unprocessable_entity/query_execution_timeout" } } ``` ## What Happened? Your analytics query took too long to execute! We limit queries to 30 seconds to keep the analytics service responsive for everyone. This usually happens when you're querying a large time range or complex data without enough filters. ## How to Fix It ### 1. Query Smaller Time Ranges The most common fix is to reduce the time range: ```sql Too Long theme={"system"} SELECT COUNT(*) FROM key_verifications WHERE time >= now() - INTERVAL 1 YEAR GROUP BY toStartOfDay(time) ``` ```sql Better theme={"system"} SELECT COUNT(*) FROM key_verifications WHERE time >= now() - INTERVAL 7 DAY GROUP BY toStartOfDay(time) ``` ### 2. Add More Filters Filter your data to reduce the amount of work the query needs to do: ```sql Slow theme={"system"} SELECT api_id, COUNT(*) as total FROM key_verifications GROUP BY api_id ``` ```sql Faster theme={"system"} SELECT api_id, COUNT(*) as total FROM key_verifications WHERE time >= now() - INTERVAL 1 DAY AND outcome = 'VALID' GROUP BY api_id ``` ### 3. Use Aggregated Tables For historical data, use pre-aggregated tables instead of raw events: ```sql Slow - Scans millions of raw events theme={"system"} SELECT toStartOfHour(time) as hour, COUNT(*) as total FROM key_verifications_raw_v2 WHERE time >= now() - INTERVAL 30 DAY GROUP BY hour ``` ```sql Fast - Uses pre-aggregated data theme={"system"} SELECT time as hour, SUM(count) as total FROM key_verifications_per_hour_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY hour ``` ### 4. Limit Result Size Add a LIMIT clause to stop processing once you have enough data: ```sql theme={"system"} SELECT api_id, COUNT(*) as total FROM key_verifications WHERE time >= now() - INTERVAL 7 DAY GROUP BY api_id ORDER BY total DESC LIMIT 100 ``` ## Need Longer Execution Time? **Have a legitimate need for longer-running queries?** Contact our support team! [Reach out to support](mailto:support@unkey.dev) and tell us: * What you're trying to analyze * Why the query needs more than 30 seconds * An example of the query you're running We'll review your use case and see if we can accommodate your needs. # query_memory_limit_exceeded Source: https://unkey.com/docs/errors/user/unprocessable_entity/query_memory_limit_exceeded Your query used more memory than allowed. `err:user:unprocessable_entity:query_memory_limit_exceeded` ```json Example theme={"system"} { "meta": { "requestId": "req_4dgzrNP3Je5mU1tD" }, "error": { "detail": "Query exceeded the maximum memory limit of 2GB", "status": 422, "title": "Unprocessable Entity", "type": "https://unkey.com/docs/errors/user/unprocessable_entity/query_memory_limit_exceeded" } } ``` ## What Happened? Your query tried to use more than 2GB of memory! We limit memory usage to keep the analytics service stable and fast for everyone. This typically happens when you're selecting too many rows, using large GROUP BY operations, or performing complex JOINs without enough filtering. ## How to Fix It ### 1. Use Aggregations Instead of Raw Data Instead of fetching all rows, aggregate the data: ```sql Memory Intensive - Fetches all rows theme={"system"} SELECT * FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY ``` ```sql Memory Efficient - Aggregates data theme={"system"} SELECT toStartOfHour(time) as hour, api_id, COUNT(*) as total, AVG(response_time) as avg_response FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY GROUP BY hour, api_id ``` ### 2. Add More Filters Reduce the amount of data the query needs to process: ```sql Too Much Data theme={"system"} SELECT api_id, key_id, outcome, time FROM key_verifications_v1 WHERE time >= now() - INTERVAL 30 DAY ``` ```sql Filtered Query theme={"system"} SELECT api_id, key_id, outcome, time FROM key_verifications_v1 WHERE time >= now() - INTERVAL 1 DAY AND api_id = 'api_123' AND outcome = 'VALID' ``` ### 3. Limit Result Size Add a LIMIT to cap the number of rows: ```sql theme={"system"} SELECT api_id, key_id, outcome, time FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY ORDER BY time DESC LIMIT 10000 ``` ### 4. Avoid Large GROUP BY Cardinality GROUP BY on high-cardinality columns (like `key_id`) uses a lot of memory. Instead, group by lower-cardinality columns: ```sql High Memory - Millions of unique keys theme={"system"} SELECT key_id, COUNT(*) as total FROM key_verifications_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY key_id ``` ```sql Lower Memory - Hundreds of APIs theme={"system"} SELECT api_id, COUNT(*) as total FROM key_verifications_v1 WHERE time >= now() - INTERVAL 30 DAY GROUP BY api_id ``` ## Need More Memory? **Have a legitimate need for higher memory limits?** Contact our support team! [Reach out to support](mailto:support@unkey.dev) and tell us: * What you're trying to analyze * Why the query needs more than 2GB of memory * An example of the query you're running We'll review your use case and see if we can accommodate your needs. # query_rows_limit_exceeded Source: https://unkey.com/docs/errors/user/unprocessable_entity/query_rows_limit_exceeded Your query tried to scan more rows than allowed. `err:user:unprocessable_entity:query_rows_limit_exceeded` ```json Example theme={"system"} { "meta": { "requestId": "req_4dgzrNP3Je5mU1tD" }, "error": { "detail": "Query exceeded the maximum rows to scan limit of 100 million", "status": 422, "title": "Unprocessable Entity", "type": "https://unkey.com/docs/errors/user/unprocessable_entity/query_rows_limit_exceeded" } } ``` ## What Happened? Your query tried to scan more than 100 million rows! We limit the number of rows that can be scanned to keep queries fast and prevent resource exhaustion. This happens when you query large time ranges or don't filter your data enough, causing ClickHouse to scan millions of rows even if the final result is small. ## How to Fix It ### 1. Add Time Range Filters Always filter by time to limit the number of rows scanned: ```sql Scans Too Many Rows theme={"system"} SELECT COUNT(*) FROM key_verifications_v1 WHERE outcome = 'VALID' ``` ```sql Limited Scan theme={"system"} SELECT COUNT(*) FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY AND outcome = 'VALID' ``` ### 2. Use More Selective Filters Add filters that reduce the data before aggregation: ```sql Scans Everything theme={"system"} SELECT api_id, COUNT(*) as total FROM key_verifications_v1 WHERE time >= now() - INTERVAL 90 DAY GROUP BY api_id ``` ```sql Scans Less theme={"system"} SELECT api_id, COUNT(*) as total FROM key_verifications_v1 WHERE time >= now() - INTERVAL 7 DAY AND api_id IN ('api_123', 'api_456') GROUP BY api_id ``` ### 3. Use Pre-Aggregated Tables For historical queries, use aggregated tables that have fewer rows: ```sql Raw Table - 100M+ rows theme={"system"} SELECT toStartOfDay(time) as day, COUNT(*) as total FROM key_verifications_v1 WHERE time >= now() - INTERVAL 90 DAY GROUP BY day ``` ```sql Aggregated Table - 2K rows theme={"system"} SELECT time as day, SUM(count) as total FROM key_verifications_v1_per_day_v1 WHERE time >= now() - INTERVAL 90 DAY GROUP BY day ``` ### 4. Query in Smaller Batches Instead of one large query, break it into smaller time windows: ```javascript theme={"system"} // Instead of querying 90 days at once const results = []; for (let i = 0; i < 90; i += 7) { const start = `now() - INTERVAL ${i + 7} DAY`; const end = `now() - INTERVAL ${i} DAY`; const result = await query(` SELECT COUNT(*) as total FROM key_verifications_v1 WHERE time >= ${start} AND time < ${end} `); results.push(result); } ``` ## Need Higher Row Limits? **Have a legitimate need to scan more rows?** Contact our support team! [Reach out to support](mailto:support@unkey.dev) and tell us: * What you're trying to analyze * Why you need to scan more than 100 million rows * An example of the query you're running We'll review your use case and help optimize your query or adjust limits if needed. # 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 theme={"system"} { "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 theme={"system"} "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 theme={"system"} 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 theme={"system"} { "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 theme={"system"} try do :ok = UnkeyElixirSdk.delete_key("xyz_AS5HDkXXPot2MMoPHD8jnL") catch err -> Logger.error(err) end ``` ```elixir theme={"system"} :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 theme={"system"} 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 theme={"system"} :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 theme={"system"} try do opts = UnkeyElixirSdk.update_remaining(%{ "keyId": "key_123", "op": "increment", "value": 1 }) catch err -> Logger.error(err) end ``` ```elixir theme={"system"} %{"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 theme={"system"} { "billingTier": "PRO", "trialEnds": "2023-06-16T17:16:37.161Z" } ``` ```elixir theme={"system"} try do is_verified = UnkeyElixirSdk.verify_key("xyz_AS5HDkXXPot2MMoPHD8jnL", %{ "apiId" => "api_7oKUUscTZy22jmVf9THxDA" }) catch err -> Logger.error(err) end ``` ```ts theme={"system"} { 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} {: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 theme={"system"} go get github.com/unkeyed/sdks/api/go/v2@latest ``` ## SDK Example Usage ### Example ```go theme={"system"} 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 theme={"system"} bun install @unkey/nuxt ``` ```bash theme={"system"} pnpm add @unkey/nuxt ``` ```bash theme={"system"} yarn add @unkey/nuxt ``` ```bash theme={"system"} npm install @unkey/nuxt ``` ## Configuration `@unkey/nuxt` just requires your root key. Create an `.env` file in your project and add the following: ```env theme={"system"} 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 theme={"system"} export default defineEventHandler(async (event) => { if (!event.context.unkey.valid) { throw createError({ statusCode: 403, message: "Invalid API key" }) } // return authorised information return { // ... }; }); ``` ```html theme={"system"} ``` ## 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/api). For example: ```ts theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} #!/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 theme={"system"} # 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 theme={"system"} # 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 theme={"system"} $ cargo add unkey ``` ### Manually Add the following to your `Cargo.toml` dependencies array: ```toml theme={"system"} unkey = "0.4" ``` ## Getting Started ### Examples #### Verifying a key ```rust theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} public class ListKeysRequest { private String apiId; private Integer limit; private Integer offset; private String ownerId; } ``` #### Response ```java theme={"system"} public class ListKeysResponse { private List keys; private Integer total; } ``` ```java theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} public class Meta { private Map meta; } ``` ```java theme={"system"} public class KeyRateLimit { private String type; private Integer limit; private Integer refillRate; private Integer refillInterval; } ``` #### Response ```java theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} public class KeyVerifyRateLimit { private Integer limit; private Integer remaining; private Long reset; } ``` ```java theme={"system"} 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 theme={"system"} 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 theme={"system"} npm add @unkey/api ``` ```bash pnpm theme={"system"} pnpm add @unkey/api ``` ```bash bun theme={"system"} bun add @unkey/api ``` ```bash yarn theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} npm install @unkey/cache ``` ```bash theme={"system"} pnpm add @unkey/cache ``` ```bash theme={"system"} yarn add @unkey/cache ``` ```bash theme={"system"} bun install @unkey/cache ``` ```ts Hello World theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} new Namespace(ctx, opts) ``` The type of data stored in this namespace, for example: ```ts theme={"system"} type User = { email: string; }; ``` An execution context, such as a request or a worker instance. [Read more](/libraries/ts/cache/overview#context) ```ts theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} sequenceDiagram autonumber App->>Cache: swr(key, loadFromOrigin) Cache->>+Tier1: get key Tier1->>-Cache: fresh value Cache->>App: value ``` ```mermaid theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} export interface Context { waitUntil: (p: Promise) => void; } ``` For stateful applications, you can use the `DefaultStatefulContext`: ```ts theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} openssl rand -base64 32 ``` ```ts Example theme={"system"} 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 theme={"system"} npm install @unkey/hono ``` ```bash pnpm theme={"system"} pnpm add @unkey/hono ``` ```bash yarn theme={"system"} yarn add @unkey/hono ``` ```bash bun theme={"system"} 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 [/settings/root-keys](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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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/create-api-key) for the full `response` object. ```ts theme={"system"} 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 theme={"system"} (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 theme={"system"} npm install @unkey/nextjs ``` ```bash pnpm theme={"system"} pnpm add @unkey/nextjs ``` ```bash yarn theme={"system"} yarn add @unkey/nextjs ``` ```bash bun theme={"system"} bun add @unkey/nextjs ``` Protecting API routes is as simple as wrapping them with the `withUnkey` handler: ```ts theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} await unkey.deleteOverride({ identifier: "user_123", namespaceName: "email.outbound", }) ``` ```ts theme={"system"} 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 theme={"system"} const override = await unkey.getOverride({ identifier:"user.example", namespaceName: "email.outbound" }); ``` ```ts theme={"system"} const override = await unkey.getOverride({ identifier:"user.example", namespaceId: "rlns_1234", }); ``` ```ts theme={"system"} { 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 theme={"system"} const overrides = await unkey.listOverrides({ namespaceName: "email.outbound" }); ``` ```ts theme={"system"} const overrides = await unkey.listOverrides({ nameSpaceId:"rlns_12345", }); ``` ```ts theme={"system"} { 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 theme={"system"} import { Override } from "@unkey/ratelimit" const unkey = new Override({ rootKey: process.env.UNKEY_ROOT_KEY, }) ``` ## Use it ```ts theme={"system"} 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 theme={"system"} const override = await unkey.setOverride({ identifier: "user_123", limit: 10, duration: 60000, namespaceName: "email.outbound", async: true }) ``` ```ts theme={"system"} const override = await unkey.setOverride({ identifier: "user_123", limit: 5, duration: 50000, namespaceId: "rlns_1234", async: false }) ``` ```ts theme={"system"} { 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 theme={"system"} npm install @unkey/ratelimit ``` ```bash theme={"system"} pnpm add @unkey/ratelimit ``` ```bash theme={"system"} yarn add @unkey/ratelimit ``` ```bash theme={"system"} bun install @unkey/ratelimit ``` ## Configure your ratelimiter ```ts theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} onError: ()=> ({ success: true, limit: 0, remaining: 0, reset: 0}) ``` Example rejecting the request: ```ts theme={"system"} 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 theme={"system"} 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/v1/migration/keys) endpoint. ## Nodejs Example ```js Hash theme={"system"} 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 theme={"system"} 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 theme={"system"} mkdir unkey-with-bun cd unkey-with-bun bun init -y ``` Now install the `@unkey/api` package ```bash theme={"system"} bun install @unkey/api@0.35 ``` Open up the file called `index.ts` and add the following code ```ts index.ts theme={"system"} 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 theme={"system"} bun run index.ts ``` Go to `https://app.unkey.com` and create a new key. Then verify it with our new server: ```bash theme={"system"} 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 theme={"system"} 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 theme={"system"} "scripts": { "start": "ts-node ./index.ts", "build": "tsc", "serve": "node dist/index.js" }, ``` Now install the `@unkey/api` package ```bash theme={"system"} npm install @unkey/api@0.35 ``` Create a file called `server.ts` and add the following code ```ts server.ts theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} npm create hono@latest ``` ```bash theme={"system"} pnpm create hono@latest ``` ```bash theme={"system"} yarn create hono@latest ``` ```bash theme={"system"} bun create hono@latest ``` Now install the `@unkey/hono` package ```bash theme={"system"} npm install @unkey/hono ``` ```bash theme={"system"} pnpm add @unkey/hono ``` ```bash theme={"system"} yarn add @unkey/hono ``` ```bash theme={"system"} bun install @unkey/hono ``` Create a new route and add the following code ```ts /src/index.ts theme={"system"} 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 theme={"system"} bun run dev ``` ```bash theme={"system"} pnpm run dev ``` ```bash theme={"system"} yarn run dev ``` ```bash theme={"system"} 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 theme={"system"} 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 theme={"system"} npx create-next-app@latest ``` ```bash theme={"system"} pnpm create next-app@latest ``` ```bash theme={"system"} yarn create-next-app@latest ``` ```bash theme={"system"} bunx create-next-app ``` Now install the `@unkey/nextjs` package ```bash theme={"system"} npm install @unkey/nextjs ``` ```bash theme={"system"} pnpm add @unkey/nextjs ``` ```bash theme={"system"} yarn add @unkey/nextjs ``` ```bash theme={"system"} bun install @unkey/nextjs ``` Create a new route and add the following code ```ts /app/protected/route.ts theme={"system"} 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 theme={"system"} bun run dev ``` ```bash theme={"system"} pnpm run dev ``` ```bash theme={"system"} yarn run dev ``` ```bash theme={"system"} 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 theme={"system"} 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 theme={"system"} const apiId = "api_XXX"; const rootKey = "unkey_XXX"; ``` The root key requires the following permissions: ```ts theme={"system"} "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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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 theme={"system"} 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/v2/auth), 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/v2/overview), 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 theme={"system"} mkdir unkey-with-bun cd unkey-with-bun bun init -y ``` Now install the `@unkey/ratelimit` package ```bash theme={"system"} bun install @unkey/ratelimit ``` Add your root key to your `.env` file ```bash theme={"system"} UNKEY_ROOT_KEY="YOUR_KEY" ``` Open up the file called `index.ts` and add the following code ```ts index.ts theme={"system"} 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 theme={"system"} bun run index.ts ``` ```bash theme={"system"} 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 theme={"system"} 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 theme={"system"} "scripts": { "start": "ts-node ./index.ts", "build": "tsc", "serve": "node dist/index.js" }, ``` Now install the `@unkey/ratelimit` package ```bash theme={"system"} npm install @unkey/ratelimit ``` Add your root key to your `.env` file ```bash theme={"system"} UNKEY_ROOT_KEY="YOUR_KEY" ``` Create a file called `server.ts` and add the following code ```ts server.ts theme={"system"} 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 theme={"system"} npm run start ``` ```bash theme={"system"} 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 theme={"system"} npm create hono@latest ``` ```bash theme={"system"} pnpm create hono@latest ``` ```bash theme={"system"} yarn create hono@latest ``` ```bash theme={"system"} bun create hono@latest ``` Now install the `@unkey/ratelimit` package ```bash theme={"system"} npm install @unkey/ratelimit ``` ```bash theme={"system"} pnpm add @unkey/ratelimit ``` ```bash theme={"system"} yarn add @unkey/ratelimit ``` ```bash theme={"system"} bun install @unkey/ratelimit ``` Add your root key to your `.env` file ```bash theme={"system"} UNKEY_ROOT_KEY="YOUR_KEY" ``` Create a new route and add the following code ```ts /src/index.ts theme={"system"} 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 theme={"system"} npm run dev ``` ```bash theme={"system"} pnpm run dev ``` ```bash theme={"system"} yarn run dev ``` ```bash theme={"system"} bun run dev ``` ```bash theme={"system"} 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 theme={"system"} npx create-next-app@latest ``` ```bash theme={"system"} pnpm create next-app@latest ``` ```bash theme={"system"} yarn create-next-app@latest ``` ```bash theme={"system"} bunx create-next-app ``` Now install the `@unkey/ratelimit` package ```bash theme={"system"} npm install @unkey/ratelimit ``` ```bash theme={"system"} pnpm add @unkey/ratelimit ``` ```bash theme={"system"} yarn add @unkey/ratelimit ``` ```bash theme={"system"} bun install @unkey/ratelimit ``` Add your root key to your `.env` file ```bash theme={"system"} UNKEY_ROOT_KEY="YOUR_KEY" ``` Create a new route and add the following code ```ts /app/protected/route.ts theme={"system"} 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 theme={"system"} npm run dev ``` ```bash theme={"system"} pnpm run dev ``` ```bash theme={"system"} yarn run dev ``` ```bash theme={"system"} 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 theme={"system"} 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 theme={"system"} 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/v2/overview) 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 theme={"system"} 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/v2/overview) ## List Overrides You can list all of the configured overirdes for a namespace to build your own dashboards. ```ts theme={"system"} 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/v2/overview) ## Delete Override Once they downgrade their plan, we can revoke any overrides: ```ts theme={"system"} 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/v2/overview) # 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/v2/overview) 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 theme={"system"} { "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 theme={"system"} 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/v2/keys/get-api-key) and [listKeys](/api-reference/v2/apis/list-api-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 theme={"system"} 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 theme={"system"} { // ... "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.