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

# Revoke API Key

> Revoke (soft delete) an API key to immediately disable access

## Endpoint

```
DELETE https://www.wiseyield.co/api/api-keys/{key_id}
```

<Note>
  **Dashboard-only endpoint.** Called from the WiseYield UI with browser cookies (Clerk session). Not callable from a server-to-server integration today. Revoke keys from **Settings → API Keys** in the [dashboard](https://www.wiseyield.co/dashboard).
</Note>

## Authentication

Requires a signed-in WiseYield session. The `Bearer` examples below show the response wire format but are not directly callable outside the browser session.

## Path Parameters

<ParamField path="key_id" type="string" required>
  Unique identifier of the API key to revoke (format: `key_{nanoid}`)
</ParamField>

## Response

Returns success confirmation.

<ResponseField name="success" type="boolean">
  Always `true` on successful revocation
</ResponseField>

<ResponseField name="message" type="string">
  Success message: "API key revoked successfully"
</ResponseField>

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

  ```javascript JavaScript theme={null}
  const keyId = 'key_abc123xyz789';

  const response = await fetch(
    `https://www.wiseyield.co/api/api-keys/${keyId}`,
    {
      method: 'DELETE',
      headers: {
        'Authorization': `Bearer ${clerkSessionToken}`
      }
    }
  );

  const result = await response.json();

  if (result.success) {
    console.log('API key revoked successfully');
  }
  ```

  ```javascript JavaScript (With Confirmation) theme={null}
  async function revokeApiKey(keyId, keyName) {
    const confirmed = await confirmDialog({
      title: 'Revoke API Key?',
      message: `Are you sure you want to revoke "${keyName}"? This action cannot be undone. All applications using this key will lose access immediately.`,
      confirmText: 'Yes, Revoke',
      confirmButtonClass: 'btn-danger',
    });

    if (!confirmed) return;

    try {
      const response = await fetch(`/api/api-keys/${keyId}`, {
        method: 'DELETE',
        headers: { 'Authorization': `Bearer ${clerkSessionToken}` }
      });

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

        if (response.status === 404) {
          toast.error('API key not found or already revoked');
          return;
        }

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

        toast.error(error.message || 'Failed to revoke API key');
        return;
      }

      toast.success('API key revoked successfully');

      // Refresh key list
      await refreshApiKeys();
    } catch (error) {
      toast.error('Network error. Please try again.');
    }
  }
  ```

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

  key_id = 'key_abc123xyz789'

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

  result = response.json()

  if result.get('success'):
      print('API key revoked successfully')
  else:
      print(f"Error: {result.get('error')}")
  ```
</RequestExample>

<ResponseExample>
  ```json 200 Success theme={null}
  {
    "success": true,
    "message": "API key revoked successfully"
  }
  ```

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

  ```json 404 Not Found theme={null}
  {
    "error": "Not found",
    "message": "API key not found or already revoked"
  }
  ```

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

## Errors

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

<ResponseField name="404" type="error">
  Not Found - API key not found, already revoked, or belongs to different user
</ResponseField>

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

## How It Works

**Revocation Flow**:

1. **Authentication**: Verifies user is signed in
2. **Ownership Check**: Finds API key where:
   * `id` matches provided key\_id
   * `userId` matches authenticated user
   * `revokedAt` is `null` (not already revoked)
3. **Soft Delete**: Updates API key:
   * `revokedAt`: `null` → current timestamp
   * `isActive`: `true` → `false`
   * `updatedAt`: current timestamp
4. **Audit Log**: Records revocation for security tracking
5. **Immediate Effect**: Key becomes invalid for all API requests

**Why Soft Delete?**: Maintains audit trail while preventing further use. Revoked keys stay in database for historical tracking but can never be reactivated.

**Post-Revocation**:

* Key will not authenticate any API requests
* Key excluded from list endpoints (filters by `revokedAt IS NULL`)
* Cannot be restored (must create new key)
* Historical usage data preserved

## Security Implications

### Immediate Access Revocation

**What Happens Instantly**:

* ✅ All active API requests using this key will fail with 401 Unauthorized
* ✅ Key removed from active keys list
* ✅ Rate limit tracking stops
* ✅ Usage tracking stops

**What's Preserved**:

* ✅ Historical usage data (`usageCount`, `lastUsedAt`)
* ✅ Audit logs showing when key was created and revoked
* ✅ Metadata and configuration

### Impact on Applications

**Applications Using Revoked Key**:

```javascript theme={null}
// After revocation, this will fail
const response = await fetch('https://www.wiseyield.co/api/farms', {
  headers: { 'Authorization': 'Bearer wy_live_revoked_key' }
});

// Response: 401 Unauthorized
{
  "error": "Invalid API key"
}
```

**Recommended Actions**:

1. Update application with new API key
2. Monitor error logs for failed requests
3. Notify team members using the key

## Use Cases

### Revoke Compromised Key

```javascript theme={null}
async function revokeCompromisedKey(keyId) {
  // Immediately revoke
  await fetch(`/api/api-keys/${keyId}`, {
    method: 'DELETE',
    headers: { 'Authorization': `Bearer ${clerkSessionToken}` }
  });

  // Create replacement key
  const newKey = await fetch('/api/api-keys', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${clerkSessionToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'Production Server (Replacement)',
      environment: 'live',
      scopes: [...], // Same scopes as revoked key
      rateLimit: 10000
    })
  }).then(r => r.json());

  // Alert: Key revoked, update app config
  console.log('Old key revoked, new key:', newKey.key);

  return newKey;
}
```

### Revoke Unused Keys

```javascript theme={null}
async function revokeUnusedKeys(daysInactive = 90) {
  // Get all keys
  const { data: keys } = await fetch('/api/api-keys').then(r => r.json());

  const cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - daysInactive);

  // Find keys not used in X days
  const unusedKeys = keys.filter(key => {
    if (!key.lastUsedAt) return true; // Never used

    const lastUsed = new Date(key.lastUsedAt);
    return lastUsed < cutoffDate;
  });

  console.log(`Found ${unusedKeys.length} unused keys`);

  // Revoke each
  for (const key of unusedKeys) {
    console.log(`Revoking: ${key.name}`);

    await fetch(`/api/api-keys/${key.id}`, {
      method: 'DELETE',
      headers: { 'Authorization': `Bearer ${clerkSessionToken}` }
    });
  }

  console.log('Cleanup complete');
}
```

### Revoke Expired Keys

```javascript theme={null}
async function revokeExpiredKeys() {
  const { data: keys } = await fetch('/api/api-keys').then(r => r.json());

  const now = new Date();
  const expiredKeys = keys.filter(key => {
    if (!key.expiresAt) return false;

    return new Date(key.expiresAt) < now;
  });

  console.log(`Found ${expiredKeys.length} expired keys`);

  for (const key of expiredKeys) {
    await fetch(`/api/api-keys/${key.id}`, {
      method: 'DELETE',
      headers: { 'Authorization': `Bearer ${clerkSessionToken}` }
    });

    console.log(`Revoked expired key: ${key.name}`);
  }
}
```

### Rotate API Key

```javascript theme={null}
async function rotateApiKey(oldKeyId) {
  // Get old key details
  const { data: keys } = await fetch('/api/api-keys').then(r => r.json());
  const oldKey = keys.find(k => k.id === oldKeyId);

  if (!oldKey) {
    throw new Error('Key not found');
  }

  // Create new key with same configuration
  const newKeyResponse = await fetch('/api/api-keys', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${clerkSessionToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: `${oldKey.name} (Rotated)`,
      environment: oldKey.environment,
      scopes: oldKey.scopes,
      rateLimit: oldKey.rateLimit,
      metadata: {
        ...oldKey.metadata,
        rotatedFrom: oldKeyId,
        rotatedAt: new Date().toISOString()
      }
    })
  });

  const newKey = await newKeyResponse.json();

  console.log('New key created:', newKey.keyPreview);
  console.log('Full key (save now):', newKey.key);

  // Update application configuration
  await updateEnvVar('WISEYIELD_API_KEY', newKey.key);

  // Test new key
  const testResponse = await fetch('https://www.wiseyield.co/api/farms', {
    headers: { 'Authorization': `Bearer ${newKey.key}` }
  });

  if (!testResponse.ok) {
    throw new Error('New key failed validation');
  }

  // Revoke old key
  await fetch(`/api/api-keys/${oldKeyId}`, {
    method: 'DELETE',
    headers: { 'Authorization': `Bearer ${clerkSessionToken}` }
  });

  console.log('Old key revoked');
  console.log('Rotation complete');

  return newKey;
}
```

### Bulk Revoke Test Keys

```javascript theme={null}
async function revokeAllTestKeys() {
  const { data: keys } = await fetch('/api/api-keys').then(r => r.json());

  const testKeys = keys.filter(key => key.environment === 'test');

  console.log(`Revoking ${testKeys.length} test keys`);

  const results = await Promise.allSettled(
    testKeys.map(key =>
      fetch(`/api/api-keys/${key.id}`, {
        method: 'DELETE',
        headers: { 'Authorization': `Bearer ${clerkSessionToken}` }
      })
    )
  );

  const succeeded = results.filter(r => r.status === 'fulfilled').length;

  console.log(`Revoked ${succeeded}/${testKeys.length} test keys`);
}
```

## Best Practices

<Tip>
  **Confirm Before Revoke**: Always show confirmation dialog before revoking keys.
</Tip>

<Tip>
  **Create Before Revoke**: When rotating, create new key and test it before revoking the old one.
</Tip>

<Tip>
  **Monitor Usage**: Check `lastUsedAt` to identify safe-to-revoke keys.
</Tip>

<Tip>
  **Audit Regularly**: Review and revoke unused keys quarterly.
</Tip>

<Warning>
  **Immediate Revocation**: Key becomes invalid instantly. All apps using it will lose access.
</Warning>

<Warning>
  **Cannot Undo**: Revocation is permanent. Must create new key to restore access.
</Warning>

<Warning>
  **No Grace Period**: There is no transition period. Applications will fail immediately.
</Warning>

## Integration Examples

### Revoke Button with Confirmation

```javascript theme={null}
function RevokeApiKeyButton({ apiKey }) {
  const [revoking, setRevoking] = useState(false);

  async function handleRevoke() {
    const confirmed = await confirmDialog({
      title: 'Revoke API Key?',
      message: (
        <div>
          <p>Are you sure you want to revoke <strong>{apiKey.name}</strong>?</p>
          <p className="text-red-600 mt-2">
            This action cannot be undone. All applications using this key will immediately lose access.
          </p>
          {apiKey.lastUsedAt && (
            <p className="text-sm text-gray-600 mt-2">
              Last used: {formatDistanceToNow(new Date(apiKey.lastUsedAt))} ago
            </p>
          )}
        </div>
      ),
      confirmText: 'Yes, Revoke',
      confirmButtonClass: 'btn-danger',
    });

    if (!confirmed) return;

    setRevoking(true);

    try {
      const response = await fetch(`/api/api-keys/${apiKey.id}`, {
        method: 'DELETE',
        headers: { 'Authorization': `Bearer ${clerkSessionToken}` }
      });

      if (!response.ok) {
        const error = await response.json();
        toast.error(error.message);
        return;
      }

      toast.success('API key revoked');

      // Remove from UI
      onRevoked?.(apiKey.id);
    } finally {
      setRevoking(false);
    }
  }

  return (
    <button
      onClick={handleRevoke}
      disabled={revoking}
      className="btn btn-danger-outline btn-sm"
    >
      {revoking ? 'Revoking...' : 'Revoke'}
    </button>
  );
}
```

### API Keys Management Table

```javascript theme={null}
function ApiKeysManagementTable() {
  const [keys, setKeys] = useState([]);

  async function handleRevoke(keyId) {
    const response = await fetch(`/api/api-keys/${keyId}`, {
      method: 'DELETE',
      headers: { 'Authorization': `Bearer ${clerkSessionToken}` }
    });

    if (response.ok) {
      toast.success('Key revoked');
      setKeys(prev => prev.filter(k => k.id !== keyId));
    }
  }

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Key</th>
          <th>Environment</th>
          <th>Usage</th>
          <th>Last Used</th>
          <th>Actions</th>
        </tr>
      </thead>
      <tbody>
        {keys.map((key) => (
          <tr key={key.id}>
            <td>{key.name}</td>
            <td><code>{key.keyPreview}</code></td>
            <td>
              <Badge color={key.environment === 'live' ? 'green' : 'blue'}>
                {key.environment}
              </Badge>
            </td>
            <td>{key.usageCount.toLocaleString()}</td>
            <td>
              {key.lastUsedAt
                ? formatDistanceToNow(new Date(key.lastUsedAt), { addSuffix: true })
                : 'Never'}
            </td>
            <td>
              <RevokeApiKeyButton
                apiKey={key}
                onRevoked={() => handleRevoke(key.id)}
              />
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}
```

### Cleanup Unused Keys Alert

```javascript theme={null}
function UnusedKeysAlert() {
  const [unusedKeys, setUnusedKeys] = useState([]);

  useEffect(() => {
    async function checkUnused() {
      const { data: keys } = await fetch('/api/api-keys').then(r => r.json());

      const sixtyDaysAgo = new Date();
      sixtyDaysAgo.setDate(sixtyDaysAgo.getDate() - 60);

      const unused = keys.filter(key => {
        if (!key.lastUsedAt) return true;
        return new Date(key.lastUsedAt) < sixtyDaysAgo;
      });

      setUnusedKeys(unused);
    }

    checkUnused();
  }, []);

  async function revokeAll() {
    const confirmed = confirm(`Revoke ${unusedKeys.length} unused keys?`);
    if (!confirmed) return;

    for (const key of unusedKeys) {
      await fetch(`/api/api-keys/${key.id}`, {
        method: 'DELETE',
        headers: { 'Authorization': `Bearer ${clerkSessionToken}` }
      });
    }

    toast.success(`Revoked ${unusedKeys.length} keys`);
    setUnusedKeys([]);
  }

  if (unusedKeys.length === 0) return null;

  return (
    <Alert severity="info">
      <AlertTitle>Unused API Keys</AlertTitle>
      <p>You have {unusedKeys.length} API key(s) that haven't been used in 60+ days.</p>
      <button onClick={revokeAll} className="btn btn-sm mt-2">
        Revoke All Unused Keys
      </button>
    </Alert>
  );
}
```

## Related Endpoints

* [List API Keys](/api-reference/api-keys/list-api-keys) - GET to list all API keys
* [Create API Key](/api-reference/api-keys/create-api-key) - POST to generate new API key
