Skip to main content
DELETE
/
api
/
api-keys
/
[id]
curl -X DELETE https://www.wiseyield.co/api/api-keys/key_abc123xyz789 \
  -H "Authorization: Bearer {CLERK_SESSION_TOKEN}"
{
  "success": true,
  "message": "API key revoked successfully"
}

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.

Endpoint

DELETE https://www.wiseyield.co/api/api-keys/{key_id}
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.

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

key_id
string
required
Unique identifier of the API key to revoke (format: key_{nanoid})

Response

Returns success confirmation.
success
boolean
Always true on successful revocation
message
string
Success message: “API key revoked successfully”
curl -X DELETE https://www.wiseyield.co/api/api-keys/key_abc123xyz789 \
  -H "Authorization: Bearer {CLERK_SESSION_TOKEN}"
{
  "success": true,
  "message": "API key revoked successfully"
}

Errors

401
error
Unauthorized - User not signed in
404
error
Not Found - API key not found, already revoked, or belongs to different user
500
error
Internal Server Error - Failed to revoke API key

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: truefalse
    • 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:
// 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

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

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

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

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

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

Confirm Before Revoke: Always show confirmation dialog before revoking keys.
Create Before Revoke: When rotating, create new key and test it before revoking the old one.
Monitor Usage: Check lastUsedAt to identify safe-to-revoke keys.
Audit Regularly: Review and revoke unused keys quarterly.
Immediate Revocation: Key becomes invalid instantly. All apps using it will lose access.
Cannot Undo: Revocation is permanent. Must create new key to restore access.
No Grace Period: There is no transition period. Applications will fail immediately.

Integration Examples

Revoke Button with Confirmation

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

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

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>
  );
}