One HTTP call sends a message to a colleague's Teams — as a direct message or into a group chat. You don't touch Teams, Graph, or Bot Framework, you don't request Azure permissions, and you don't run a bot of your own.
Request / manage my API key →The company's shared outbound channel for Teams notifications. Messages are delivered by a bot called AlertBot, so the recipient sees a DM from AlertBot, or a bot message in a group chat. It fits anything where a system needs to reach a person: release notices, alerts, approval reminders, due-date nudges.
Open the admin portal, sign in with your company Microsoft account, and submit a request (short client id, app name, purpose). Once an admin approves it, the key is sent to you as a Teams DM — you can also come back to the portal and click “Reveal key” to see it once.
The portal also shows which clients you own, where each request stands, the key's prefix, and lets you withdraw a request that hasn't been approved yet.
POST /send
Authorization: Bearer <your API key>
Content-Type: application/json
{"to": "someone@lofty.com", "text": "Build #1234 shipped ✅"}
curl -sS -X POST "$NOTIFY_URL/send" \
-H "Authorization: Bearer $NOTIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"to":"someone@lofty.com","text":"Build #1234 shipped ✅"}'
import json, os, urllib.request
def notify(to, text):
req = urllib.request.Request(
os.environ["NOTIFY_URL"] + "/send",
data=json.dumps({"to": to, "text": text}).encode(),
headers={"Authorization": "Bearer " + os.environ["NOTIFY_KEY"],
"Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=20) as r:
return json.load(r)
{"ok": true, "to": "someone@lofty.com", "toKind": "user",
"displayName": "Someone", "chatId": "19:...@unq.gbl.spaces",
"messageId": "1699...", "requestId": "21ddaecf-..."}
Keep the requestId — hand it to the platform owner and they can look up exactly
that call in the audit log.
Group chats use a separate endpoint, and the recipient field is chat_id:
POST /send/group
Authorization: Bearer <your API key>
{"chat_id": "19:xxxx@thread.v2", "text": "Deploying at 22:00 tonight"}
Open the group in Teams → “⋯” next to the group name → Copy link. You get something like:
https://teams.microsoft.com/l/chat/19:xxxx@thread.v2/conversations?...
The 19:…@thread.v2 segment in the middle is the chat_id.
AlertBot and add it — once is enough.
Until then calls return 403/404, and the response includes a hint saying exactly this.Channels (@thread.tacv2) aren't supported yet — only group chats
(@thread.v2). Ask the platform owner if you need channel posts.
Rather than assembling Adaptive Card JSON yourself, pass a template_id plus
params and the server renders it. Works for both DMs and group chats.
{"to": "someone@lofty.com",
"template_id": "release-notice",
"params": {"service": "payment-api", "version": "v2.3.1",
"env": "prod", "url": "https://ci.example.com/build/1234"}}
| template_id | Use | Required params | Optional params |
|---|---|---|---|
release-notice | Release / deploy notice | service, version | env, notes, url |
alert | Alert | title, message | severity, source, url |
approval-request | Approval request | title, requester | details, url |
Optional params you leave out are stripped from the card, so you don't get empty rows or dead
buttons. You can also pass your own card (full Adaptive Card JSON) instead — supply
exactly one of text, card, or template_id.
Template labels are currently Chinese. What language your notifications are in is
the calling app's decision — if you need English cards, build the card JSON yourself,
or ask the platform owner to add English templates.
| Status | Meaning | What to do |
|---|---|---|
| 200 | Delivered | — |
| 400 | Bad parameters / unknown recipient / malformed group id / bot not in the group | Read error and detail.hint, fix and retry |
| 401 | Key invalid or revoked | Check the key's status in the portal, or rotate it |
| 502 | Upstream failure (Teams / Graph / Bot Framework) | Retry later; if it keeps failing, send the requestId to the platform owner |
| 500 | Channel-internal error | Send the requestId to the platform owner |
Failures always come back as {"ok": false, "error": ..., "detail": ..., "requestId": ...}.
Error messages follow your Accept-Language header. Force one language
with ?lang=en / ?lang=zh, or an X-Lang header.
client_id / recipient / time /
status / content hash and length; message bodies are never stored.
Records are kept 180 days by default.