Budibase/budibase · error · Error

Teams Bot API ${resp.status}: ${await resp.text()}

Error message

Teams Bot API ${resp.status}: ${await resp.text()}

What it means

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).

Source

Thrown at packages/server/src/escalation/notifications/ms-teams.ts:222

}

const teamsPost = async <T = void>(
  serviceUrl: string,
  token: string,
  conversationId: string,
  body: object
): Promise<T> => {
  const url = `${serviceUrl.replace(/\/$/, "")}/v3/conversations/${encodeURIComponent(conversationId)}/activities`
  const resp = await fetch(url, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  })
  if (!resp.ok) {
    throw new Error(`Teams Bot API ${resp.status}: ${await resp.text()}`)
  }
  return resp.json()
}

// Replies to the requester in their originating conversation on escalation
// resume - DM as plain text, channel with an @mention so it's attributed.
export async function replyToConversation({
  appId,
  agentId,
  channel,
  text,
}: {
  appId: string
  agentId?: string
  channel: ChatConversationChannel
  text: string
}): Promise<void> {
  if (!channel.conversationId) {

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Use the serviceUrl captured from the original conversation (channel.serviceUrl or the conversation-creation response) instead of the default region URL.
  2. On 401, force a token refresh (getOAuthToken caches with dynamic TTL — clear the OAUTH2_TOKEN cache entry for the bot scope) and retry.
  3. Verify the bot app is still installed in the target team/tenant and the user hasn't blocked it.
  4. Confirm the conversationId is still valid; if not, create a new conversation (DM path) before posting.

Example fix

// before
await teamsPost(DEFAULT_SERVICE_URL, token, channel.conversationId, message) // 404: wrong region
// after
const serviceUrl = channel.serviceUrl || DEFAULT_SERVICE_URL
await teamsPost(serviceUrl, token, channel.conversationId, message)
Defensive patterns

Strategy: try-catch

Validate before calling

if (!channel.conversationId) throw new Error("Cannot post: missing conversationId")
if (!channel.serviceUrl) console.warn("No stored serviceUrl; falling back to default region")

Try / catch

try {
  await teamsPost(serviceUrl, token, conversationId, message)
} catch (err) {
  if (/Teams Bot API 40[134]/.test(err.message)) {
    // 401: refresh token; 403/404: bot removed or conversation stale — recreate conversation
  } else if (/Teams Bot API 5\d\d/.test(err.message)) {
    // transient — retry with backoff
  } else throw err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/ccf24688b11c2e96. Report an issue: GitHub.