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

# API Key Management

> Creating, rotating, and revoking API keys for secure programmatic access

This guide covers how to manage your DocIntell API keys programmatically. API keys provide secure access to the DocIntell API for your applications and services.

<Note>
  DocIntell is currently invite-only. Contact your account manager to get started.
</Note>

## Overview

DocIntell uses API keys for programmatic access to the platform. Each API key:

* Is scoped to a single tenant (your organization)
* Can be either `live` (production) or `test` (development)
* Is hashed before storage (we never store plaintext keys)
* Can be rotated with zero downtime using grace periods
* Can be revoked immediately if compromised

## Key Environments

DocIntell supports two key environments:

| Environment | Prefix     | Purpose                 | Billing           |
| ----------- | ---------- | ----------------------- | ----------------- |
| **Test**    | `dk_test_` | Development and testing | Free (no billing) |
| **Live**    | `dk_live_` | Production workloads    | Billable usage    |

<Warning>
  Always use **test keys** for development and staging environments. Only use **live keys** in production.
</Warning>

## Creating an API Key

Create a new API key by calling `POST /v1/keys`:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.docintell.com/v1/keys \
    -H "Authorization: Bearer dk_live_YOUR_ADMIN_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Production Server",
      "environment": "live"
    }'
  ```

  ```python Python theme={null}
  import requests

  headers = {
      "Authorization": "Bearer dk_live_YOUR_ADMIN_KEY",
      "Content-Type": "application/json"
  }

  data = {
      "name": "Production Server",
      "environment": "live"
  }

  response = requests.post(
      "https://api.docintell.com/v1/keys",
      headers=headers,
      json=data
  )

  api_key = response.json()["api_key"]
  print(f"New API key: {api_key}")
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch('https://api.docintell.com/v1/keys', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer dk_live_YOUR_ADMIN_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: 'Production Server',
      environment: 'live',
    }),
  });

  const { api_key } = await response.json();
  console.log(`New API key: ${api_key}`);
  ```

  ```go Go theme={null}
  import (
      "bytes"
      "encoding/json"
      "net/http"
  )

  payload := map[string]string{
      "name":        "Production Server",
      "environment": "live",
  }

  body, _ := json.Marshal(payload)
  req, _ := http.NewRequest("POST", "https://api.docintell.com/v1/keys", bytes.NewBuffer(body))
  req.Header.Set("Authorization", "Bearer dk_live_YOUR_ADMIN_KEY")
  req.Header.Set("Content-Type", "application/json")

  client := &http.Client{}
  resp, _ := client.Do(req)
  ```
</CodeGroup>

**Response:**

```json theme={null}
{
  "key_id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Production Server",
  "api_key": "dk_live_K7gNU3sdo-OL0wNhqoVWhr3g6s1xYv72ol_pe_Unols",
  "environment": "live",
  "created_at": "2024-01-15T10:30:00Z"
}
```

<Warning>
  **The API key is shown ONLY ONCE in this response.** Copy it immediately and store it securely.
  If you lose the key, you'll need to create a new one.
</Warning>

## Listing Your API Keys

View all API keys in your account with `GET /v1/keys`:

```bash theme={null}
curl -X GET https://api.docintell.com/v1/keys \
  -H "Authorization: Bearer dk_live_YOUR_API_KEY"
```

**Response:**

```json theme={null}
{
  "keys": [
    {
      "key_id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Production Server",
      "key_prefix": "dk_live_",
      "key_suffix": "nols",
      "environment": "live",
      "is_active": true,
      "status": "active",
      "created_at": "2024-01-15T10:30:00Z",
      "last_used_at": "2024-01-15T12:45:00Z"
    },
    {
      "key_id": "660e8400-e29b-41d4-a716-446655440001",
      "name": "Old Production Key",
      "key_prefix": "dk_live_",
      "key_suffix": "aBc1",
      "environment": "live",
      "is_active": true,
      "status": "deprecated",
      "created_at": "2024-01-01T10:30:00Z",
      "last_used_at": "2024-01-15T12:45:00Z",
      "deprecated_at": "2024-01-15T10:30:00Z",
      "grace_period_ends_at": "2024-01-22T10:30:00Z",
      "grace_period_days_remaining": 5
    }
  ]
}
```

<Info>
  For security, the full API key is **never shown** after creation. Only the prefix (`dk_live_`) and last 4 characters are displayed for identification.
</Info>

### Response Fields

| Field                         | Description                                          |
| ----------------------------- | ---------------------------------------------------- |
| `key_prefix`                  | First 8 characters (e.g., `dk_live_`)                |
| `key_suffix`                  | Last 4 characters for identification                 |
| `status`                      | `active`, `deprecated`, or `revoked`                 |
| `is_active`                   | Whether the key can still be used for authentication |
| `deprecated_at`               | When key rotation began (if status is `deprecated`)  |
| `grace_period_ends_at`        | When the deprecated key will stop working            |
| `grace_period_days_remaining` | Days left before deprecated key expires              |

## Rotating API Keys

Key rotation is the **recommended way** to update your API keys without service disruption. When you rotate a key:

1. A new key is created
2. The old key is marked as **deprecated** (but still works)
3. You have **7 days** to migrate your systems
4. After 7 days, the old key automatically expires

### Why Rotate Keys?

<CardGroup cols={2}>
  <Card title="Security Best Practice" icon="shield">
    Regular rotation limits the window of exposure if a key is compromised.
  </Card>

  <Card title="Zero Downtime" icon="clock">
    Grace period allows gradual migration without service interruption.
  </Card>

  <Card title="Compliance" icon="file-check">
    Many compliance frameworks require periodic credential rotation.
  </Card>

  <Card title="Incident Response" icon="bell">
    Quick response to suspected key compromise without breaking production.
  </Card>
</CardGroup>

### Step-by-Step Rotation

<Steps>
  <Step title="Initiate Rotation">
    Call `POST /v1/keys/rotate` to create a new key:

    ```bash theme={null}
    curl -X POST https://api.docintell.com/v1/keys/rotate \
      -H "Authorization: Bearer dk_live_YOUR_CURRENT_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Production Server",
        "environment": "live"
      }'
    ```

    **Response:**

    ```json theme={null}
    {
      "new_key": {
        "key_id": "770e8400-e29b-41d4-a716-446655440002",
        "name": "Production Server",
        "api_key": "dk_live_newKeyXYZ123456789abcdefghijklmnop",
        "environment": "live",
        "created_at": "2024-01-15T10:30:00Z"
      },
      "deprecated_key": {
        "key_id": "550e8400-e29b-41d4-a716-446655440000",
        "name": "Production Server",
        "key_prefix": "dk_live_",
        "key_suffix": "xY9z",
        "environment": "live",
        "is_active": true,
        "status": "deprecated",
        "created_at": "2024-01-01T10:30:00Z",
        "last_used_at": "2024-01-15T10:29:00Z",
        "deprecated_at": "2024-01-15T10:30:00Z",
        "grace_period_ends_at": "2024-01-22T10:30:00Z",
        "grace_period_days_remaining": 7
      }
    }
    ```

    <Warning>
      Save the `new_key.api_key` value immediately - it's shown only in this response!
    </Warning>
  </Step>

  <Step title="Update Your Applications">
    During the 7-day grace period, update your applications to use the new key:

    * Update environment variables
    * Redeploy services with the new key
    * Update CI/CD pipelines
    * Update secrets in your secrets manager (AWS Secrets Manager, GCP Secret Manager, etc.)

    <Note>
      Both the old and new keys work during the grace period. There's no rush to migrate everything at once.
    </Note>
  </Step>

  <Step title="Monitor Usage">
    Track which systems are still using the old key by checking the `last_used_at` timestamp:

    ```bash theme={null}
    curl -X GET https://api.docintell.com/v1/keys \
      -H "Authorization: Bearer dk_live_YOUR_NEW_KEY"
    ```

    If the deprecated key's `last_used_at` timestamp is recent, some systems are still using it.
  </Step>

  <Step title="Wait for Grace Period to Expire">
    After 7 days, the old key automatically expires. If you've migrated all systems, you can manually revoke it early:

    ```bash theme={null}
    curl -X DELETE https://api.docintell.com/v1/keys/550e8400-e29b-41d4-a716-446655440000 \
      -H "Authorization: Bearer dk_live_YOUR_NEW_KEY"
    ```
  </Step>
</Steps>

### Grace Period Details

| Timeline             | Old Key                    | New Key        |
| -------------------- | -------------------------- | -------------- |
| **Day 0** (rotation) | `deprecated` (still works) | `active`       |
| **Days 1-7**         | Both keys work             | Both keys work |
| **Day 8+**           | Automatically expires      | Active         |

<Info>
  The 7-day grace period is fixed and cannot be extended. Plan your migration accordingly.
</Info>

## Revoking API Keys

If a key is compromised or no longer needed, revoke it immediately with `DELETE /v1/keys/{key_id}`:

```bash theme={null}
curl -X DELETE https://api.docintell.com/v1/keys/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer dk_live_YOUR_ADMIN_KEY"
```

**Response:** `204 No Content`

<Warning>
  **Revocation is immediate.** The key stops working as soon as the request completes. There is no grace period.
</Warning>

### When to Revoke vs Rotate

| Scenario                  | Action                                        |
| ------------------------- | --------------------------------------------- |
| Suspected key compromise  | **Revoke immediately**, then create a new key |
| Routine security hygiene  | **Rotate** (7-day grace period)               |
| Decommissioning a service | **Revoke** once the service is shut down      |
| Employee offboarding      | **Revoke** keys associated with that employee |

<Note>
  Revoked keys are retained in the database for audit purposes but cannot be used for authentication.
</Note>

## Monitoring Your API Keys

Effective API key monitoring helps you maintain security, track usage, and prevent service disruptions during key rotation. The `GET /v1/keys` endpoint provides comprehensive visibility into your key inventory.

### List All Keys

Retrieve all API keys in your account with their metadata:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET https://api.docintell.com/v1/keys \
    -H "Authorization: Bearer dk_live_YOUR_API_KEY"
  ```

  ```python Python theme={null}
  import requests

  headers = {
      "Authorization": "Bearer dk_live_YOUR_API_KEY"
  }

  response = requests.get(
      "https://api.docintell.com/v1/keys",
      headers=headers
  )

  keys = response.json()["keys"]
  for key in keys:
      print(f"{key['name']}: {key['status']} (last used: {key['last_used_at']})")
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch('https://api.docintell.com/v1/keys', {
    method: 'GET',
    headers: {
      'Authorization': 'Bearer dk_live_YOUR_API_KEY',
    },
  });

  const { keys } = await response.json();
  keys.forEach((key: any) => {
    console.log(`${key.name}: ${key.status} (last used: ${key.last_used_at})`);
  });
  ```

  ```go Go theme={null}
  import (
      "encoding/json"
      "net/http"
  )

  req, _ := http.NewRequest("GET", "https://api.docintell.com/v1/keys", nil)
  req.Header.Set("Authorization", "Bearer dk_live_YOUR_API_KEY")

  client := &http.Client{}
  resp, _ := client.Do(req)

  var result map[string]interface{}
  json.NewDecoder(resp.Body).Decode(&result)

  keys := result["keys"].([]interface{})
  for _, k := range keys {
      key := k.(map[string]interface{})
      fmt.Printf("%s: %s (last used: %s)\n",
          key["name"], key["status"], key["last_used_at"])
  }
  ```
</CodeGroup>

**Example Response:**

```json theme={null}
{
  "keys": [
    {
      "key_id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Production Server",
      "key_prefix": "dk_live_",
      "key_suffix": "nols",
      "environment": "live",
      "is_active": true,
      "status": "active",
      "created_at": "2024-01-15T10:30:00Z",
      "last_used_at": "2024-01-15T12:45:00Z",
      "deprecated_at": null,
      "grace_period_ends_at": null,
      "grace_period_days_remaining": null
    },
    {
      "key_id": "660e8400-e29b-41d4-a716-446655440001",
      "name": "Old Production Key",
      "key_prefix": "dk_live_",
      "key_suffix": "aBc1",
      "environment": "live",
      "is_active": true,
      "status": "deprecated",
      "created_at": "2024-01-01T10:30:00Z",
      "last_used_at": "2024-01-15T12:45:00Z",
      "deprecated_at": "2024-01-15T10:30:00Z",
      "grace_period_ends_at": "2024-01-22T10:30:00Z",
      "grace_period_days_remaining": 5
    }
  ]
}
```

### Query Parameters

| Parameter            | Type    | Default | Description                                                |
| -------------------- | ------- | ------- | ---------------------------------------------------------- |
| `include_deprecated` | boolean | `true`  | Include keys in grace period (deprecated but still active) |

**Example: Hide deprecated keys**

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.docintell.com/v1/keys?include_deprecated=false" \
    -H "Authorization: Bearer dk_live_YOUR_API_KEY"
  ```

  ```python Python theme={null}
  response = requests.get(
      "https://api.docintell.com/v1/keys",
      headers=headers,
      params={"include_deprecated": False}
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(
    'https://api.docintell.com/v1/keys?include_deprecated=false',
    {
      headers: { 'Authorization': 'Bearer dk_live_YOUR_API_KEY' },
    }
  );
  ```

  ```go Go theme={null}
  req, _ := http.NewRequest(
      "GET",
      "https://api.docintell.com/v1/keys?include_deprecated=false",
      nil,
  )
  req.Header.Set("Authorization", "Bearer dk_live_YOUR_API_KEY")
  ```
</CodeGroup>

### Response Fields

Each key in the response includes the following fields:

| Field                         | Type      | Description                                                      |
| ----------------------------- | --------- | ---------------------------------------------------------------- |
| `key_id`                      | UUID      | Unique identifier for the key                                    |
| `name`                        | string    | Descriptive name (e.g., "Production Server")                     |
| `key_prefix`                  | string    | First 8 characters (e.g., `dk_live_`)                            |
| `key_suffix`                  | string    | Last 4 characters for identification                             |
| `environment`                 | string    | `live` or `test`                                                 |
| `is_active`                   | boolean   | Whether the key can still be used for authentication             |
| `status`                      | string    | Key lifecycle status: `active`, `deprecated`, or `expired`       |
| `created_at`                  | timestamp | When the key was created                                         |
| `last_used_at`                | timestamp | Most recent successful authentication (null if never used)       |
| `deprecated_at`               | timestamp | When rotation began (null if not deprecated)                     |
| `grace_period_ends_at`        | timestamp | When deprecated key expires (null if not deprecated)             |
| `grace_period_days_remaining` | integer   | Days left before deprecated key expires (null if not deprecated) |

<Info>
  For security, the full API key is **never shown** after creation. Only the prefix (`dk_live_`) and last 4 characters are displayed for identification.
</Info>

### Key Status Lifecycle

API keys transition through three lifecycle states:

<Steps>
  <Step title="Active">
    **Normal operation**

    * Key is working and valid
    * `status: "active"`
    * `is_active: true`
    * Can authenticate API requests
    * No expiration date
  </Step>

  <Step title="Deprecated">
    **Grace period after rotation**

    * Key was replaced via rotation
    * `status: "deprecated"`
    * `is_active: true` (still works!)
    * Can still authenticate for 7 days
    * Monitor `grace_period_days_remaining`
    * Automatically expires after grace period

    **Timeline:**

    ```
    Day 0: Rotation initiated → deprecated_at set
    Days 1-7: Grace period → key still works
    Day 8: Automatic expiration → status becomes "expired"
    ```
  </Step>

  <Step title="Expired">
    **Grace period ended**

    * Key can no longer authenticate
    * `status: "expired"`
    * `is_active: false`
    * Retained for audit purposes
    * Cannot be reactivated (create a new key instead)
  </Step>
</Steps>

<Warning>
  **Revoked keys** have a special status:

  * `status: "revoked"` (not shown in lifecycle above)
  * `is_active: false`
  * Immediately stopped (no grace period)
  * Result of manual `DELETE /v1/keys/{key_id}` call
</Warning>

### Monitoring Best Practices

<CardGroup cols={2}>
  <Card title="Track Unused Keys" icon="magnifying-glass">
    Identify keys that haven't been used in 30+ days for security audits.

    ```python theme={null}
    from datetime import datetime, timedelta

    cutoff = datetime.utcnow() - timedelta(days=30)

    for key in keys:
        if key["last_used_at"] is None:
            print(f"⚠️ Never used: {key['name']}")
        elif datetime.fromisoformat(key["last_used_at"].replace("Z", "+00:00")) < cutoff:
            print(f"⚠️ Unused (30+ days): {key['name']}")
    ```
  </Card>

  <Card title="Monitor Grace Periods" icon="clock">
    Track keys nearing expiration during rotation.

    ```python theme={null}
    for key in keys:
        if key["status"] == "deprecated":
            days = key["grace_period_days_remaining"]
            if days <= 2:
                print(f"🚨 Expires soon: {key['name']} ({days} days)")
            else:
                print(f"⏳ In rotation: {key['name']} ({days} days)")
    ```
  </Card>

  <Card title="Verify Active Keys" icon="check">
    Ensure production services use active (non-deprecated) keys.

    ```python theme={null}
    active_keys = [k for k in keys if k["status"] == "active"]
    deprecated_keys = [k for k in keys if k["status"] == "deprecated"]

    print(f"✅ Active: {len(active_keys)}")
    print(f"⏳ In grace period: {len(deprecated_keys)}")
    ```
  </Card>

  <Card title="Set Up Alerts" icon="bell">
    Create automated alerts for key expiration.

    Example cron job (daily check):

    ```bash theme={null}
    # /etc/cron.daily/check-api-keys
    #!/bin/bash
    python /scripts/check_api_keys.py
    ```
  </Card>
</CardGroup>

### Example: Automated Key Health Check

Here's a complete script that monitors API key health and warns about potential issues:

<CodeGroup>
  ```python Python theme={null}
  #!/usr/bin/env python3
  """API Key Health Check - Run daily to monitor key status."""
  import os
  import sys
  from datetime import datetime, timedelta

  import requests

  API_KEY = os.environ["DOCINTELL_API_KEY"]
  BASE_URL = "https://api.docintell.com"

  def check_api_keys():
      """Check API key health and report issues."""
      response = requests.get(
          f"{BASE_URL}/v1/keys",
          headers={"Authorization": f"Bearer {API_KEY}"}
      )
      response.raise_for_status()

      keys = response.json()["keys"]
      issues = []

      # Check 1: Unused keys (never used or >30 days)
      cutoff = datetime.utcnow() - timedelta(days=30)
      for key in keys:
          if key["last_used_at"] is None:
              issues.append(f"⚠️ UNUSED: '{key['name']}' has never been used")
          else:
              last_used = datetime.fromisoformat(
                  key["last_used_at"].replace("Z", "+00:00")
              )
              if last_used < cutoff:
                  days_ago = (datetime.utcnow().replace(tzinfo=last_used.tzinfo) - last_used).days
                  issues.append(
                      f"⚠️ STALE: '{key['name']}' not used in {days_ago} days"
                  )

      # Check 2: Keys in grace period
      for key in keys:
          if key["status"] == "deprecated":
              days = key["grace_period_days_remaining"]
              if days <= 2:
                  issues.append(
                      f"🚨 URGENT: '{key['name']}' expires in {days} days!"
                  )
              else:
                  issues.append(
                      f"⏳ GRACE PERIOD: '{key['name']}' expires in {days} days"
                  )

      # Check 3: Multiple active keys (should only have 1 per environment)
      active_live = [k for k in keys if k["status"] == "active" and k["environment"] == "live"]
      active_test = [k for k in keys if k["status"] == "active" and k["environment"] == "test"]

      if len(active_live) > 1:
          issues.append(
              f"⚠️ MULTIPLE LIVE KEYS: {len(active_live)} active live keys found"
          )
      if len(active_test) > 3:
          issues.append(
              f"⚠️ MANY TEST KEYS: {len(active_test)} active test keys found"
          )

      # Report results
      if issues:
          print("API Key Health Check - Issues Found:")
          for issue in issues:
              print(f"  {issue}")
          return 1
      else:
          print("✅ API Key Health Check - All keys healthy")
          return 0

  if __name__ == "__main__":
      sys.exit(check_api_keys())
  ```

  ```typescript TypeScript theme={null}
  #!/usr/bin/env node
  /**
   * API Key Health Check - Run daily to monitor key status.
   */
  const API_KEY = process.env.DOCINTELL_API_KEY!;
  const BASE_URL = 'https://api.docintell.com';

  async function checkApiKeys(): Promise<number> {
    const response = await fetch(`${BASE_URL}/v1/keys`, {
      headers: { Authorization: `Bearer ${API_KEY}` },
    });

    if (!response.ok) throw new Error(`HTTP ${response.status}`);

    const { keys } = await response.json();
    const issues: string[] = [];

    // Check 1: Unused keys (never used or >30 days)
    const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
    for (const key of keys) {
      if (!key.last_used_at) {
        issues.push(`⚠️ UNUSED: '${key.name}' has never been used`);
      } else {
        const lastUsed = new Date(key.last_used_at);
        if (lastUsed < cutoff) {
          const daysAgo = Math.floor((Date.now() - lastUsed.getTime()) / (24 * 60 * 60 * 1000));
          issues.push(`⚠️ STALE: '${key.name}' not used in ${daysAgo} days`);
        }
      }
    }

    // Check 2: Keys in grace period
    for (const key of keys) {
      if (key.status === 'deprecated') {
        const days = key.grace_period_days_remaining;
        if (days <= 2) {
          issues.push(`🚨 URGENT: '${key.name}' expires in ${days} days!`);
        } else {
          issues.push(`⏳ GRACE PERIOD: '${key.name}' expires in ${days} days`);
        }
      }
    }

    // Check 3: Multiple active keys
    const activeLive = keys.filter(
      (k: any) => k.status === 'active' && k.environment === 'live'
    );
    const activeTest = keys.filter(
      (k: any) => k.status === 'active' && k.environment === 'test'
    );

    if (activeLive.length > 1) {
      issues.push(`⚠️ MULTIPLE LIVE KEYS: ${activeLive.length} active live keys found`);
    }
    if (activeTest.length > 3) {
      issues.push(`⚠️ MANY TEST KEYS: ${activeTest.length} active test keys found`);
    }

    // Report results
    if (issues.length > 0) {
      console.log('API Key Health Check - Issues Found:');
      issues.forEach((issue) => console.log(`  ${issue}`));
      return 1;
    } else {
      console.log('✅ API Key Health Check - All keys healthy');
      return 0;
    }
  }

  checkApiKeys().then((exitCode) => process.exit(exitCode));
  ```

  ```bash Bash Script theme={null}
  #!/bin/bash
  # API Key Health Check - Run daily to monitor key status

  API_KEY="${DOCINTELL_API_KEY}"
  BASE_URL="https://api.docintell.com"

  # Fetch all keys
  response=$(curl -s -X GET "${BASE_URL}/v1/keys" \
    -H "Authorization: Bearer ${API_KEY}")

  # Parse and check for issues
  issues=0

  # Check for deprecated keys
  deprecated_count=$(echo "$response" | jq '[.keys[] | select(.status == "deprecated")] | length')
  if [ "$deprecated_count" -gt 0 ]; then
    echo "⏳ GRACE PERIOD: $deprecated_count keys in rotation"

    # Check if any are expiring soon (< 3 days)
    urgent=$(echo "$response" | jq '[.keys[] | select(.status == "deprecated" and .grace_period_days_remaining < 3)] | length')
    if [ "$urgent" -gt 0 ]; then
      echo "🚨 URGENT: $urgent keys expire in < 3 days!"
      issues=1
    fi
  fi

  # Check for unused keys (last_used_at is null)
  unused=$(echo "$response" | jq '[.keys[] | select(.last_used_at == null)] | length')
  if [ "$unused" -gt 0 ]; then
    echo "⚠️ UNUSED: $unused keys have never been used"
    issues=1
  fi

  # Exit with appropriate code
  if [ $issues -eq 0 ]; then
    echo "✅ API Key Health Check - All keys healthy"
    exit 0
  else
    exit 1
  fi
  ```
</CodeGroup>

**Usage:**

```bash theme={null}
# Set up environment variable
export DOCINTELL_API_KEY="dk_live_YOUR_KEY"

# Run the check
python check_api_keys.py

# Or set up as a cron job (daily at 9 AM)
0 9 * * * /usr/local/bin/check_api_keys.py
```

<Tip>
  Integrate this health check into your monitoring stack:

  * Send alerts to **Slack** or **PagerDuty** when issues are detected
  * Track key usage metrics in **Datadog** or **Prometheus**
  * Run checks before deployments to catch expiring keys early
</Tip>

## Best Practices

### Store Keys Securely

<CardGroup cols={2}>
  <Card title="Environment Variables" icon="terminal">
    Store keys in environment variables, never in code:

    ```bash theme={null}
    export DOCINTELL_API_KEY="dk_live_..."
    ```

    Access in your application:

    ```python theme={null}
    import os
    api_key = os.environ["DOCINTELL_API_KEY"]
    ```
  </Card>

  <Card title="Secrets Managers" icon="lock">
    Use a secrets manager for production:

    * **AWS Secrets Manager**
    * **GCP Secret Manager**
    * **HashiCorp Vault**
    * **Azure Key Vault**
  </Card>
</CardGroup>

<Warning>
  **Never commit API keys to version control.** Add `.env` files to your `.gitignore`:

  ```bash theme={null}
  # .gitignore
  .env
  .env.local
  .env.*.local
  ```
</Warning>

### Use Separate Keys per Environment

| Environment     | Key Type   | Storage         | Purpose                   |
| --------------- | ---------- | --------------- | ------------------------- |
| **Development** | `dk_test_` | `.env.local`    | Local development         |
| **Staging**     | `dk_test_` | Secrets manager | Integration testing       |
| **Production**  | `dk_live_` | Secrets manager | Customer-facing workloads |

<Info>
  Test keys (`dk_test_`) are free and never billed. Use them liberally for development and testing.
</Info>

### Rotate Keys Regularly

<Steps>
  <Step title="Set a Rotation Schedule">
    Rotate keys every **90 days** as a security best practice.

    Set calendar reminders or use automation:

    ```bash theme={null}
    # Example: Rotate keys quarterly
    0 0 1 */3 * /usr/local/bin/rotate-docintell-keys.sh
    ```
  </Step>

  <Step title="Monitor Grace Periods">
    Track upcoming key expirations to avoid service disruptions.

    Check `grace_period_days_remaining` in the API response.
  </Step>

  <Step title="Audit Key Usage">
    Review `last_used_at` timestamps monthly to identify:

    * Unused keys (can be revoked)
    * Keys in use by unknown systems (investigate)
  </Step>
</Steps>

### Minimize Key Scope

<CardGroup cols={2}>
  <Card title="One Key per Service" icon="server">
    Create separate API keys for each service or application.

    This limits the blast radius if one key is compromised.
  </Card>

  <Card title="Descriptive Names" icon="tag">
    Use clear, descriptive names:

    * "Production API Server - us-east-1"
    * "CI/CD Pipeline - GitHub Actions"
    * ~~"My Key"~~
    * ~~"Test"~~
  </Card>
</CardGroup>

### Monitor for Suspicious Activity

Check `last_used_at` timestamps regularly:

```bash theme={null}
# List all keys with usage timestamps
curl -X GET https://api.docintell.com/v1/keys \
  -H "Authorization: Bearer dk_live_YOUR_API_KEY" \
  | jq '.keys[] | {name, last_used_at, status}'
```

If a key shows unexpected usage, revoke it immediately and investigate.

## Error Handling

### Missing Authorization Header

```json theme={null}
{
  "error": "missing_api_key",
  "message": "Authorization header is required"
}
```

**HTTP Status:** `401 Unauthorized`

**Fix:** Include the `Authorization: Bearer` header in all requests.

### Invalid API Key

```json theme={null}
{
  "error": "invalid_api_key",
  "message": "The provided API key is invalid or has been revoked"
}
```

**HTTP Status:** `401 Unauthorized`

**Possible Causes:**

* Key was revoked
* Key expired after grace period
* Typo in the key value
* Wrong environment (using test key on production URL or vice versa)

### Malformed Authorization Header

```json theme={null}
{
  "error": "malformed_auth_header",
  "message": "Authorization header must use Bearer scheme"
}
```

**HTTP Status:** `401 Unauthorized`

**Fix:** Ensure the header format is exactly: `Authorization: Bearer dk_live_...`

## Next Steps

<CardGroup cols={2}>
  <Card title="Authentication Guide" icon="key" href="/authentication">
    Learn how to use your API key in requests
  </Card>

  <Card title="Upload Your First Document" icon="file-arrow-up" href="/guides/first-document">
    Start extracting data from PDFs
  </Card>

  <Card title="Webhook Setup" icon="webhook" href="/guides/webhook-setup">
    Get real-time notifications for processing events
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/guides/error-handling">
    Handle API errors gracefully
  </Card>
</CardGroup>
