{"record":{"id":"728b16d6347fcd2a","repo":"supabase/supabase","slug":"method-method-not-allowed-728b16","errorCode":null,"errorMessage":"Method ${method} Not Allowed","messagePattern":"Method (.+?) Not Allowed","errorType":"http","errorClass":null,"httpStatus":405,"severity":"warning","filePath":"apps/studio/pages/api/platform/auth/[ref]/users/[id]/factors.ts","lineNumber":16,"sourceCode":"import { NextApiRequest, NextApiResponse } from 'next'\n\nimport { apiWrapper } from '@/lib/api/apiWrapper'\nimport { selfHostedSupabaseAdmin as supabase } from '@/lib/api/self-hosted-admin'\n\nexport default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)\n\nasync function handler(req: NextApiRequest, res: NextApiResponse) {\n  const { method } = req\n\n  switch (method) {\n    case 'DELETE':\n      return handleDelete(req, res)\n    default:\n      res.setHeader('Allow', ['DELETE'])\n      res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } })\n  }\n}\n\nconst handleDelete = async (req: NextApiRequest, res: NextApiResponse) => {\n  const { id } = req.query\n\n  // Get all factors for the user\n  const { data: factors, error } = await supabase.auth.admin.mfa.listFactors({\n    userId: id as string,\n  })\n  if (error) {\n    return res.status(400).json({ error: { message: error.message } })\n  }\n\n  factors?.factors.forEach(async (factor: any) => {\n    const { error } = await supabase.auth.admin.mfa.deleteFactor({\n      id: factor.id,\n      userId: id as string,","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/supabase/supabase/blob/beee91b9c2228dd57302dec75c733baaa84ab543/apps/studio/pages/api/platform/auth/[ref]/users/[id]/factors.ts#L1-L34","documentation":"HTTP 405 from the MFA-factors admin route. Only DELETE is handled (it lists and removes all MFA factors for a user via selfHostedSupabaseAdmin.auth.admin.mfa); all other methods return `{ data: null, error: { message: 'Method ${method} Not Allowed' } }` with Allow: DELETE. A 405 here means the wrong verb reached an admin-only destructive endpoint.","triggerScenarios":"Calling /api/platform/auth/[ref]/users/[id]/factors with GET (expecting to list factors), POST (creating), or PUT/PATCH; or a fetch missing `method: 'DELETE'`.","commonSituations":"Assuming the endpoint lists factors (it does not — it only deletes); a UI 'Remove MFA factors' button that drops the method and sends GET; integration tests reusing a GET helper.","solutions":["Send DELETE: `fetch(`/api/platform/auth/${ref}/users/${id}/factors`, { method: 'DELETE' })`.","If you needed to list factors, hit the pg/supabase admin list endpoint directly, not this route.","Verify the calling code is not falling back to a default GET helper.","Only add a non-DELETE case if the route is intended to support listing/creating."],"exampleFix":"// before\nawait fetch(`/api/platform/auth/${ref}/users/${id}/factors`)\n\n// after\nawait fetch(`/api/platform/auth/${ref}/users/${id}/factors`, { method: 'DELETE' })","handlingStrategy":"validation","validationCode":"if (method.toUpperCase() !== 'DELETE') {\n  throw new Error('factors route only supports DELETE')\n}","typeGuard":"type FactorMethod = 'DELETE'\nfunction isFactorMethod(m: string): m is FactorMethod {\n  return m.toUpperCase() === 'DELETE'\n}","tryCatchPattern":"const res = await fetch(url, { method: 'DELETE' })\nif (res.status === 405) {\n  throw new Error('Use DELETE to remove MFA factors; this route does not list/create')\n}","preventionTips":["Remember this route only deletes — list factors elsewhere.","Set method explicitly on destructive calls.","Drive the call through a typed mutation in data/fetchers.ts."],"tags":["http-405","nextjs-api-route","supabase-auth","mfa","method-mismatch"],"backgroundTag":null,"analyzedSha":"beee91b9c2228dd57302dec75c733baaa84ab543","analyzedAt":"2026-08-12T06:51:48.935Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}