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

> ## Agent Instructions
> The Exa API is served at https://api.exa.ai. Authenticate with `Authorization: Bearer $EXA_API_KEY` (or `x-api-key: $EXA_API_KEY`); create keys at https://dashboard.exa.ai/api-keys?utm_source=docs&utm_medium=docs&utm_campaign=markdown_instructions. Every way an agent can get access to Exa is described at https://exa.ai/auth.md.
> Prefer the official SDKs, `exa-py` (`pip install exa-py`) and `exa-js` (`npm install exa-js`); both read `EXA_API_KEY` from the environment.
> Tool-using agents can call Exa without writing code through the hosted MCP server at https://mcp.exa.ai/mcp, or install the Exa agent skill with `npx skills add exa-labs/agent-skills` (skill file: https://exa.ai/docs/skill.md).
> The OpenAPI specs at https://exa.ai/docs/exa-spec.yaml and https://exa.ai/docs/team-management-spec.yaml are the source of truth for request and response schemas.

# Rotate API key

> Replace an API key's secret, keeping the old secret valid for a grace period.

> [card] [Get your Exa API key](https://dashboard.exa.ai/api-keys?utm_source=docs\&utm_medium=docs\&utm_campaign=get_key_card)
>
> Create a key in the dashboard. New accounts start with free credits.

> [!NOTE]
>
> The Team Management API is enabled per team. It authenticates with a service account API key, which is created from the **Service keys** tab on the [API keys page](https://dashboard.exa.ai/api-keys?utm_source=docs\&utm_medium=docs\&utm_campaign=team_management_access) once the feature is enabled for your team. Contact [support@exa.ai](mailto:support@exa.ai) to request access.

## Overview

Rotating replaces a key's secret in place. The key keeps its `publicId`, name, rate limit, budget, and usage history, so only the secret stored in your vault changes.

## Rotating on a schedule

1. Call this endpoint with the key's `publicId` and a `gracePeriodSeconds` long enough to roll out the new secret, for example `86400` for one day.
2. Store `apiKey.secret` from the response in your secrets manager and deploy it.
3. The old secret stops working at `previousSecretExpiresAt`. To cut it off sooner, call [Revoke previous API key secret](https://exa.ai/docs/reference/team-management/revoke-previous-api-key-secret.md).

Address the key by its `publicId`. It never changes, while an old secret stops identifying the key once its grace period ends.

## Rotating again during a grace period

A key has at most one previous secret. While the secret replaced by the last rotation is still in its grace period, a new rotation returns `409` with `code` set to `previous_secret_active` and changes nothing, so a job that fires twice cannot revoke a secret you are still deploying. Wait until `previousSecretExpiresAt`, revoke the previous secret first, or send `"force": true` to rotate anyway.

## Recovering a lost response

If the rotation succeeded but your job did not receive the response, read the current secret back from the `id` field of [Get API key](https://exa.ai/docs/reference/team-management/get-api-key.md) using the key's `publicId`.


## OpenAPI

````yaml team-management-spec.yaml POST /api-keys/{id}/rotate
openapi: 3.1.0
info:
  version: 1.0.0
  title: Team Management API
  description: >-
    API for managing API keys within teams. Provides CRUD operations for
    creating, listing, updating, and deleting API keys with team-based access
    controls. The API is enabled per team. Contact support@exa.ai to request
    access.
servers:
  - url: https://admin-api.exa.ai/team-management
security:
  - apikey: []
paths:
  /api-keys/{id}/rotate:
    post:
      tags:
        - Team Management
      summary: Rotate API key
      description: >-
        Replaces an API key's secret and returns the new one. The key keeps its
        `publicId`, name, rate limit, budget, and usage history. The old secret
        keeps working for `gracePeriodSeconds` (0 revokes it immediately), so
        the new secret can be rolled out before the old one stops working. A key
        has at most one previous secret: while it is still in its grace period,
        rotation returns 409 unless `force` is true.
      operationId: rotate-api-key
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: >-
            The key's `publicId`. The key's current secret, or a previous secret
            still in its grace period, is also accepted for backward
            compatibility.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - gracePeriodSeconds
              properties:
                gracePeriodSeconds:
                  type: integer
                  minimum: 0
                  maximum: 7776000
                  description: >-
                    How long the old secret keeps working, in seconds (at most
                    90 days). 0 revokes it immediately.
                  example: 86400
                force:
                  type: boolean
                  default: false
                  description: >-
                    Rotate even if the secret replaced by an earlier rotation is
                    still in its grace period. That secret stops working
                    immediately.
              additionalProperties: false
      responses:
        '200':
          description: API key rotated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  apiKey:
                    type: object
                    properties:
                      id:
                        type: string
                        format: uuid
                        deprecated: true
                        description: >-
                          Same value as `secret`, kept for backward
                          compatibility. Use `publicId` to identify the key.
                      publicId:
                        type: string
                        description: >-
                          Stable identifier of the API key, unchanged by
                          rotation.
                      secret:
                        type: string
                        description: >-
                          The new API key. Send it in the `x-api-key` header of
                          Exa API requests and keep it in your secrets manager.
                      name:
                        type: string
                      rateLimit:
                        type:
                          - integer
                          - 'null'
                        description: Rate limit in requests per second
                      budgetCents:
                        type:
                          - integer
                          - 'null'
                        description: Spending budget for the API key, in cents
                      isOverBudget:
                        type: boolean
                        description: Whether the API key is currently over its budget
                      teamId:
                        type: string
                      createdAt:
                        type: string
                        format: date-time
                      previousSecretExpiresAt:
                        type:
                          - string
                          - 'null'
                        format: date-time
                        description: >-
                          When the old secret stops working, or null if it was
                          revoked immediately.
              example:
                apiKey:
                  id: 00000000-0000-4000-8000-000000000000
                  publicId: key_a1B2c3D4e5F6g7H8i9J0
                  secret: 00000000-0000-4000-8000-000000000000
                  name: Production API Key
                  rateLimit: 1000
                  budgetCents: null
                  isOverBudget: false
                  teamId: cmdz0ppkp0001x7ec2yh4ph46
                  createdAt: '2026-01-01T00:00:00.000Z'
                  previousSecretExpiresAt: '2026-10-06T12:00:00.000Z'
        '400':
          description: Bad Request - Invalid parameters
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples:
                      - Invalid API key ID format.
                      - >-
                        gracePeriodSeconds: Invalid input: expected number,
                        received undefined
        '401':
          description: Unauthorized - Invalid or missing service key
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
        '403':
          description: Forbidden - the key is managed by an integration
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: >-
                      This key is managed by an integration and cannot be
                      rotated
        '404':
          description: Not Found - API key does not exist
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: API key not found
        '409':
          description: >-
            Conflict - the secret replaced by an earlier rotation is still in
            its grace period (`previous_secret_active`), or another rotation of
            the same key finished first (`rotation_conflict`). Nothing was
            changed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  code:
                    type: string
                    enum:
                      - previous_secret_active
                      - rotation_conflict
                  previousSecretExpiresAt:
                    type: string
                    format: date-time
                    description: >-
                      When the previous secret stops working. Present with
                      `previous_secret_active`.
      security:
        - apikey: []
      x-codeSamples:
        - lang: bash
          label: Rotate with a one-day grace period
          source: >
            curl -X POST
            'https://admin-api.exa.ai/team-management/api-keys/{id}/rotate' \
              -H "x-api-key: $EXA_SERVICE_KEY" \
              -H 'Content-Type: application/json' \
              -d '{
                "gracePeriodSeconds": 86400
              }'
        - lang: python
          label: Rotate with a one-day grace period
          source: |
            import os

            import requests

            response = requests.post(
                'https://admin-api.exa.ai/team-management/api-keys/{id}/rotate',
                headers={'x-api-key': os.environ['EXA_SERVICE_KEY']},
                json={'gracePeriodSeconds': 86400}
            )
            response.raise_for_status()

            new_secret = response.json()['apiKey']['secret']
        - lang: javascript
          label: Rotate with a one-day grace period
          source: >
            const response = await
            fetch('https://admin-api.exa.ai/team-management/api-keys/{id}/rotate',
            {
              method: 'POST',
              headers: {
                'x-api-key': process.env.EXA_SERVICE_KEY,
                'Content-Type': 'application/json'
              },
              body: JSON.stringify({ gracePeriodSeconds: 86400 })
            });


            const { apiKey } = await response.json();

            const newSecret = apiKey.secret;
components:
  securitySchemes:
    apikey:
      type: apiKey
      in: header
      name: x-api-key
      description: Service API key for team authentication

````