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

  1. Revoke the key first: POST /admin/api-keys/:id/revoke (revoked_by required), then delete
  2. For batch deletes, filter out or revoke all unrevoked keys before calling delete
  3. 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

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


AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27). Data as JSON: /api/errors/830694a0727ea483. Report an issue: GitHub.