{"record":{"id":"ccf24688b11c2e96","repo":"Budibase/budibase","slug":"teams-bot-api-resp-status-await-resp-text","errorCode":null,"errorMessage":"Teams Bot API ${resp.status}: ${await resp.text()}","messagePattern":"Teams Bot API (.+?): (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/server/src/escalation/notifications/ms-teams.ts","lineNumber":222,"sourceCode":"}\n\nconst teamsPost = async <T = void>(\n  serviceUrl: string,\n  token: string,\n  conversationId: string,\n  body: object\n): Promise<T> => {\n  const url = `${serviceUrl.replace(/\\/$/, \"\")}/v3/conversations/${encodeURIComponent(conversationId)}/activities`\n  const resp = await fetch(url, {\n    method: \"POST\",\n    headers: {\n      Authorization: `Bearer ${token}`,\n      \"Content-Type\": \"application/json\",\n    },\n    body: JSON.stringify(body),\n  })\n  if (!resp.ok) {\n    throw new Error(`Teams Bot API ${resp.status}: ${await resp.text()}`)\n  }\n  return resp.json()\n}\n\n// Replies to the requester in their originating conversation on escalation\n// resume - DM as plain text, channel with an @mention so it's attributed.\nexport async function replyToConversation({\n  appId,\n  agentId,\n  channel,\n  text,\n}: {\n  appId: string\n  agentId?: string\n  channel: ChatConversationChannel\n  text: string\n}): Promise<void> {\n  if (!channel.conversationId) {","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/escalation/notifications/ms-teams.ts#L204-L240","documentation":"teamsPost sends an activity to the Azure Bot Framework REST API (POST /v3/conversations/{conversationId}/activities) using the bot-scoped OAuth token. Non-ok responses are thrown as this error with the status and response body. It is used both for replying in an existing conversation (replyToConversation) and for posting the adaptive escalation card after a conversation is created (sendMSTeamsNotification).","triggerScenarios":"The Bot Framework POST returns non-2xx: 401 (bot token invalid/expired or wrong serviceUrl region), 403 (bot not installed in the team/user has blocked the bot), 404 (conversationId no longer exists or is from a different channel), or 5xx from Microsoft.","commonSituations":"channel.serviceUrl stale or region-specific while TEAMS_API_URL/DEFAULT_SERVICE_URL points at a different region; bot was uninstalled from the team after escalation was created; bot token cached past expiry; user blocked the bot so DMs are rejected; conversationId from an old escalation.","solutions":["Use the serviceUrl captured from the original conversation (channel.serviceUrl or the conversation-creation response) instead of the default region URL.","On 401, force a token refresh (getOAuthToken caches with dynamic TTL — clear the OAUTH2_TOKEN cache entry for the bot scope) and retry.","Verify the bot app is still installed in the target team/tenant and the user hasn't blocked it.","Confirm the conversationId is still valid; if not, create a new conversation (DM path) before posting."],"exampleFix":"// before\nawait teamsPost(DEFAULT_SERVICE_URL, token, channel.conversationId, message) // 404: wrong region\n// after\nconst serviceUrl = channel.serviceUrl || DEFAULT_SERVICE_URL\nawait teamsPost(serviceUrl, token, channel.conversationId, message)","handlingStrategy":"try-catch","validationCode":"if (!channel.conversationId) throw new Error(\"Cannot post: missing conversationId\")\nif (!channel.serviceUrl) console.warn(\"No stored serviceUrl; falling back to default region\")","typeGuard":null,"tryCatchPattern":"try {\n  await teamsPost(serviceUrl, token, conversationId, message)\n} catch (err) {\n  if (/Teams Bot API 40[134]/.test(err.message)) {\n    // 401: refresh token; 403/404: bot removed or conversation stale — recreate conversation\n  } else if (/Teams Bot API 5\\d\\d/.test(err.message)) {\n    // transient — retry with backoff\n  } else throw err\n}","preventionTips":["Persist the serviceUrl from the original conversation and use it for replies","Keep bot installation in the team/tenant active and the messaging endpoint deployed","Refresh bot tokens on 401 instead of reusing cached values indefinitely"],"tags":["bot-framework","http","teams","network"],"backgroundTag":"bot-api-request-failed","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}