FlowiseAI/Flowise · error · Error
Microsoft Graph API error: ${response.status} ${response.sta
Error message
Microsoft Graph API error: ${response.status} ${response.statusText} - ${errorText} What it means
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.
Source
Thrown at packages/components/nodes/tools/MicrosoftTeams/core.ts:41
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
const config: RequestInit = {
method,
headers
}
if (body && (method === 'POST' || method === 'PATCH')) {
config.body = JSON.stringify(body)
}
try {
const response = await fetch(`${BASE_URL}${endpoint}`, config)
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Microsoft Graph API error: ${response.status} ${response.statusText} - ${errorText}`)
}
// Handle empty responses for DELETE operations
if (method === 'DELETE' || response.status === 204) {
return { success: true, message: 'Operation completed successfully' }
}
return await response.json()
} catch (error) {
throw new Error(`Microsoft Graph request failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}
// Base Teams Tool class
abstract class BaseTeamsTool extends DynamicStructuredTool {
accessToken = ''
protected defaultParams: any
View on GitHub (pinned to abe4a8601a)
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.
Example fix
// before
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Microsoft Graph API error: ${response.status} ${response.statusText} - ${errorText}`)
}
// after — surface retry-after and parse OData error code
if (!response.ok) {
const errorText = await response.text()
let odata: { error?: { code?: string; message?: string } } = {}
try { odata = JSON.parse(errorText) } catch {}
const err = new GraphApiError(
response.status,
odata.error?.code ?? 'Unknown',
odata.error?.message ?? errorText,
response.headers.get('Retry-After')
)
throw err
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before calling, sanity-check the token and endpoint shape
function preflight(token: string, endpoint: string) {
if (!token || token.split('.').length !== 3) {
throw new Error('Invalid JWT shape — refresh the access token')
}
if (!endpoint.startsWith('/')) {
throw new Error('endpoint must start with /')
}
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString())
if (payload.exp && Date.now() >= payload.exp * 1000) {
throw new Error(`Token expired at ${new Date(payload.exp * 1000).toISOString()}`)
}
} Type guard
function isGraphError(e: unknown): e is Error & { status?: number } {
return e instanceof Error && /Microsoft Graph API error: (\d{3})/.test(e.message)
}
function graphStatus(e: unknown): number | undefined {
if (!isGraphError(e)) return undefined
const m = e.message.match(/Microsoft Graph API error: (\d{3})/)
return m ? Number(m[1]) : undefined
} Try / catch
try {
await tool.invoke(input)
} catch (e) {
const status = graphStatus(e)
if (status === 401) { await refreshToken(); /* retry once */ }
else if (status === 429) { await sleep(parseRetryAfter(e)); /* retry */ }
else if (status && status >= 500) { /* transient — retry with backoff */ }
else throw e
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- HTTP Error ${res.status}: ${res.statusText}
- ${errorMessage}
- HTTP error! status: ${response.status}
- Failed to fetch ${url}: ${error}
- Failed to post ${url}: ${error}
AI-assisted analysis of FlowiseAI/Flowise@abe4a8601a (2026-08-12).
Data as JSON: /api/errors/2fe3049cdbbe47f8.
Report an issue: GitHub.