Mintplex-Labs/anything-llm · warning

Unauthorized

Error message

Unauthorized

What it means

Deliberate 403 from DELETE /browser-extension/api-keys/:id. In multi-user mode, a non-admin (manager) may only delete keys they own: the code looks the key up by { id, user_id } and a null result means 'no such key owned by you', refusing with Unauthorized. Admins skip the ownership check entirely and can delete any key.

Source

Thrown at server/endpoints/browserExtension.js:209

      }
    }
  );

  app.delete(
    "/browser-extension/api-keys/:id",
    [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
    async (request, response) => {
      try {
        const { id } = request.params;
        const user = await userFromSession(request, response);

        if (multiUserMode(response) && user.role !== ROLES.admin) {
          const apiKey = await BrowserExtensionApiKey.get({
            id: parseInt(id),
            user_id: user?.id,
          });
          if (!apiKey) {
            return response.status(403).json({ error: "Unauthorized" });
          }
        }

        const { success, error } = await BrowserExtensionApiKey.delete(id);
        if (!success) throw new Error(error);
        response.status(200).json({ success: true });
      } catch (error) {
        console.error(error);
        response.status(500).json({ error: "Failed to revoke API key" });
      }
    }
  );
}

module.exports = { browserExtensionEndpoints };

View on GitHub (pinned to 3aec848f28)

Solutions

  1. Refresh GET /browser-extension/api-keys and delete using the numeric id of a key your own user created.
  2. If the key belongs to another user, ask an admin to perform the delete.
  3. Confirm you are sending the numeric id in the URL, not the API key string.
Defensive patterns

Strategy: validation

Validate before calling

const myRole = await currentUserRole();
const keys = await listExtensionApiKeys();
const owned = keys.find((k) => String(k.id) === String(id));
if (myRole !== 'admin' && !owned) throw new Error('This key is not yours to revoke - ask an admin');

Type guard

const isNumericId = (v: unknown): v is string | number =>
  (typeof v === 'string' && /^\d+$/.test(v)) || typeof v === 'number';

Try / catch

try { await revokeKey(id); }
catch (e) {
  if (e.status === 403) { /* refresh the key list; the id is gone or not owned by you */ }
  else throw e;
}

Prevention

When it happens

Trigger: A manager calling DELETE on a key created by another user; the key was already deleted so the scoped lookup misses; passing the wrong id (or the key string instead of the numeric id), which parseInt turns into NaN so no row ever matches.

Common situations: Multi-admin teams with per-user extension keys; stale key list in the admin UI after someone else revoked a key.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of Mintplex-Labs/anything-llm@3aec848f28 (2026-08-18). Data as JSON: /api/errors/480265467db1377f. Report an issue: GitHub.