{"record":{"id":"2fe3049cdbbe47f8","repo":"FlowiseAI/Flowise","slug":"microsoft-graph-api-error-response-status-re","errorCode":null,"errorMessage":"Microsoft Graph API error: ${response.status} ${response.statusText} - ${errorText}","messagePattern":"Microsoft Graph API error: (.+?) (.+?) - (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/components/nodes/tools/MicrosoftTeams/core.ts","lineNumber":41,"sourceCode":"        Authorization: `Bearer ${accessToken}`,\n        'Content-Type': 'application/json'\n    }\n\n    const config: RequestInit = {\n        method,\n        headers\n    }\n\n    if (body && (method === 'POST' || method === 'PATCH')) {\n        config.body = JSON.stringify(body)\n    }\n\n    try {\n        const response = await fetch(`${BASE_URL}${endpoint}`, config)\n\n        if (!response.ok) {\n            const errorText = await response.text()\n            throw new Error(`Microsoft Graph API error: ${response.status} ${response.statusText} - ${errorText}`)\n        }\n\n        // Handle empty responses for DELETE operations\n        if (method === 'DELETE' || response.status === 204) {\n            return { success: true, message: 'Operation completed successfully' }\n        }\n\n        return await response.json()\n    } catch (error) {\n        throw new Error(`Microsoft Graph request failed: ${error instanceof Error ? error.message : 'Unknown error'}`)\n    }\n}\n\n// Base Teams Tool class\nabstract class BaseTeamsTool extends DynamicStructuredTool {\n    accessToken = ''\n    protected defaultParams: any\n","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/FlowiseAI/Flowise/blob/abe4a8601a058047b350c260676826e21dd14101/packages/components/nodes/tools/MicrosoftTeams/core.ts#L23-L59","documentation":"Thrown by the internal makeGraphRequest helper when the Microsoft Graph endpoint returns a non-2xx HTTP status. The message embeds response.status, response.statusText, and the raw response body so the caller can see Graph's OData error payload. It is the primary signal that the request reached Graph but was rejected (auth, permissions, throttling, not-found, validation). This thrown Error is then caught and re-wrapped by the outer try/catch at core.ts:50, becoming error 441.","triggerScenarios":"Graph returns 401 when the Bearer accessToken is expired/missing/malformed; 403 when the granted OAuth scope lacks Team/Chat.ReadBasic.All or ChannelMessage.Send; 404 when teamId/channelId/chatId do not exist or are not tenant-resolvable; 429 with Retry-After when Graph throttling kicks in; 400 when the body fails Graph validation (e.g. invalid membershipType, malformed user@odata.bind).","commonSituations":"Access token minted with the wrong client/tenant; delegated permissions used without a signed-in user; app-only calls against endpoints that require delegated access; stale token cached beyond its 1h lifetime; wrong environment (sovereign cloud) hitting the public graph.microsoft.com BASE_URL; Cross-Cloud throttling during bulk channel operations.","solutions":["Inspect the embedded status code and errorText first — they tell you which of auth/permission/notfound/throttle applies.","For 401: refresh the access token via the oauth2 v2.0 token endpoint and confirm client_id/client_secret/certificate are correct.","For 403: in Azure AD app registration, add the required delegated or application permissions (e.g. Channel.ReadBasic.All, Chat.ReadWrite) and have an admin grant admin consent.","For 429: read the Retry-After header (lost by this wrapper — fix the wrapper) and back off; reduce concurrency to stay under Graph's per-app 10000 req/10s budget.","For 404/400: log the exact endpoint+body and validate IDs came from a prior Graph listing rather than user input.","Fix the wrapper at core.ts:39 to surface response.headers and status separately instead of string-mashing them into one Error."],"exampleFix":"// before\nif (!response.ok) {\n    const errorText = await response.text()\n    throw new Error(`Microsoft Graph API error: ${response.status} ${response.statusText} - ${errorText}`)\n}\n\n// after — surface retry-after and parse OData error code\nif (!response.ok) {\n    const errorText = await response.text()\n    let odata: { error?: { code?: string; message?: string } } = {}\n    try { odata = JSON.parse(errorText) } catch {}\n    const err = new GraphApiError(\n        response.status,\n        odata.error?.code ?? 'Unknown',\n        odata.error?.message ?? errorText,\n        response.headers.get('Retry-After')\n    )\n    throw err\n}","handlingStrategy":"try-catch","validationCode":"// Before calling, sanity-check the token and endpoint shape\nfunction preflight(token: string, endpoint: string) {\n  if (!token || token.split('.').length !== 3) {\n    throw new Error('Invalid JWT shape — refresh the access token')\n  }\n  if (!endpoint.startsWith('/')) {\n    throw new Error('endpoint must start with /')\n  }\n  const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString())\n  if (payload.exp && Date.now() >= payload.exp * 1000) {\n    throw new Error(`Token expired at ${new Date(payload.exp * 1000).toISOString()}`)\n  }\n}","typeGuard":"function isGraphError(e: unknown): e is Error & { status?: number } {\n  return e instanceof Error && /Microsoft Graph API error: (\\d{3})/.test(e.message)\n}\n\nfunction graphStatus(e: unknown): number | undefined {\n  if (!isGraphError(e)) return undefined\n  const m = e.message.match(/Microsoft Graph API error: (\\d{3})/)\n  return m ? Number(m[1]) : undefined\n}","tryCatchPattern":"try {\n  await tool.invoke(input)\n} catch (e) {\n  const status = graphStatus(e)\n  if (status === 401) { await refreshToken(); /* retry once */ }\n  else if (status === 429) { await sleep(parseRetryAfter(e)); /* retry */ }\n  else if (status && status >= 500) { /* transient — retry with backoff */ }\n  else throw e\n}","preventionTips":["Refresh tokens proactively before expiry, not reactively after a 401.","Request only the Graph scopes you need; admin-consent them in Azure AD.","Log status + Retry-After + x-ms-ags-diagnostic header for every non-2xx.","Use the SDK's built-in retry handler or @microsoft/microsoft-graph-client which parses 429 for you.","Avoid polling loops that can trip Graph throttling — use webhooks/change notifications."],"tags":["microsoft-graph","http","authentication","network","api-error"],"backgroundTag":null,"analyzedSha":"abe4a8601a058047b350c260676826e21dd14101","analyzedAt":"2026-08-12T16:04:40.823Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}