---
title: Microsoft Teams
description: Connect a Microsoft Teams bot so chats and channels start and continue Beeuni sessions.
---

Microsoft Teams is a channel: an `@`-mention in a Teams chat or channel starts
a [session](/docs/work/sessions), and the agent replies right there with live
progress. It follows the same session, identity, and credential rules as
[Slack](/docs/connect/slack); this page covers what is specific to Teams.

Every project can connect Teams. There is no feature flag to turn on.

## Connect

Two ways, both from the project's **Connectors → Channels** page:

- **One click (managed).** A Teams **tenant admin** clicks **Add to Teams**,
  signs in, and consents. Beeuni publishes the app to your org's Teams catalog
  automatically — there is nothing to build or paste. The row then offers
  **Add to Teams** (a deep link into the Teams client) so anyone can add the
  bot to a chat or channel. When Beeuni ships a newer version of the app,
  see [Update the app](#update-the-app).
- **Bring your own bot.** Click **Use your own Microsoft Teams app** for a
  three-step wizard: register an Azure Bot, connect it to Beeuni with its app
  id, client secret and tenant id, then download the generated app package and
  upload it to Teams. Use this when you run your own bot registration.

  The order matters. Beeuni builds the manifest from **your** app id, so it can
  only generate the package after step 2 — the wizard shows you the messaging
  endpoint to paste into Azure before it asks for anything else.

The CLI mirrors the dashboard:

```bash
beeuni channels connect --platform teams      # prints the admin-consent URL
beeuni channels status --platform teams        # tenant, catalog, publish state, app version
beeuni channels disconnect --platform teams
```

## Update the app

Beeuni adds permissions to its Teams app over time. App version 1.1.0 added
`ChannelMessage.Read.Group`, which the agent needs to read a channel thread.
Version 1.3.0 added `ChatMessage.Read.Chat`, so a group-chat reply continues
a session without a mention. A team still on 1.0.0 refuses every thread read,
and the refusal the agent receives names who fixes it.

Two people update the app, in this order:

1. **A Teams admin publishes the update.** When your org catalog has an older
   version, the Channels page shows **Update the Beeuni app in Teams**, and the
   Teams row's button reads **Publish app update**. The admin clicks it and
   signs in with Microsoft. `beeuni channels status --platform teams` prints
   both versions, and `beeuni channels connect --platform teams` prints the
   sign-in link.
2. **A team owner updates the app in each team where Teams offers it.** In
   Teams, open the team, then **⋯ → Manage team → Apps**, select **Update** on
   the Beeuni app, and accept it. Teams installs an update on its own unless it
   adds a permission or a message action; those wait for a team owner.

Beeuni records the app version at each publish. An install published before
Beeuni recorded versions shows the notice until its next publish.

## Chats, channels, and mentions

- **Personal chat:** every message reaches the bot.
- **Team channel / group chat:** a new post needs an `@`-mention. After that,
  a reply in a conversation the bot already owns continues the session
  **without** one, the way a Slack thread does. That needs the app's
  `ChannelMessage.Read.Group` (channels) and `ChatMessage.Read.Chat` (group
  chats) permissions, which the tenant admin consents to when the app is added.
  If the app was added before these were requested, a team owner updates it:
  see [Update the app](#update-the-app).

Each chat or channel conversation is its own session. The first message creates
it; later messages continue it, even after the sandbox stops and resumes. A
tenant connected to more than one project shows a **project picker** on the
first unrouted message, and remembers the choice. In a channel only a message
that mentions the bot gets the picker, and a command about the project, such as
`/models`, asks which project first.

In a channel, every new post starts its own session. A personal or group chat
is one conversation for its whole life, so it keeps one session until you send
`/new`. Put a message after it (`/new summarize today's deploys`) to start the
new session with that message. The previous session stays in Beeuni, with its
full history, and the confirmation links to it.

## Conversation names

Teams sends ids, such as `19:…@thread.tacv2;messageid=…`. The Channels page and
`beeuni channels bindings` show the names people read in Teams:

| Conversation | Name | Under the name |
|---|---|---|
| A channel post | `Team › Channel` | The title of its session, which tells two posts in one channel apart |
| A group chat | The chat's title | Group chat |
| A personal chat | The person | Personal chat |

A channel message carries the team's id but not its name, so Beeuni reads the
team and channel names from Teams and stores them. A post in a team's General
channel is named the next time the Channels page lists it. A post in another
channel is named by its next message. Until then it reads **Channel thread**.

## Who may join a session

Each conversation has a join policy, set with `/policy` in Teams or on the
Channels page, where **N channels** on the Teams row lists every conversation:

- **open** (default) — any linked project member may join a session started here.
- **approval** — the session owner gets an Approve / Deny card for each new person.
- **owner** — only the person who started the session.

Under **approval** and **owner**, the session is not visible to the whole
project in Beeuni either: only to its owner, the people approved into it, and
account admins who oversee sessions. A decision on a review or approval card
resumes the agent under any policy.

## Identity linking

The sender links their Beeuni account once with `/login`; the agent then runs
as them, with their own credentials, secrets, and connected apps. An unlinked
sender, or one without access to the project, gets a card to connect or request
access instead of a session.

The sign-in link appears only where you alone can see it: in your one-to-one
chat with the bot, or, in a channel or group chat, in a reply marked **Only you
can see this message**. The link connects the person it was issued for to
whoever opens it, so it never appears where others can see it. If Teams refuses
that private reply, the bot sends the link to your private chat with it and
says so in the conversation. Teams allows that only when you have added the app
for yourself; otherwise the card asks you to open a private chat with the bot
and send `/login` there. A message you sent in the channel while unlinked runs
once you connect, if you do so within 10 minutes. When you finish connecting in
the browser, your private chat with the bot confirms which Beeuni account you
are linked as.

A sign-in link works once and expires after 10 minutes. A Teams account that is
connected to one Beeuni account cannot be connected to another: send `/logout`
first. If your organization requires two-factor authentication, Beeuni asks for
your second factor before it connects you, and the chat runs on the factor you
connected with. A link made without it asks you to connect again.

When someone without access asks for it, every account owner and admin gets
the request in Beeuni. Those who linked Teams also get it in their private chat
with the bot, with a **Review in Beeuni** button.

## Private replies

In a channel or group chat, what the bot says to one person is visible only to
that person, marked **Only you can see this message**, as Slack's ephemeral
messages are:

- the replies to your commands;
- the card to connect your account or to request access;
- why your message did not run: the join policy, or an agent you may not use;
- under **approval**, the owner's Approve / Deny card, and the decision to the
  person who asked.

The **Working on it…** card that everyone sees appears only when your message
may run. Teams deletes a private reply after 24 hours. If Teams refuses a
private reply, the bot posts the card for the whole conversation instead. The
sign-in link is the exception: it then goes to your private chat with the bot,
never to the conversation.

## Roles and permissions

A chat carries no Beeuni credential of its own, so every action runs as the
linked Beeuni account of the person who took it, with that account's project
role. Account owners and admins count as project managers.

| Action | Who may do it |
| --- | --- |
| Send a message the agent answers | A linked account with access to the project, within the conversation's join policy |
| Stop a run | The person who sent the message being answered, or anyone approved into that session, who can still use the project |
| `/new` in a group chat | A linked project member; under `approval` or `owner`, someone approved into the current session |
| Change the project, agent, model or policy | A project manager; in a personal chat with the bot, any project member |
| Approve or deny a review | A project manager |
| Approve or deny a connector call | A project manager, or the person who started the session |
| Approve someone into an `approval` session | The session owner |
| Request access | Anyone linked; each account owner and admin is notified once |

A person scoped out of an agent cannot start a session on it from Teams, as in
Beeuni.

## Commands

Type these in a chat with the bot (in a channel, `@`-mention it first):

| Command | What it does |
| --- | --- |
| `/help` | List the commands |
| `/login`, `/logout`, `/whoami` | Link, unlink, or show your Beeuni account |
| `/status` | The effective project, agent and model here, and the run in progress |
| `/stop` | Stop the run in progress here |
| `/new [message]` | Start a new session in this chat (personal and group chats) |
| `/models`, `/model <ref>` | Pick the model for this conversation — see [Models and keys](#models-and-keys) |
| `/agents`, `/agent <name>` | Pick the agent for this conversation |
| `/projects`, `/use <name>` | List connected projects, or point this one at another (not on a project's own bot) |
| `/policy [open\|approval\|owner]` | Who may join sessions started here |
| `/unbind` | Disconnect this conversation from its project; the next message picks one again |
| `/home` | The organization's projects and what to try |

On any message, **⋯ → Open in Beeuni** opens the session that conversation
belongs to. When you add the app for yourself, your private chat with the bot
starts with the same overview as `/home`.

## Models and keys

`/models` lists every model you can run here, as the web picker does: Beeuni
models, and models you reach through a provider API key or a ChatGPT
subscription. Each one says how it is paid for. A long list is a searchable
dropdown. Tap a model, or type `/model <id>`; `/model default` goes back to the
project default.

Which keys count depends on the chat:

- **A personal chat with the bot** — your own keys and ChatGPT subscription,
  plus keys shared with the project. Its sessions are private to you.
- **A group chat or channel** — only keys shared with the whole project. The
  chat is shared, and a personal key there would let everyone in it spend one
  person's subscription. Pick such a model in a personal chat instead.

A model that runs on keys uses every key you may use for its provider, and
they rotate on rate limits. The choice applies to the next message; the chat
keeps its session. Changing the model of a running session needs a linked
account, and under the `owner` or `approval` policy only the session's owner
can change it. A personal chat whose session started before sessions there
became private still shares that session with the project: `/model` says so,
and `/new` starts a private one. See
[Provider keys and member access](/docs/project/models#provider-keys-and-member-access).

## While the agent works

The agent answers in one card: its steps while it works, then the reply. A long
reply is cut at a line, marked as cut, and links to the full session in Beeuni.
Another message waits for the current run, then gets its own card. A quiet step
does not close a run; only the runtime's completion does.

## Stopping a run

While the agent works, its live card carries a **Stop** button. `/stop` does
the same thing after the card has scrolled out of reach. Either ends the run at
the runtime, not just on the card — a run that is left open swallows the next
message in the conversation instead of answering it.

The person who sent the message being answered can stop it, as can anyone
approved into that session, while they can still use the project. A bystander
in the channel cannot, and neither can someone approved into an earlier session
of the chat.

## Files

The agent sends files with `teams send --file`. Images appear inline in personal,
group, and channel chats. An oversized image falls back to a OneDrive consent
card in a personal chat or to team files in a channel; a group chat cannot
receive an oversized transfer. Other files arrive as a consent card in a
personal chat or a SharePoint link when shared to a team. Inbound files and
pasted images, including images embedded in a channel message body, are
available to the agent to download and work with.

- An image pasted into a channel or a group chat downloads only when the agent
  may read that thread or chat, by the rules in
  [Reading a channel](#reading-a-channel). Otherwise `teams download` answers
  `403`.
- A download stops at 50 MB and answers `413`.
- `teams history` and `teams thread` list a post's inline images (`images`,
  each one a URL for `teams download`) and the names of files shared from
  SharePoint (`files`). The agent cannot download a SharePoint file.

Paste a screenshot and ask about it and the turn runs on a model that can read
images, even when the conversation's own model is text-only — the session's
model pick is left alone. A model the session can no longer run — a key that
was revoked, a ChatGPT subscription the chat cannot use — is replaced for that
message the same way, instead of failing it.

## Questions and forms

The agent asks with controls, not prose. One question with up to six options
gets one button per option. Multiple questions, a pick-several question, or a
longer list get a form with a box for your own answer. Dates, times, numbers,
and on/off choices use matching inputs. Your answer reaches the agent as your
next message, labelled with the question it answers. You can always type an
answer instead.

## Approvals

When an agent needs a human decision before it proceeds — a risky step, a
change it wants signed off — it files a review item. If the session was started
from this conversation, the decision lands here as a card: the title, a summary,
a risk level, and **Approve**, **Request changes** and **Deny**, plus a link
into Beeuni.

The card carries an optional feedback box. Whatever you type is stored with the
decision and passed to the agent with it, so "Request changes" does not cost a
second round trip to ask what you meant. It is optional by design — a plain
Approve should not need typing.

Anyone in the conversation sees the card, but acting on it needs a linked
Beeuni account with access to the project.

## Posting on its own

Besides replying where it was asked, the agent can post into any chat or
channel the bot is already in for this project — a nightly summary, an alert,
a hand-off. It cannot reach a conversation it was never added to.

## When a session will not start

Beeuni says what is wrong instead of asking you to try again, because most
start failures never clear on a retry: a sandbox template that no longer
exists, an account that is not linked, a project you cannot reach, or a
workspace out of credits. Each one names the fix and the command that applies
it.

If the agent set for the conversation has been deleted or renamed, the failure
arrives as a picker: choose a live agent, then send your message again. The
list only offers agents you have access to.

## Reading a channel

In a channel the bot only receives messages that mention it, so the discussion
it was asked about is not in its session. When it needs that context, the agent
reads the thread it was mentioned in, or the channel's recent posts, through
the `ChannelMessage.Read.Group` permission the app requests when it is added to
a team. A team that added the app before that permission was requested must
update it: see [Update the app](#update-the-app). In a personal chat every
message already reaches the session, so there is nothing earlier to read.

Every project connected to the same Microsoft 365 tenant uses the same app, so
Beeuni keeps each read inside the project's own conversations. In a channel,
every new post is its own conversation:

| Read | Allowed when |
| --- | --- |
| A thread (`teams thread`) | The thread is a conversation of this project, or of no project and the channel is readable |
| A channel's recent posts (`teams history`) | This project has a conversation in the channel. Posts that start another project's conversation are left out |
| A channel no project has a conversation in | No other project is connected to the tenant |

A refused read answers `403` with the reason `conversation_not_in_project` and
a message that names the channel or thread. To read a channel of the first kind
in a tenant with several projects, mention the bot in it and pick this project.

`get_channel` and `list_channels` answer for standard channels. A private or
shared channel appears only when the agent may read it by the rules above.
`get_team`, `list_members`, `get_user` and `list_teams` read the tenant
directory and are not limited.

## Limits

Teams bots cannot add message reactions or search messages — those Slack
affordances have no Teams equivalent. The agent can read a channel only in a
team the app was added to, and only by the rules in
[Reading a channel](#reading-a-channel).
