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

# List API Keys

> List all active API keys for the authenticated user

## Endpoint

```
GET https://www.wiseyield.co/api/api-keys
```

<Note>
  **Dashboard-only endpoint.** Authentication is via the WiseYield browser session (Clerk cookies). It cannot be called from a server-to-server integration today. Use the WiseYield dashboard's **Settings → API Keys** page to manage keys; this page documents the wire format that page consumes.
</Note>

## Authentication

Requires a signed-in WiseYield session (Clerk cookies set by the dashboard). The cURL and Bearer examples below illustrate the wire shape but are not directly callable outside the browser — `fetch('/api/api-keys', { credentials: 'include' })` from the dashboard is how it's actually invoked.

## Response

Returns list of API keys with usage statistics.

<ResponseField name="data" type="array">
  Array of API key objects (sorted by creation date, newest first)

  <Expandable title="API Key Object">
    <ResponseField name="id" type="string">
      Unique identifier (format: `key_{nanoid}`)
    </ResponseField>

    <ResponseField name="name" type="string">
      User-defined name for the API key
    </ResponseField>

    <ResponseField name="keyPrefix" type="string">
      First 16 characters of the key (e.g., `wy_live_abc123de`)
    </ResponseField>

    <ResponseField name="keyPreview" type="string">
      Masked version for display (e.g., `wy_live_abc123de...****`)
    </ResponseField>

    <ResponseField name="environment" type="string">
      Environment type: `live` (production) or `test` (development)
    </ResponseField>

    <ResponseField name="scopes" type="array">
      Array of permission scopes (e.g., `["farms:read", "crops:write"]`)
    </ResponseField>

    <ResponseField name="isActive" type="boolean">
      Whether the key is currently active
    </ResponseField>

    <ResponseField name="lastUsedAt" type="string">
      ISO 8601 timestamp of last usage (null if never used)
    </ResponseField>

    <ResponseField name="usageCount" type="number">
      Total number of API requests made with this key
    </ResponseField>

    <ResponseField name="rateLimit" type="number">
      Maximum requests per hour (1-100,000)
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      ISO 8601 timestamp of creation
    </ResponseField>

    <ResponseField name="updatedAt" type="string">
      ISO 8601 timestamp of last update
    </ResponseField>

    <ResponseField name="expiresAt" type="string">
      ISO 8601 timestamp when key expires (null if no expiration)
    </ResponseField>

    <ResponseField name="metadata" type="object">
      Custom metadata attached to the key
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="total" type="number">
  Total number of active API keys
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X GET https://www.wiseyield.co/api/api-keys \
    -H "Authorization: Bearer {CLERK_SESSION_TOKEN}"
  ```

  ```javascript JavaScript theme={null}
  // Using Clerk session
  const response = await fetch(
    'https://www.wiseyield.co/api/api-keys',
    {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${clerkSessionToken}`
      }
    }
  );

  const { data, total } = await response.json();

  console.log(`You have ${total} active API keys`);
  data.forEach(key => {
    console.log(`${key.name}: ${key.keyPreview}`);
  });
  ```

  ```javascript JavaScript (With Error Handling) theme={null}
  async function listApiKeys() {
    try {
      const response = await fetch('/api/api-keys', {
        method: 'GET',
        headers: {
          'Authorization': `Bearer ${clerkSessionToken}`
        }
      });

      if (!response.ok) {
        const error = await response.json();

        if (response.status === 401) {
          router.push('/sign-in');
          return;
        }

        toast.error(error.error || 'Failed to load API keys');
        return;
      }

      const { data, total } = await response.json();

      return { keys: data, total };
    } catch (error) {
      toast.error('Network error. Please try again.');
    }
  }
  ```

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

  response = requests.get(
      'https://www.wiseyield.co/api/api-keys',
      headers={'Authorization': 'Bearer {CLERK_SESSION_TOKEN}'}
  )

  result = response.json()

  if 'data' in result:
      print(f"Total API keys: {result['total']}")
      for key in result['data']:
          print(f"{key['name']}: {key['keyPreview']}")
  else:
      print(f"Error: {result.get('error')}")
  ```
</RequestExample>

<ResponseExample>
  ```json 200 Success theme={null}
  {
    "data": [
      {
        "id": "key_abc123xyz789",
        "name": "Production API Key",
        "keyPrefix": "wy_live_a1b2c3d4",
        "keyPreview": "wy_live_a1b2c3d4...****",
        "environment": "live",
        "scopes": ["farms:read", "farms:write", "crops:read", "crops:write"],
        "isActive": true,
        "lastUsedAt": "2025-10-29T14:30:00.000Z",
        "usageCount": 1523,
        "rateLimit": 5000,
        "createdAt": "2025-10-01T10:00:00.000Z",
        "updatedAt": "2025-10-29T14:30:00.000Z",
        "expiresAt": null,
        "metadata": {
          "userAgent": "Mozilla/5.0..."
        }
      },
      {
        "id": "key_def456uvw012",
        "name": "Development Testing",
        "keyPrefix": "wy_test_e5f6g7h8",
        "keyPreview": "wy_test_e5f6g7h8...****",
        "environment": "test",
        "scopes": ["all"],
        "isActive": true,
        "lastUsedAt": null,
        "usageCount": 0,
        "rateLimit": 1000,
        "createdAt": "2025-10-15T16:20:00.000Z",
        "updatedAt": "2025-10-15T16:20:00.000Z",
        "expiresAt": "2025-12-31T23:59:59.000Z",
        "metadata": {}
      }
    ],
    "total": 2
  }
  ```

  ```json 200 Success (No Keys) theme={null}
  {
    "data": [],
    "total": 0
  }
  ```

  ```json 401 Unauthorized theme={null}
  {
    "error": "Unauthorized",
    "message": "Authentication required"
  }
  ```

  ```json 500 Internal Server Error theme={null}
  {
    "error": "Internal server error"
  }
  ```
</ResponseExample>

## Errors

<ResponseField name="401" type="error">
  Unauthorized - User not signed in via Clerk
</ResponseField>

<ResponseField name="500" type="error">
  Internal Server Error - Failed to fetch API keys
</ResponseField>

## How It Works

**Query Flow**:

1. **Authentication**: Verifies user is signed in via Clerk
2. **Database Query**: Fetches all API keys where:
   * `userId` matches authenticated user
   * `revokedAt` is `null` (not revoked)
3. **Sorting**: Orders by `createdAt` descending (newest first)
4. **Security Masking**: Masks full keys using `maskApiKey()` function
5. **Response**: Returns array of masked keys with metadata

**What's Hidden**: Full API key value is NEVER returned in list responses. Only the prefix and masked preview are shown.

**What's Shown**: All metadata (name, scopes, usage stats, expiration) is visible.

## API Key Structure

### Key Format

```
wy_{environment}_{48_hex_characters}

Example (Live):
wy_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2

Example (Test):
wy_test_x9y8z7w6v5u4t3s2r1q0p9o8n7m6l5k4j3i2h1g0f9e8
```

**Components**:

* Prefix: `wy_` (WiseYield identifier)
* Environment: `live` or `test`
* Random part: 48 hexadecimal characters (24 random bytes)

### Environments

<ParamField name="live" type="environment">
  **Production Environment**

  * Used for production applications
  * Full access to real data
  * Usage counts toward billing
  * Should be kept secure and never committed to version control
</ParamField>

<ParamField name="test" type="environment">
  **Test Environment**

  * Used for development and testing
  * Isolated from production data
  * No billing impact
  * Can be safely shared with team members
</ParamField>

### Available Scopes

**Farm Management**:

* `farms:read` - View farms
* `farms:write` - Create and update farms

**Crop Management**:

* `crops:read` - View crops
* `crops:write` - Create and update crops

**Field Management**:

* `fields:read` - View fields
* `fields:write` - Create and update fields

**Analytics**:

* `analytics:read` - View analytics and predictions

**Task Management**:

* `tasks:read` - View tasks
* `tasks:write` - Create and update tasks

**Team Management**:

* `team:read` - View team members
* `team:write` - Manage team members

**Webhooks**:

* `webhooks:read` - View webhooks
* `webhooks:write` - Create and update webhooks

**Full Access**:

* `all` - Full access to all resources

## Use Cases

### Display API Keys Table

```javascript theme={null}
function ApiKeysTable() {
  const [keys, setKeys] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function loadKeys() {
      const response = await fetch('/api/api-keys', {
        headers: { 'Authorization': `Bearer ${clerkSessionToken}` }
      });

      const { data } = await response.json();
      setKeys(data);
      setLoading(false);
    }

    loadKeys();
  }, []);

  if (loading) return <div>Loading...</div>;

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Key</th>
          <th>Environment</th>
          <th>Scopes</th>
          <th>Usage</th>
          <th>Created</th>
          <th>Actions</th>
        </tr>
      </thead>
      <tbody>
        {keys.map((key) => (
          <tr key={key.id}>
            <td>{key.name}</td>
            <td>
              <code className="text-sm">{key.keyPreview}</code>
            </td>
            <td>
              <Badge color={key.environment === 'live' ? 'green' : 'blue'}>
                {key.environment}
              </Badge>
            </td>
            <td>
              <div className="flex flex-wrap gap-1">
                {key.scopes.map((scope) => (
                  <Badge key={scope} variant="outline" size="sm">
                    {scope}
                  </Badge>
                ))}
              </div>
            </td>
            <td>
              <div className="text-sm">
                <div>{key.usageCount.toLocaleString()} requests</div>
                <div className="text-gray-500">
                  {key.rateLimit.toLocaleString()}/hour limit
                </div>
              </div>
            </td>
            <td>
              {new Date(key.createdAt).toLocaleDateString()}
            </td>
            <td>
              <button onClick={() => revokeKey(key.id)}>
                Revoke
              </button>
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}
```

### Check Key Usage

```javascript theme={null}
async function checkKeyUsage(keyId) {
  const response = await fetch('/api/api-keys');
  const { data } = await response.json();

  const key = data.find(k => k.id === keyId);

  if (!key) {
    console.log('Key not found or revoked');
    return;
  }

  const percentUsed = (key.usageCount / key.rateLimit) * 100;

  console.log(`Key: ${key.name}`);
  console.log(`Usage: ${key.usageCount} / ${key.rateLimit} (${percentUsed.toFixed(1)}%)`);
  console.log(`Last used: ${key.lastUsedAt || 'Never'}`);

  if (percentUsed > 80) {
    console.warn('⚠️ Approaching rate limit!');
  }
}
```

### Filter by Environment

```javascript theme={null}
async function getProductionKeys() {
  const response = await fetch('/api/api-keys');
  const { data } = await response.json();

  const liveKeys = data.filter(key => key.environment === 'live');
  const testKeys = data.filter(key => key.environment === 'test');

  return { liveKeys, testKeys };
}
```

### Check for Expiring Keys

```javascript theme={null}
async function checkExpiringKeys() {
  const response = await fetch('/api/api-keys');
  const { data } = await response.json();

  const now = new Date();
  const thirtyDaysFromNow = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);

  const expiringSoon = data.filter(key => {
    if (!key.expiresAt) return false;

    const expiresAt = new Date(key.expiresAt);
    return expiresAt < thirtyDaysFromNow && expiresAt > now;
  });

  if (expiringSoon.length > 0) {
    console.warn(`${expiringSoon.length} key(s) expiring within 30 days:`);
    expiringSoon.forEach(key => {
      console.log(`- ${key.name}: expires ${new Date(key.expiresAt).toLocaleDateString()}`);
    });
  }
}
```

## Best Practices

<Tip>
  **Regular Audits**: Review API keys list monthly to remove unused keys.
</Tip>

<Tip>
  **Monitor Usage**: Check `usageCount` and `lastUsedAt` to identify inactive keys.
</Tip>

<Tip>
  **Environment Separation**: Use `test` keys for development, `live` keys only in production.
</Tip>

<Tip>
  **Scope Principle**: Grant minimum scopes needed for each use case.
</Tip>

<Warning>
  **Full Keys Never Shown**: The complete API key is only shown once during creation. Save it securely.
</Warning>

<Warning>
  **Revoked Keys Excluded**: This endpoint only returns active keys (revokedAt = null).
</Warning>

<Warning>
  **10 Key Limit**: Each user can have maximum 10 active API keys. Revoke unused ones.
</Warning>

## Security Considerations

**Key Storage**:

* Only SHA-256 hash stored in database
* Prefix stored for identification (first 16 chars)
* Full key never retrievable after creation

**Display Safety**:

* Keys masked in UI: `wy_live_abc123de...****`
* Prevents accidental exposure in screenshots
* Safe to display in logs and monitoring

**Scope Isolation**:

* Keys cannot access resources outside their scopes
* `all` scope grants full access (use cautiously)
* Scopes validated on every API request

## Related Endpoints

* [Create API Key](/api-reference/api-keys/create-api-key) - POST to generate new API key
* [Revoke API Key](/api-reference/api-keys/revoke-api-key) - DELETE to revoke existing key
