curl -X DELETE https://www.wiseyield.co/api/api-keys/key_abc123xyz789 \
-H "Authorization: Bearer {CLERK_SESSION_TOKEN}"
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');
}
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.');
}
}
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')}")
{
"success": true,
"message": "API key revoked successfully"
}
{
"error": "Unauthorized",
"message": "Authentication required"
}
{
"error": "Not found",
"message": "API key not found or already revoked"
}
{
"error": "Internal server error"
}
API Keys
Revoke API Key
Revoke (soft delete) an API key to immediately disable access
DELETE
/
api
/
api-keys
/
[id]
curl -X DELETE https://www.wiseyield.co/api/api-keys/key_abc123xyz789 \
-H "Authorization: Bearer {CLERK_SESSION_TOKEN}"
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');
}
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.');
}
}
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')}")
{
"success": true,
"message": "API key revoked successfully"
}
{
"error": "Unauthorized",
"message": "Authentication required"
}
{
"error": "Not found",
"message": "API key not found or already revoked"
}
{
"error": "Internal server error"
}
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. TheBearer examples below show the response wire format but are not directly callable outside the browser session.
Path Parameters
string
required
Unique identifier of the API key to revoke (format:
key_{nanoid})Response
Returns success confirmation.boolean
Always
true on successful revocationstring
Success message: “API key revoked successfully”
curl -X DELETE https://www.wiseyield.co/api/api-keys/key_abc123xyz789 \
-H "Authorization: Bearer {CLERK_SESSION_TOKEN}"
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');
}
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.');
}
}
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')}")
{
"success": true,
"message": "API key revoked successfully"
}
{
"error": "Unauthorized",
"message": "Authentication required"
}
{
"error": "Not found",
"message": "API key not found or already revoked"
}
{
"error": "Internal server error"
}
Errors
error
Unauthorized - User not signed in
error
Not Found - API key not found, already revoked, or belongs to different user
error
Internal Server Error - Failed to revoke API key
How It Works
Revocation Flow:- Authentication: Verifies user is signed in
- Ownership Check: Finds API key where:
idmatches provided key_iduserIdmatches authenticated userrevokedAtisnull(not already revoked)
- Soft Delete: Updates API key:
revokedAt:null→ current timestampisActive:true→falseupdatedAt: current timestamp
- Audit Log: Records revocation for security tracking
- Immediate Effect: Key becomes invalid for all API requests
- 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
- ✅ 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"
}
- Update application with new API key
- Monitor error logs for failed requests
- 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>
);
}
Related Endpoints
- List API Keys - GET to list all API keys
- Create API Key - POST to generate new API key