Budibase/budibase · error · Error
Teams Graph API ${resp.status}: ${await resp.text()}
Error message
Teams Graph API ${resp.status}: ${await resp.text()} What it means
graphGet is the generic wrapper for Microsoft Graph GET calls (used by listTeamsChannels to list teams and channels). Any non-ok response from Graph is rethrown as this error containing the HTTP status and the Graph error body. The token used is an app-only Graph token obtained with the https://graph.microsoft.com/.default scope.
Source
Thrown at packages/server/src/escalation/notifications/ms-teams.ts:95
)
}
const data = (await resp.json()) as {
access_token: string
expires_in?: number
}
return { value: data.access_token, ttl: data.expires_in ?? 3600 }
}
)
}
const GRAPH_BASE = "https://graph.microsoft.com/v1.0"
const graphGet = async <T>(url: string, token: string): Promise<T> => {
const resp = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
})
if (!resp.ok) {
throw new Error(`Teams Graph API ${resp.status}: ${await resp.text()}`)
}
return (await resp.json()) as T
}
// Opaque base64url Graph nextLink. Validate origin + exact pathname so the
// Graph token can only ever be sent to the teams collection.
const decodeTeamsCursor = (cursor: string): string => {
const decoded = Buffer.from(cursor, "base64url").toString()
let url: URL
try {
url = new URL(decoded)
} catch {
throw new HTTPError("Invalid cursor", 400)
}
if (
url.origin !== "https://graph.microsoft.com" ||
url.pathname !== "/v1.0/teams"
) {View on GitHub (pinned to a81a902e9a)
Solutions
- Read the status/body in the message: 401 → refresh/re-obtain the Graph token; 403 → grant admin consent for Team.ReadBasic.All and Channel.ReadBasic.All in Azure AD.
- Verify the OAuth app has the required Graph application permissions (not delegated) and admin consent was granted.
- If status is 429, retry with backoff and honor the Retry-After header.
- Confirm the tenant allows service-principal access to Graph and the network can reach graph.microsoft.com.
Example fix
// before // Azure portal: app has only Bot permissions → graphGet returns 403 // after // App registration → API permissions → add application permissions: // Team.ReadBasic.All, Channel.ReadBasic.All → grant admin consent
Defensive patterns
Strategy: retry
Try / catch
try {
const page = await graphGet(url, graphToken)
} catch (err) {
if (err.message.startsWith("Teams Graph API 429")) {
await sleep(backoff)
return retry()
}
if (err.message.startsWith("Teams Graph API 401")) {
graphToken = await getOAuthToken(...) // refresh token
return retry()
}
throw err
} Prevention
- Grant and verify admin consent for Team.ReadBasic.All and Channel.ReadBasic.All application permissions
- Refresh Graph tokens proactively based on expires_in rather than on failure
- Implement exponential backoff for Graph 429 responses honoring Retry-After
When it happens
Trigger: A Graph request made through graphGet returns a non-2xx status: expired/invalid bearer token (401), missing consented application permissions like Team.ReadBasic.All or Channel.ReadBasic.All (403), a malformed URL, or Graph throttling (429).
Common situations: Admin has not consented to the Graph application permissions; the app registration only has Bot Framework permissions but not Graph ones; Graph token cached past expiry due to clock skew; hitting rate limits while enumerating many teams; tenant restrictions blocking graph.microsoft.com.
Related errors
- Teams Bot API ${resp.status}: ${await resp.text()}
- Error getting account by tenantId ${tenantId}
- OIDC Config contents invalid
- Koa context must be supplied to logout.
- Unexpected response when fetching openid-configuration: ${re
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/2315c2700c308bbe.
Report an issue: GitHub.