> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hexclave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API Keys

> Create and manage API keys for users and teams

The API Keys app enables your users to generate and manage API keys for programmatic access to your backend services. API keys provide a secure way to authenticate requests, allowing developers to associate API calls with specific **users** or **teams**. Hexclave provides prebuilt UI components so users and teams can manage their own keys.

## Concepts

### The authentication flow

A user or client sends an API request with an API key to your application server. Your server validates the API key with Hexclave, which returns an authenticated `User` (or `Team`) object. Your server then processes the request and returns the response.

### Two kinds of API keys

Hexclave supports two kinds of API keys:

* **User API keys** - associated with an individual user; calls authenticated with this key act on behalf of that user.
* **Team API keys** - associated with a team; calls authenticated with this key act on behalf of that team. Only users with the `$manage_api_keys` permission for the team can create or revoke them.

> [!NOTE]
>
> **Don't confuse API Keys with Project Keys.** The **API Keys app** documented here lets your end users issue keys for *their* accounts and teams. If you want to create or rotate the publishable / secret keys that *your* Hexclave project uses to call the Hexclave API, that lives in **Project Settings → Project Keys** instead.

#### User API keys

User API keys are associated with individual users and allow them to authenticate with your API.

> [tab] Next.js Client
>
> ```typescript title="app/components/create-api-key.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/next";
>
> export default function CreateApiKey() {
>   const user = useUser({ or: 'redirect' });
>
>   const handleCreateKey = async () => {
>     const apiKey = await user.createApiKey({
>       description: "My client application",
>       expiresAt: new Date(Date.now() + (90 * 24 * 60 * 60 * 1000)), // 90 days
>     });
>
>     console.log("API Key created:", apiKey.value);
>   };
>
>   return <button onClick={handleCreateKey}>Create API Key</button>;
> }
> ```

> [tab] Next.js Server
>
> ```typescript title="app/components/create-api-key.tsx" theme={null}
> import { hexclaveServerApp } from "@/hexclave/server";
>
> export default async function CreateApiKey() {
>   const user = await hexclaveServerApp.getUser({ or: 'throw' });
>
>   const apiKey = await user.createApiKey({
>     description: "Admin-provisioned API key",
>     expiresAt: new Date(Date.now() + (30 * 24 * 60 * 60 * 1000)), // 30 days
>   });
>
>   return <div>API Key: {apiKey.value}</div>;
> }
> ```

> [tab] React
>
> ```typescript title="components/CreateApiKey.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/react";
>
> export default function CreateApiKey() {
>   const user = useUser({ or: 'redirect' });
>
>   const handleCreateKey = async () => {
>     const apiKey = await user.createApiKey({
>       description: "My client application",
>       expiresAt: new Date(Date.now() + (90 * 24 * 60 * 60 * 1000)), // 90 days
>     });
>
>     console.log("API Key created:", apiKey.value);
>   };
>
>   return <button onClick={handleCreateKey}>Create API Key</button>;
> }
> ```

> [tab] Django
>
> ```python title="views.py" theme={null}
> import requests
> from django.http import JsonResponse
>
> def create_user_api_key(request):
>     # Get the current user's access token from session/cookie
>     access_token = request.COOKIES.get('hexclave-access-token')
>
>     # Create API key via client API
>     response = requests.post(
>         'https://api.hexclave.com/api/v1/user-api-keys',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         },
>         json={
>             'user_id': 'me',
>             'description': 'My client application',
>             'expires_at_millis': int((time.time() + 90 * 24 * 60 * 60) * 1000),
>         }
>     )
>
>     if response.status_code != 200:
>         raise Exception(f"Failed to create API key: {response.text}")
>
>     return JsonResponse(response.json())
> ```

> [tab] FastAPI
>
> ```python title="main.py" theme={null}
> import requests
> import time
> from fastapi import Cookie, HTTPException
>
> @app.post("/api/create-user-api-key")
> async def create_user_api_key(hexclave_access_token: str = Cookie(None, alias="hexclave-access-token")):
>     if not hexclave_access_token:
>         raise HTTPException(status_code=401, detail="Not authenticated")
>
>     # Create API key via client API
>     response = requests.post(
>         'https://api.hexclave.com/api/v1/user-api-keys',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': hexclave_access_token,
>         },
>         json={
>             'user_id': 'me',
>             'description': 'My client application',
>             'expires_at_millis': int((time.time() + 90 * 24 * 60 * 60) * 1000),
>         }
>     )
>
>     if response.status_code != 200:
>         raise HTTPException(status_code=response.status_code, detail=response.text)
>
>     return response.json()
> ```

> [tab] Flask
>
> ```python title="app.py" theme={null}
> import requests
> import time
> from flask import request, jsonify
>
> @app.route('/api/create-user-api-key', methods=['POST'])
> def create_user_api_key():
>     access_token = request.cookies.get('hexclave-access-token')
>     if not access_token:
>         return jsonify({'error': 'Not authenticated'}), 401
>
>     # Create API key via client API
>     response = requests.post(
>         'https://api.hexclave.com/api/v1/user-api-keys',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         },
>         json={
>             'user_id': 'me',
>             'description': 'My client application',
>             'expires_at_millis': int((time.time() + 90 * 24 * 60 * 60) * 1000),
>         }
>     )
>
>     if response.status_code != 200:
>         return jsonify({'error': response.text}), response.status_code
>
>     return jsonify(response.json())
> ```

#### Team API keys

Team API keys are associated with teams and can be used to provide access to team resources over your API.

> [tab] Next.js Client
>
> ```typescript title="app/components/create-team-api-key.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/next";
>
> export default function CreateTeamApiKey({ teamId }: { teamId: string }) {
>   const user = useUser({ or: 'redirect' });
>   const team = user.useTeam(teamId);
>
>   const handleCreateKey = async () => {
>     if (!team) return;
>
>     const teamApiKey = await team.createApiKey({
>       description: "Team integration service",
>       expiresAt: new Date(Date.now() + (60 * 24 * 60 * 60 * 1000)), // 60 days
>     });
>
>     console.log("Team API Key created:", teamApiKey.value);
>   };
>
>   return <button onClick={handleCreateKey}>Create Team API Key</button>;
> }
> ```

> [tab] Next.js Server
>
> ```typescript title="app/components/create-team-api-key.tsx" theme={null}
> import { hexclaveServerApp } from "@/hexclave/server";
>
> export default async function CreateTeamApiKey({ teamId }: { teamId: string }) {
>   const team = await hexclaveServerApp.getTeam(teamId);
>
>   if (!team) {
>     return <div>Team not found</div>;
>   }
>
>   const teamApiKey = await team.createApiKey({
>     description: "Admin-provisioned team API key",
>     expiresAt: new Date(Date.now() + (30 * 24 * 60 * 60 * 1000)), // 30 days
>   });
>
>   return <div>Team API Key: {teamApiKey.value}</div>;
> }
> ```

> [tab] React
>
> ```typescript title="components/CreateTeamApiKey.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/react";
>
> export default function CreateTeamApiKey({ teamId }: { teamId: string }) {
>   const user = useUser({ or: 'redirect' });
>   const team = user.useTeam(teamId);
>
>   const handleCreateKey = async () => {
>     if (!team) return;
>
>     const teamApiKey = await team.createApiKey({
>       description: "Team integration service",
>       expiresAt: new Date(Date.now() + (60 * 24 * 60 * 60 * 1000)), // 60 days
>     });
>
>     console.log("Team API Key created:", teamApiKey.value);
>   };
>
>   return <button onClick={handleCreateKey}>Create Team API Key</button>;
> }
> ```

> [tab] Django
>
> ```python title="views.py" theme={null}
> import requests
> import time
> from django.http import JsonResponse
>
> def create_team_api_key(request, team_id):
>     # Get the current user's access token from session/cookie
>     access_token = request.COOKIES.get('hexclave-access-token')
>
>     # Create team API key via client API
>     response = requests.post(
>         'https://api.hexclave.com/api/v1/team-api-keys',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         },
>         json={
>             'team_id': team_id,
>             'description': 'Team integration service',
>             'expires_at_millis': int((time.time() + 60 * 24 * 60 * 60) * 1000),
>         }
>     )
>
>     if response.status_code != 200:
>         raise Exception(f"Failed to create team API key: {response.text}")
>
>     return JsonResponse(response.json())
> ```

> [tab] FastAPI
>
> ```python title="main.py" theme={null}
> import requests
> import time
> from fastapi import Cookie, HTTPException
>
> @app.post("/api/teams/{team_id}/api-keys")
> async def create_team_api_key(team_id: str, hexclave_access_token: str = Cookie(None, alias="hexclave-access-token")):
>     if not hexclave_access_token:
>         raise HTTPException(status_code=401, detail="Not authenticated")
>
>     # Create team API key via client API
>     response = requests.post(
>         'https://api.hexclave.com/api/v1/team-api-keys',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': hexclave_access_token,
>         },
>         json={
>             'team_id': team_id,
>             'description': 'Team integration service',
>             'expires_at_millis': int((time.time() + 60 * 24 * 60 * 60) * 1000),
>         }
>     )
>
>     if response.status_code != 200:
>         raise HTTPException(status_code=response.status_code, detail=response.text)
>
>     return response.json()
> ```

> [tab] Flask
>
> ```python title="app.py" theme={null}
> import requests
> import time
> from flask import request, jsonify
>
> @app.route('/api/teams/<team_id>/api-keys', methods=['POST'])
> def create_team_api_key(team_id):
>     access_token = request.cookies.get('hexclave-access-token')
>     if not access_token:
>         return jsonify({'error': 'Not authenticated'}), 401
>
>     # Create team API key via client API
>     response = requests.post(
>         'https://api.hexclave.com/api/v1/team-api-keys',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         },
>         json={
>             'team_id': team_id,
>             'description': 'Team integration service',
>             'expires_at_millis': int((time.time() + 60 * 24 * 60 * 60) * 1000),
>         }
>     )
>
>     if response.status_code != 200:
>         return jsonify({'error': response.text}), response.status_code
>
>     return jsonify(response.json())
> ```

## Enabling the API Keys App

To use API keys in your application, you must enable the **API Keys** app in your Hexclave dashboard:

1. Open your Hexclave dashboard
2. Go to **Apps**
3. Find and open **API Keys**
4. Click **Enable**

### Dashboard settings

Once enabled, the API Keys app exposes exactly two toggles under **API Key Settings**:

| Setting | Config field | Description |
| - | - | - |
| **User API Keys** | `apiKeys.enabled.user` | Allow users to create API keys for their accounts. Enables the `user-api-keys` backend routes. |
| **Team API Keys** | `apiKeys.enabled.team` | Allow users to create API keys for their teams. Enables the `team-api-keys` backend routes. |

Both are **disabled by default**. Changes require clicking **Save** before they take effect.

Toggling **User API Keys** controls whether the `<AccountSettings>` component shows its API Keys tab. Toggling **Team API Keys** controls whether the team settings page shows its API Keys section to users with the `$manage_api_keys` permission.

### Team permission requirement

Creating, listing, and revoking **team** API keys requires the [`$manage_api_keys`](https://docs.hexclave.com/guides/apps/rbac/overview) permission on the team. Make sure your team roles grant this permission to the right members (e.g. admins).

## Prebuilt UI Components

Hexclave provides prebuilt UI components that let your users manage their own API keys without any additional code.

### User API Keys UI

For frameworks that support React components, the `<AccountSettings>` component includes an API Keys tab where users can:

* View all their active API keys
* Create new API keys with a description and an expiration date
* Revoke existing API keys
* See when each key was created and when it expires

The tab is only shown when `apiKeys.enabled.user` is on for your project.

> [tab] Next.js
>
> ```typescript title="app/account/page.tsx" theme={null}
> import { AccountSettings } from '@hexclave/next';
>
> export default function MyAccountPage() {
>   return <AccountSettings fullPage />;
> }
> ```

> [tab] React
>
> ```typescript title="src/account-page.tsx" theme={null}
> import { AccountSettings } from '@hexclave/react';
>
> export default function MyAccountPage() {
>   return <AccountSettings fullPage />;
> }
> ```

### Team API Keys UI

The team settings page automatically includes an **API Keys** section when **all** of the following are true:

* The API Keys app is enabled
* `apiKeys.enabled.team` is on for your project
* The current user has the `$manage_api_keys` permission on the team

Users with the right permission can create, list, and revoke team API keys directly from the team settings interface - no extra code required.

## The `ApiKey` object

Both `user.listApiKeys()` and `team.listApiKeys()` return arrays of `ApiKey` objects. The same shape comes back from `user.createApiKey(...)` / `team.createApiKey(...)`, except the `value` is the full plaintext key only on the first view.

| Field | Type | Description |
| - | - | - |
| `id` | `string` | Stable identifier for the key |
| `description` | `string` | Human-readable description set on creation |
| `createdAt` | `Date` | When the key was created |
| `expiresAt` | `Date \| undefined` | Optional expiration timestamp |
| `manuallyRevokedAt` | `Date \| null \| undefined` | Set when the key was explicitly revoked |
| `value` | `string` (first view) / `{ lastFour: string }` | Full key on first view only, then just the last 4 characters |
| `type` | `"user" \| "team"` | Which flavor of key this is |
| `userId` / `teamId` | `string` | The owning user (for `type: "user"`) or team (for `type: "team"`) |
| `update(options)` | `(options) => Promise<void>` | Update `description`, `expiresAt`, or `revoked` |
| `revoke()` | `() => Promise<void>` | Convenience for `update({ revoked: true })` |
| `isValid()` | `() => boolean` | `true` if the key is not expired and not manually revoked |
| `whyInvalid()` | `() => "manually-revoked" \| "expired" \| null` | Reason the key is invalid, or `null` if it's still valid |

> [!WARNING]
>
> The full plaintext value of an API key is **only returned once** - at creation time. After that, the SDK only ever exposes `value.lastFour`. Display, copy, or store the value immediately on creation; it cannot be retrieved later.

### `isPublic` keys

When creating a key, pass `isPublic: true` to exempt it from Hexclave's secret scanner. The secret scanner automatically revokes API keys it detects in public places (e.g. exposed in a GitHub repo). Use `isPublic` only for keys that are intentionally exposed to clients (e.g. anonymous-style access tokens).

## Working with API Keys

### Creating a user API key

> [tab] Next.js Client
>
> ```typescript title="app/components/create-api-key.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/next";
>
> export default function CreateApiKey() {
>   const user = useUser({ or: 'redirect' });
>
>   const handleCreateKey = async () => {
>     const apiKey = await user.createApiKey({
>       description: "My client application",
>       expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), // 90 days
>     });
>
>     console.log("API Key created:", apiKey.value);
>   };
>
>   return <button onClick={handleCreateKey}>Create API Key</button>;
> }
> ```

> [tab] Next.js Server
>
> ```typescript title="app/components/create-api-key.tsx" theme={null}
> import { hexclaveServerApp } from "@/hexclave/server";
>
> export default async function CreateApiKey() {
>   const user = await hexclaveServerApp.getUser({ or: 'throw' });
>
>   const apiKey = await user.createApiKey({
>     description: "Admin-provisioned API key",
>     expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days
>   });
>
>   return <div>API Key: {apiKey.value}</div>;
> }
> ```

> [tab] React
>
> ```typescript title="components/CreateApiKey.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/react";
>
> export default function CreateApiKey() {
>   const user = useUser({ or: 'redirect' });
>
>   const handleCreateKey = async () => {
>     const apiKey = await user.createApiKey({
>       description: "My client application",
>       expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),
>     });
>
>     console.log("API Key created:", apiKey.value);
>   };
>
>   return <button onClick={handleCreateKey}>Create API Key</button>;
> }
> ```

> [tab] Django
>
> ```python title="views.py" theme={null}
> import requests
> import time
> from django.http import JsonResponse
>
> def create_user_api_key(request):
>     # Get the current user's access token from session/cookie
>     access_token = request.COOKIES.get('hexclave-access-token')
>
>     # Create API key via client API
>     response = requests.post(
>         'https://api.hexclave.com/api/v1/user-api-keys',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         },
>         json={
>             'user_id': 'me',
>             'description': 'My client application',
>             'expires_at_millis': int((time.time() + 90 * 24 * 60 * 60) * 1000),
>         }
>     )
>
>     if response.status_code != 200:
>         raise Exception(f"Failed to create API key: {response.text}")
>
>     return JsonResponse(response.json())
> ```

> [tab] FastAPI
>
> ```python title="main.py" theme={null}
> import requests
> import time
> from fastapi import Cookie, HTTPException
>
> @app.post("/api/create-user-api-key")
> async def create_user_api_key(hexclave_access_token: str = Cookie(None, alias="hexclave-access-token")):
>     if not hexclave_access_token:
>         raise HTTPException(status_code=401, detail="Not authenticated")
>
>     # Create API key via client API
>     response = requests.post(
>         'https://api.hexclave.com/api/v1/user-api-keys',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': hexclave_access_token,
>         },
>         json={
>             'user_id': 'me',
>             'description': 'My client application',
>             'expires_at_millis': int((time.time() + 90 * 24 * 60 * 60) * 1000),
>         }
>     )
>
>     if response.status_code != 200:
>         raise HTTPException(status_code=response.status_code, detail=response.text)
>
>     return response.json()
> ```

> [tab] Flask
>
> ```python title="app.py" theme={null}
> import requests
> import time
> from flask import request, jsonify
>
> @app.route('/api/create-user-api-key', methods=['POST'])
> def create_user_api_key():
>     access_token = request.cookies.get('hexclave-access-token')
>     if not access_token:
>         return jsonify({'error': 'Not authenticated'}), 401
>
>     # Create API key via client API
>     response = requests.post(
>         'https://api.hexclave.com/api/v1/user-api-keys',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         },
>         json={
>             'user_id': 'me',
>             'description': 'My client application',
>             'expires_at_millis': int((time.time() + 90 * 24 * 60 * 60) * 1000),
>         }
>     )
>
>     if response.status_code != 200:
>         return jsonify({'error': response.text}), response.status_code
>
>     return jsonify(response.json())
> ```

### Creating a team API key

Requires the `$manage_api_keys` team permission.

> [tab] Next.js Client
>
> ```typescript title="app/components/create-team-api-key.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/next";
>
> export default function CreateTeamApiKey({ teamId }: { teamId: string }) {
>   const user = useUser({ or: 'redirect' });
>   const team = user.useTeam(teamId);
>
>   const handleCreateKey = async () => {
>     if (!team) return;
>
>     const teamApiKey = await team.createApiKey({
>       description: "Team integration service",
>       expiresAt: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000), // 60 days
>     });
>
>     console.log("Team API Key created:", teamApiKey.value);
>   };
>
>   return <button onClick={handleCreateKey}>Create Team API Key</button>;
> }
> ```

> [tab] Next.js Server
>
> ```typescript title="app/components/create-team-api-key.tsx" theme={null}
> import { hexclaveServerApp } from "@/hexclave/server";
>
> export default async function CreateTeamApiKey({ teamId }: { teamId: string }) {
>   const team = await hexclaveServerApp.getTeam(teamId);
>
>   if (!team) {
>     return <div>Team not found</div>;
>   }
>
>
>   const teamApiKey = await team.createApiKey({
>     description: "Admin-provisioned team API key",
>     expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days
>   });
>
>   return <div>Team API Key: {teamApiKey.value}</div>;
> }
> ```

> [tab] React
>
> ```typescript title="components/CreateTeamApiKey.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/react";
>
> export default function CreateTeamApiKey({ teamId }: { teamId: string }) {
>   const user = useUser({ or: 'redirect' });
>   const team = user.useTeam(teamId);
>
>   const handleCreateKey = async () => {
>     if (!team) return;
>
>     const teamApiKey = await team.createApiKey({
>       description: "Team integration service",
>       expiresAt: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000), // 60 days
>     });
>
>     console.log("Team API Key created:", teamApiKey.value);
>   };
>
>   return <button onClick={handleCreateKey}>Create Team API Key</button>;
> }
> ```

> [tab] Django
>
> ```python title="views.py" theme={null}
> import requests
> import time
> from django.http import JsonResponse
>
> def create_team_api_key(request, team_id):
>     # Get the current user's access token from session/cookie
>     access_token = request.COOKIES.get('hexclave-access-token')
>
>     # Create team API key via client API
>     response = requests.post(
>         'https://api.hexclave.com/api/v1/team-api-keys',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         },
>         json={
>             'team_id': team_id,
>             'description': 'Team integration service',
>             'expires_at_millis': int((time.time() + 60 * 24 * 60 * 60) * 1000),
>         }
>     )
>
>     if response.status_code != 200:
>         raise Exception(f"Failed to create team API key: {response.text}")
>
>     return JsonResponse(response.json())
> ```

> [tab] FastAPI
>
> ```python title="main.py" theme={null}
> import requests
> import time
> from fastapi import Cookie, HTTPException
>
> @app.post("/api/teams/{team_id}/api-keys")
> async def create_team_api_key(team_id: str, hexclave_access_token: str = Cookie(None, alias="hexclave-access-token")):
>     if not hexclave_access_token:
>         raise HTTPException(status_code=401, detail="Not authenticated")
>
>     # Create team API key via client API
>     response = requests.post(
>         'https://api.hexclave.com/api/v1/team-api-keys',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': hexclave_access_token,
>         },
>         json={
>             'team_id': team_id,
>             'description': 'Team integration service',
>             'expires_at_millis': int((time.time() + 60 * 24 * 60 * 60) * 1000),
>         }
>     )
>
>     if response.status_code != 200:
>         raise HTTPException(status_code=response.status_code, detail=response.text)
>
>     return response.json()
> ```

> [tab] Flask
>
> ```python title="app.py" theme={null}
> import requests
> import time
> from flask import request, jsonify
>
> @app.route('/api/teams/<team_id>/api-keys', methods=['POST'])
> def create_team_api_key(team_id):
>     access_token = request.cookies.get('hexclave-access-token')
>     if not access_token:
>         return jsonify({'error': 'Not authenticated'}), 401
>
>     # Create team API key via client API
>     response = requests.post(
>         'https://api.hexclave.com/api/v1/team-api-keys',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         },
>         json={
>             'team_id': team_id,
>             'description': 'Team integration service',
>             'expires_at_millis': int((time.time() + 60 * 24 * 60 * 60) * 1000),
>         }
>     )
>
>     if response.status_code != 200:
>         return jsonify({'error': response.text}), response.status_code
>
>     return jsonify(response.json())
> ```

### Listing API keys

> [tab] Next.js Client
>
> ```typescript title="app/components/api-keys-list.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/next";
>
> export default function ApiKeysList() {
>   const user = useUser({ or: 'redirect' });
>   const apiKeys = user.useApiKeys();
>
>   return (
>     <div>
>       <h2>Your API Keys</h2>
>       {apiKeys.map(key => (
>         <div key={key.id}>
>           <p>{key.description}</p>
>           <p>Last 4 digits: {key.value.lastFour}</p>
>           <p>Created: {key.createdAt.toLocaleDateString()}</p>
>         </div>
>       ))}
>     </div>
>   );
> }
> ```

> [tab] Next.js Server
>
> ```typescript title="app/components/api-keys-list.tsx" theme={null}
> import { hexclaveServerApp } from "@/hexclave/server";
>
> export default async function ApiKeysList() {
>   const user = await hexclaveServerApp.getUser({ or: 'throw' });
>   const apiKeys = await user.listApiKeys();
>
>   return (
>     <div>
>       <h2>Your API Keys</h2>
>       {apiKeys.map(key => (
>         <div key={key.id}>
>           <p>{key.description}</p>
>           <p>Last 4 digits: {key.value.lastFour}</p>
>           <p>Created: {key.createdAt.toLocaleDateString()}</p>
>         </div>
>       ))}
>     </div>
>   );
> }
> ```

> [tab] React
>
> ```typescript title="components/ApiKeysList.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/react";
>
> export default function ApiKeysList() {
>   const user = useUser({ or: 'redirect' });
>   const apiKeys = user.useApiKeys();
>
>   return (
>     <div>
>       <h2>Your API Keys</h2>
>       {apiKeys.map(key => (
>         <div key={key.id}>
>           <p>{key.description}</p>
>           <p>Last 4 digits: {key.value.lastFour}</p>
>           <p>Created: {key.createdAt.toLocaleDateString()}</p>
>         </div>
>       ))}
>     </div>
>   );
> }
> ```

> [tab] Django
>
> ```python title="views.py" theme={null}
> import requests
> from django.http import JsonResponse
>
> def list_user_api_keys(request):
>     # Get the current user's access token from session/cookie
>     access_token = request.COOKIES.get('hexclave-access-token')
>
>     # List user's API keys via client API
>     response = requests.get(
>         'https://api.hexclave.com/api/v1/user-api-keys?user_id=me',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         }
>     )
>
>     if response.status_code != 200:
>         raise Exception(f"Failed to list API keys: {response.text}")
>
>     return JsonResponse(response.json(), safe=False)
> ```

> [tab] FastAPI
>
> ```python title="main.py" theme={null}
> import requests
> from fastapi import Cookie, HTTPException
>
> @app.get("/api/user-api-keys")
> async def list_user_api_keys(hexclave_access_token: str = Cookie(None, alias="hexclave-access-token")):
>     if not hexclave_access_token:
>         raise HTTPException(status_code=401, detail="Not authenticated")
>
>     # List user's API keys via client API
>     response = requests.get(
>         'https://api.hexclave.com/api/v1/user-api-keys?user_id=me',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': hexclave_access_token,
>         }
>     )
>
>     if response.status_code != 200:
>         raise HTTPException(status_code=response.status_code, detail=response.text)
>
>     return response.json()
> ```

> [tab] Flask
>
> ```python title="app.py" theme={null}
> import requests
> from flask import request, jsonify
>
> @app.route('/api/user-api-keys', methods=['GET'])
> def list_user_api_keys():
>     access_token = request.cookies.get('hexclave-access-token')
>     if not access_token:
>         return jsonify({'error': 'Not authenticated'}), 401
>
>     # List user's API keys via client API
>     response = requests.get(
>         'https://api.hexclave.com/api/v1/user-api-keys?user_id=me',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         }
>     )
>
>     if response.status_code != 200:
>         return jsonify({'error': response.text}), response.status_code
>
>     return jsonify(response.json())
> ```

The same pattern works for team API keys via `team.useApiKeys()` (client) or `team.listApiKeys()` (server), and the `/api/v1/team-api-keys` REST endpoint.

### Validating an incoming API key on your server

This is the core authentication flow: an incoming request includes an API key, and your server needs to know **which user or team** it represents. Pass the plaintext key directly to `hexclaveServerApp.getUser({ apiKey })` or `hexclaveServerApp.getTeam({ apiKey })` - Hexclave validates it and returns the corresponding object, or `null` if the key is invalid, expired, or revoked.

> [tab] Next.js Server
>
> ```typescript title="app/api/protected/route.ts" theme={null}
> import { hexclaveServerApp } from "@/hexclave/server";
>
> export async function GET(request: Request) {
>   const auth = request.headers.get("authorization");
>   const apiKey = auth?.replace(/^Bearer\s+/i, "");
>   if (!apiKey) {
>     return new Response("Missing API key", { status: 401 });
>   }
>
>   const user = await hexclaveServerApp.getUser({ apiKey });
>   if (!user) {
>     return new Response("Invalid API key", { status: 401 });
>   }
>
>   return Response.json({ userId: user.id, displayName: user.displayName });
> }
> ```

> [tab] Team key (Next.js Server)
>
> ```typescript title="app/api/team-protected/route.ts" theme={null}
> import { hexclaveServerApp } from "@/hexclave/server";
>
> export async function GET(request: Request) {
>   const apiKey = request.headers.get("authorization")?.replace(/^Bearer\s+/i, "");
>   if (!apiKey) return new Response("Missing API key", { status: 401 });
>
>   const team = await hexclaveServerApp.getTeam({ apiKey });
>   if (!team) return new Response("Invalid team API key", { status: 401 });
>
>   return Response.json({ teamId: team.id, displayName: team.displayName });
> }
> ```

> [tab] Django
>
> ```python title="views.py" theme={null}
> import requests
> from django.http import JsonResponse
>
> def protected_view(request):
>     auth_header = request.headers.get('Authorization', '')
>     if not auth_header.startswith('Bearer '):
>         return JsonResponse({'error': 'Missing API key'}, status=401)
>     api_key = auth_header[len('Bearer '):]
>
>     # Check the API key via server API
>     check = requests.post(
>         'https://api.hexclave.com/api/v1/user-api-keys/check',
>         headers={
>             'x-hexclave-access-type': 'server',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-secret-server-key': hexclave_secret_server_key,
>         },
>         json={'api_key': api_key},
>     )
>
>     if check.status_code != 200:
>         return JsonResponse({'error': 'Invalid API key'}, status=401)
>
>     api_key_obj = check.json()
>     # Fetch the owning user
>     user_resp = requests.get(
>         f'https://api.hexclave.com/api/v1/users/{api_key_obj["user_id"]}',
>         headers={
>             'x-hexclave-access-type': 'server',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-secret-server-key': hexclave_secret_server_key,
>         },
>     )
>     user = user_resp.json()
>     return JsonResponse({'userId': user['id'], 'displayName': user.get('display_name')})
> ```

> [tab] FastAPI
>
> ```python title="main.py" theme={null}
> import requests
> from fastapi import Header, HTTPException
>
> @app.get("/api/protected")
> async def protected_view(authorization: str = Header(None)):
>     if not authorization or not authorization.startswith('Bearer '):
>         raise HTTPException(status_code=401, detail="Missing API key")
>     api_key = authorization[len('Bearer '):]
>
>     # Check the API key via server API
>     check = requests.post(
>         'https://api.hexclave.com/api/v1/user-api-keys/check',
>         headers={
>             'x-hexclave-access-type': 'server',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-secret-server-key': hexclave_secret_server_key,
>         },
>         json={'api_key': api_key},
>     )
>
>     if check.status_code != 200:
>         raise HTTPException(status_code=401, detail="Invalid API key")
>
>     api_key_obj = check.json()
>     user_resp = requests.get(
>         f'https://api.hexclave.com/api/v1/users/{api_key_obj["user_id"]}',
>         headers={
>             'x-hexclave-access-type': 'server',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-secret-server-key': hexclave_secret_server_key,
>         },
>     )
>     user = user_resp.json()
>     return {'userId': user['id'], 'displayName': user.get('display_name')}
> ```

> [tab] Flask
>
> ```python title="app.py" theme={null}
> import requests
> from flask import request, jsonify
>
> @app.route('/api/protected', methods=['GET'])
> def protected_view():
>     auth_header = request.headers.get('Authorization', '')
>     if not auth_header.startswith('Bearer '):
>         return jsonify({'error': 'Missing API key'}), 401
>     api_key = auth_header[len('Bearer '):]
>
>     # Check the API key via server API
>     check = requests.post(
>         'https://api.hexclave.com/api/v1/user-api-keys/check',
>         headers={
>             'x-hexclave-access-type': 'server',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-secret-server-key': hexclave_secret_server_key,
>         },
>         json={'api_key': api_key},
>     )
>
>     if check.status_code != 200:
>         return jsonify({'error': 'Invalid API key'}), 401
>
>     api_key_obj = check.json()
>     user_resp = requests.get(
>         f'https://api.hexclave.com/api/v1/users/{api_key_obj["user_id"]}',
>         headers={
>             'x-hexclave-access-type': 'server',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-secret-server-key': hexclave_secret_server_key,
>         },
>     )
>     user = user_resp.json()
>     return jsonify({'userId': user['id'], 'displayName': user.get('display_name')})
> ```

### Checking an existing key's validity

When you already hold an `ApiKey` object (e.g. from `useApiKeys()`), use its synchronous helpers `isValid()` and `whyInvalid()`. The latter returns `"manually-revoked"`, `"expired"`, or `null`.

> [tab] Next.js Client
>
> ```typescript title="app/components/check-api-key.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/next";
>
> export default function CheckApiKeyValidity({ apiKeyId }: { apiKeyId: string }) {
>   const user = useUser({ or: 'redirect' });
>   const apiKeys = user.useApiKeys();
>
>   const apiKey = apiKeys.find(key => key.id === apiKeyId);
>
>   if (!apiKey) {
>     return <div>API key not found</div>;
>   }
>
>   if (apiKey.isValid()) {
>     return <div>API key is valid</div>;
>   }
>
>   const reason = apiKey.whyInvalid();
>   return <div>API key is invalid: {reason}</div>;
> }
> ```

> [tab] Next.js Server
>
> ```typescript title="app/components/check-api-key.tsx" theme={null}
> import { hexclaveServerApp } from "@/hexclave/server";
>
> export default async function CheckApiKeyValidity({
>   userId,
>   apiKeyId
> }: {
>   userId: string,
>   apiKeyId: string
> }) {
>   const user = await hexclaveServerApp.getUser(userId);
>   if (!user) return <div>User not found</div>;
>
>   const apiKeys = await user.listApiKeys();
>   const apiKey = apiKeys.find(key => key.id === apiKeyId);
>
>   if (!apiKey) {
>     return <div>API key not found</div>;
>   }
>
>   if (apiKey.isValid()) {
>     return <div>API key is valid</div>;
>   }
>
>   const reason = apiKey.whyInvalid();
>   return <div>API key is invalid: {reason}</div>;
> }
> ```

> [tab] React
>
> ```typescript title="components/CheckApiKey.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/react";
>
> export default function CheckApiKeyValidity({ apiKeyId }: { apiKeyId: string }) {
>   const user = useUser({ or: 'redirect' });
>   const apiKeys = user.useApiKeys();
>
>   const apiKey = apiKeys.find(key => key.id === apiKeyId);
>
>   if (!apiKey) {
>     return <div>API key not found</div>;
>   }
>
>   if (apiKey.isValid()) {
>     return <div>API key is valid</div>;
>   }
>
>   const reason = apiKey.whyInvalid();
>   return <div>API key is invalid: {reason}</div>;
> }
> ```

> [tab] Django
>
> ```python title="views.py" theme={null}
> import requests
> import time
> from django.http import JsonResponse
>
> def check_api_key_validity(request, api_key_id):
>     # Get the current user's access token from session/cookie
>     access_token = request.COOKIES.get('hexclave-access-token')
>
>     # Get API key details via client API
>     response = requests.get(
>         f'https://api.hexclave.com/api/v1/user-api-keys/{api_key_id}',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         }
>     )
>
>     if response.status_code != 200:
>         return JsonResponse({'error': 'API key not found'}, status=404)
>
>     api_key = response.json()
>
>     # Check if manually revoked
>     if api_key.get('manually_revoked_at_millis'):
>         return JsonResponse({
>             'valid': False,
>             'reason': 'manually-revoked'
>         })
>
>     # Check if expired
>     if api_key.get('expires_at_millis'):
>         if api_key['expires_at_millis'] < time.time() * 1000:
>             return JsonResponse({
>                 'valid': False,
>                 'reason': 'expired'
>             })
>
>     return JsonResponse({'valid': True})
> ```

> [tab] FastAPI
>
> ```python title="main.py" theme={null}
> import requests
> import time
> from fastapi import Cookie, HTTPException
>
> @app.get("/api/check-api-key/{api_key_id}")
> async def check_api_key_validity(api_key_id: str, hexclave_access_token: str = Cookie(None, alias="hexclave-access-token")):
>     if not hexclave_access_token:
>         raise HTTPException(status_code=401, detail="Not authenticated")
>
>     # Get API key details via client API
>     response = requests.get(
>         f'https://api.hexclave.com/api/v1/user-api-keys/{api_key_id}',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': hexclave_access_token,
>         }
>     )
>
>     if response.status_code != 200:
>         raise HTTPException(status_code=404, detail="API key not found")
>
>     api_key = response.json()
>
>     # Check if manually revoked
>     if api_key.get('manually_revoked_at_millis'):
>         return {
>             'valid': False,
>             'reason': 'manually-revoked'
>         }
>
>     # Check if expired
>     if api_key.get('expires_at_millis'):
>         if api_key['expires_at_millis'] < time.time() * 1000:
>             return {
>                 'valid': False,
>                 'reason': 'expired'
>             }
>
>     return {'valid': True}
> ```

> [tab] Flask
>
> ```python title="app.py" theme={null}
> import requests
> import time
> from flask import request, jsonify
>
> @app.route('/api/check-api-key/<api_key_id>', methods=['GET'])
> def check_api_key_validity(api_key_id):
>     access_token = request.cookies.get('hexclave-access-token')
>     if not access_token:
>         return jsonify({'error': 'Not authenticated'}), 401
>
>     # Get API key details via client API
>     response = requests.get(
>         f'https://api.hexclave.com/api/v1/user-api-keys/{api_key_id}',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         }
>     )
>
>     if response.status_code != 200:
>         return jsonify({'error': 'API key not found'}), 404
>
>     api_key = response.json()
>
>     # Check if manually revoked
>     if api_key.get('manually_revoked_at_millis'):
>         return jsonify({
>             'valid': False,
>             'reason': 'manually-revoked'
>         })
>
>     # Check if expired
>     if api_key.get('expires_at_millis'):
>         if api_key['expires_at_millis'] < time.time() * 1000:
>             return jsonify({
>                 'valid': False,
>                 'reason': 'expired'
>             })
>
>     return jsonify({'valid': True})
> ```

### Revoking an API key

API keys can be revoked when they are no longer needed or if they have been compromised. Revoking is irreversible: a revoked key's `manuallyRevokedAt` becomes set and `isValid()` returns `false` (`whyInvalid()` returns `"manually-revoked"`).

> [tab] Next.js Client
>
> ```typescript title="app/components/revoke-api-key.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/next";
>
> export default function RevokeApiKey({ apiKeyId }: { apiKeyId: string }) {
>   const user = useUser({ or: 'redirect' });
>   const apiKeys = user.useApiKeys();
>
>   const handleRevoke = async () => {
>     const apiKeyToRevoke = apiKeys.find(key => key.id === apiKeyId);
>
>     if (apiKeyToRevoke) {
>       await apiKeyToRevoke.revoke();
>       console.log("API Key revoked");
>     }
>   };
>
>   return <button onClick={handleRevoke}>Revoke API Key</button>;
> }
> ```

> [tab] Next.js Server
>
> ```typescript title="lib/api-keys.ts" theme={null}
> import { hexclaveServerApp } from "@/hexclave/server";
>
> export async function revokeApiKey(userId: string, apiKeyId: string) {
>   const user = await hexclaveServerApp.getUser(userId);
>   if (!user) return;
>
>   const apiKeys = await user.listApiKeys();
>   const apiKeyToRevoke = apiKeys.find(key => key.id === apiKeyId);
>
>   if (apiKeyToRevoke) {
>     await apiKeyToRevoke.revoke();
>   }
> }
> ```

> [tab] React
>
> ```typescript title="components/RevokeApiKey.tsx" theme={null}
> "use client";
> import { useUser } from "@hexclave/react";
>
> export default function RevokeApiKey({ apiKeyId }: { apiKeyId: string }) {
>   const user = useUser({ or: 'redirect' });
>   const apiKeys = user.useApiKeys();
>
>   const handleRevoke = async () => {
>     const apiKeyToRevoke = apiKeys.find(key => key.id === apiKeyId);
>
>     if (apiKeyToRevoke) {
>       await apiKeyToRevoke.revoke();
>       console.log("API Key revoked");
>     }
>   };
>
>   return <button onClick={handleRevoke}>Revoke API Key</button>;
> }
> ```

> [tab] Django
>
> ```python title="views.py" theme={null}
> import requests
> from django.http import JsonResponse
>
> def revoke_api_key(request, api_key_id):
>     # Get the current user's access token from session/cookie
>     access_token = request.COOKIES.get('hexclave-access-token')
>
>     # Revoke API key via client API (update with revoked: true)
>     response = requests.patch(
>         f'https://api.hexclave.com/api/v1/user-api-keys/{api_key_id}',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         },
>         json={
>             'revoked': True,
>         }
>     )
>
>     if response.status_code != 200:
>         raise Exception(f"Failed to revoke API key: {response.text}")
>
>     return JsonResponse({'message': 'API key revoked successfully'})
> ```

> [tab] FastAPI
>
> ```python title="main.py" theme={null}
> import requests
> from fastapi import Cookie, HTTPException
>
> @app.delete("/api/user-api-keys/{api_key_id}")
> async def revoke_api_key(api_key_id: str, hexclave_access_token: str = Cookie(None, alias="hexclave-access-token")):
>     if not hexclave_access_token:
>         raise HTTPException(status_code=401, detail="Not authenticated")
>
>     # Revoke API key via client API (update with revoked: true)
>     response = requests.patch(
>         f'https://api.hexclave.com/api/v1/user-api-keys/{api_key_id}',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': hexclave_access_token,
>         },
>         json={
>             'revoked': True,
>         }
>     )
>
>     if response.status_code != 200:
>         raise HTTPException(status_code=response.status_code, detail=response.text)
>
>     return {"message": "API key revoked successfully"}
> ```

> [tab] Flask
>
> ```python title="app.py" theme={null}
> import requests
> from flask import request, jsonify
>
> @app.route('/api/user-api-keys/<api_key_id>', methods=['DELETE'])
> def revoke_api_key(api_key_id):
>     access_token = request.cookies.get('hexclave-access-token')
>     if not access_token:
>         return jsonify({'error': 'Not authenticated'}), 401
>
>     # Revoke API key via client API (update with revoked: true)
>     response = requests.patch(
>         f'https://api.hexclave.com/api/v1/user-api-keys/{api_key_id}',
>         headers={
>             'x-hexclave-access-type': 'client',
>             'x-hexclave-project-id': hexclave_project_id,
>             'x-hexclave-publishable-client-key': hexclave_publishable_client_key,
>             'x-hexclave-access-token': access_token,
>         },
>         json={
>             'revoked': True,
>         }
>     )
>
>     if response.status_code != 200:
>         return jsonify({'error': response.text}), response.status_code
>
>     return jsonify({'message': 'API key revoked successfully'})
> ```

## Best Practices

1. **Show the value once.** API key values are returned in plaintext only at creation. Always display, copy, or send them immediately - don't expect to fetch them again later.
2. **Set sensible expirations.** Long-lived keys are convenient but risky. Default to short expirations (30–90 days) and let users rotate.
3. **Don't share user keys across users.** A user API key acts as that exact user. If a service needs to act independently, prefer a team API key with a service-style role.
4. **Use `$manage_api_keys` deliberately.** Only grant this team permission to roles you'd trust to lock or unlock the entire team's programmatic access.
5. **Mark public keys with `isPublic: true`.** This opts them out of the secret scanner so your legitimately-public keys don't get auto-revoked.
6. **Use `getUser({ apiKey })` / `getTeam({ apiKey })` for validation.** Never try to parse or compare the plaintext value yourself - Hexclave handles hashing, expiration, and revocation.
