medusajs/medusa · error · MedusaError
Cannot delete api keys that are not revoked - ${unrevokedApi
Error message
Cannot delete api keys that are not revoked - ${unrevokedApiKeys.join(
", "
)} What it means
Thrown by the api-key module when deleting API keys that are still active — Medusa requires keys to be revoked before deletion so that in-use keys are never silently removed. The error lists the offending key ids.
Source
Thrown at packages/modules/api-key/src/services/api-key-module-service.ts:101
) {
const apiKeyIds = Array.isArray(ids) ? ids : [ids]
const unrevokedApiKeys = (
await this.apiKeyService_.list(
{
id: ids,
$or: [
{ revoked_at: { $eq: null } },
{ revoked_at: { $gt: new Date() } },
],
},
{ select: ["id"] },
sharedContext
)
).map((apiKey) => apiKey.id)
if (isPresent(unrevokedApiKeys)) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`Cannot delete api keys that are not revoked - ${unrevokedApiKeys.join(
", "
)}`
)
}
return await super.deleteApiKeys(apiKeyIds, sharedContext)
}
//@ts-expect-error
createApiKeys(
data: ApiKeyTypes.CreateApiKeyDTO[],
sharedContext?: Context
): Promise<ApiKeyTypes.ApiKeyDTO[]>
//@ts-expect-error
createApiKeys(
data: ApiKeyTypes.CreateApiKeyDTO,View on GitHub (pinned to 5e06e544a2)
Solutions
- Revoke the key first: POST /admin/api-keys/:id/revoke (revoked_by required), then delete
- For batch deletes, filter out or revoke all unrevoked keys before calling delete
- Keep the two-step revoke→delete flow in key-rotation scripts
Example fix
// before
await apiKeyModuleService.deleteApiKeys(["apk_active"])
// after
await apiKeyModuleService.revokeApiKeys({
data: [{ id: "apk_active", revoked_by: "user_1" }],
})
await apiKeyModuleService.deleteApiKeys(["apk_active"]) Defensive patterns
Strategy: validation
Validate before calling
// check revoked_at before deleting
const keys = await service.listApiKeys({ id: ids }, { select: ['id', 'revoked_at'] })
const unrevoked = keys.filter(k => !k.revoked_at)
for (const k of unrevoked) {
await service.revokeApiKeys({ data: [{ id: k.id, revoked_by: actorId }] })
}
await service.deleteApiKeys(ids) Type guard
const isRevoked = (key: { revoked_at: string | Date | null }): boolean =>
key.revoked_at !== null Try / catch
try {
await service.deleteApiKeys(ids)
} catch (e) {
if (e.type === 'not_allowed' && /not revoked/.test(e.message)) {
// parse listed ids, revoke them, then retry the delete
}
throw e
} Prevention
- Always run revoke before delete in key-lifecycle scripts and treat it as one atomic flow
- Guard deletion UI/actions with a 'revoked' state check
When it happens
Trigger: Calling DELETE /admin/api-keys/:id (or apiKeyModuleService.deleteApiKeys) on a key whose revoked_at is null; batch deletes fail if any key in the list is unrevoked.
Common situations: Cleanup scripts deleting old keys without revoking them first; attempting to delete a publishable key still used by a live storefront.
Related errors
- Sales channels can only be associated with publishable API k
- Campaign with id: ${req.params.id} was not found
- Campaign with id "${req.params.id}" not found
- Claim with id: ${req.params.id} was not found
- Collection with id "${req.params.id}" not found
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/830694a0727ea483.
Report an issue: GitHub.