> ## 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.
</Note>

#### User API keys

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

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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())
    ```
  </Tab>
</Tabs>

#### Team API keys

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

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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())
    ```
  </Tab>
</Tabs>

## 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`](/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.

<Tabs>
  <Tab title="Next.js">
    ```typescript title="app/account/page.tsx" theme={null}
    import { AccountSettings } from '@hexclave/next';

    export default function MyAccountPage() {
      return <AccountSettings fullPage />;
    }
    ```
  </Tab>

  <Tab title="React">
    ```typescript title="src/account-page.tsx" theme={null}
    import { AccountSettings } from '@hexclave/react';

    export default function MyAccountPage() {
      return <AccountSettings fullPage />;
    }
    ```
  </Tab>
</Tabs>

### 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.
</Warning>

### `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

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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())
    ```
  </Tab>
</Tabs>

### Creating a team API key

Requires the `$manage_api_keys` team permission.

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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())
    ```
  </Tab>
</Tabs>

### Listing API keys

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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())
    ```
  </Tab>
</Tabs>

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.

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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')})
    ```
  </Tab>
</Tabs>

### 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`.

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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})
    ```
  </Tab>
</Tabs>

### 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"`).

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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'})
    ```
  </Tab>
</Tabs>

## 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.
