msteams-transcripts
Download Microsoft Teams meeting transcripts from the command line, including the meetings where the Download button is greyed out.
It drives a browser you are already signed in to, so it needs no admin consent, no app registration and no Graph permissions, and it reaches exactly the meetings you can open yourself. Anything you can read in the Recap tab, this can save to a file.
uvx msteams-transcripts browser
uvx msteams-transcripts list --from 2026-09-01 --to today
uvx msteams-transcripts get 3 --details
Why the Download button being disabled does not matter
- Listing meetings. The Teams web client loads your calendar from its middle-tier API. Each online meeting carries a thread identifier.
- Finding the transcript. The Recap tab asks the meeting-content service about that thread. The reply lists resources; a transcript resource holds the OneDrive drive, item and transcript identifiers.
- Reading the transcript. The recap page renders it from SharePoint, at
/_api/v2.1/drives/<driveId>/items/<itemId>/media/transcripts/<id>/content, in JSON or WebVTT. That call is authenticated by your SharePoint cookies alone. The whole transcript is already delivered to your browser in one request, so no scrolling or scraping is involved. The disabled button is a user-interface policy, not an access control. - Tokens. The two Teams APIs want bearer tokens that the web client keeps encrypted in local storage. The tool captures them from the requests the Teams tab makes as it loads.
Install
With uv, nothing to install:
uvx msteams-transcripts --help
Or install it permanently:
uv tool install msteams-transcripts # or: pipx install msteams-transcripts
pip install msteams-transcripts # into the current environment
Both msteams-transcripts and the shorter teams-transcripts are installed as
commands. Python 3.10 or newer is required. Playwright comes as a dependency,
but do not run playwright install: the tool attaches to the Edge or Chrome
already on your machine and downloads no browsers of its own.
Walkthrough
A first session from a clean machine, start to finish.
1. Start the browser.
uvx msteams-transcripts browser
An Edge window opens on Teams using a profile of its own. On a corporate, Entra-joined PC it usually signs in by itself. If a sign-in page appears, complete it there once. Leave the window open; you can minimise it. Recent Edge and Chrome refuse remote debugging on your everyday profile, which is why a separate one is used.
2. See what is available.
uvx msteams-transcripts list --from 2026-09-01 --to today
# Start (UTC) Subject Organizer
1 2026-09-15 05:41 Weekly one to one Priya Raman
2 2026-09-15 06:09 Platform team sync Tomas Lindqvist
3 2026-09-16 09:02 Architecture review: storage tiering Maya Okonkwo
12 transcript(s). Use: msteams-transcripts get <#>
The first run takes 30 to 60 seconds while Teams loads and issues its tokens; later runs in the same browser session are quicker. Rows are transcripts rather than meetings, so a recurring meeting recorded twice appears twice.
3. Download one transcript with the meeting details.
uvx msteams-transcripts get 3 --details
This writes transcripts\2026-09-16 0902 - Architecture review storage tiering.txt:
# Architecture review: storage tiering
# Date: 2026-09-16 09:02
# Source: https://contoso-my.sharepoint.com/personal/.../Recordings/Architecture-review....mp4
# Organizer: Maya Okonkwo <maya.okonkwo@example.com>
# Location: Microsoft Teams Meeting
# Invited (13):
# Priya Raman <priya.raman@example.com> (Required, Accepted)
# Tomas Lindqvist <tomas.lindqvist@example.com> (Required, Tentative)
# ...
# Speakers in transcript (5):
# Priya Raman (84 segments)
# Maya Okonkwo (72 segments)
# ...
# Files shared in meeting chat (1):
# capacity-plan.xlsx https://contoso.sharepoint.com/sites/...
# Invitation text:
# Agenda: ...
[00:00:03] Priya Raman:
Good morning everyone, let's start with the architecture review.
A sidecar ...meta.json holds the same details as structured data: attendees
with response status, speakers with segment counts, shared files, invitation
text and the thread identifier.
4. Download from a link instead. Open the meeting in Teams, go to the Recap tab, copy the address bar, and pass it:
uvx msteams-transcripts get "https://teams.cloud.microsoft/l/meetingrecap?driveId=...&driveItemId=...&sitePath=..." --details -f all
This path does not touch the calendar and finishes in a few seconds.
5. Archive a whole period.
uvx msteams-transcripts batch --from 2026-09-01 --to 2026-09-30 --details -o C:\Archive\teams
uvx msteams-transcripts batch --from 2026-09-01 --to 2026-09-30 --only 3,7,12
Files with the same name are overwritten. Batch ends with a count of what was downloaded and what failed.
6. Skipping the "Stay better connected with the Teams desktop app" popup
when you open recap links yourself: change /l/meetingrecap? in the address to
/_#/l/meetingrecap?. That form opens the web client directly. The tool
already uses it internally.
Commands
msteams-transcripts browser
msteams-transcripts list --from YYYY-MM-DD --to YYYY-MM-DD|today [--json]
msteams-transcripts get <row#|recap-url|thread-id> [-d] [-f txt|json|vtt|all] [-o DIR]
msteams-transcripts batch --from D --to D [-d] [-f FMT] [-o DIR] [--only 3,7,12]
| Option | Meaning |
|---|---|
-d, --details |
organizer, location, invited attendees with response status, speakers, shared files, invitation text, plus a .meta.json sidecar |
-f, --format |
txt (default), json for the raw Stream document, vtt for WebVTT, or all |
-o, --out |
output directory, ./transcripts by default |
--json |
on list, print the rows as JSON for scripting |
--only |
on batch, restrict to given row numbers |
--port, --profile, --browser |
override the debugging port, profile directory or browser executable |
The text format is:
[00:00:03] Speaker Name:
what they said
the next sentence by the same speaker
Timestamps are offsets from the start of the recording, not wall-clock time.
Row numbers used by get N come from the most recent list, cached in
~/.teams_transcripts/last_list.json.
Using it from an agent
.claude/skills/teams-transcripts/SKILL.md describes the tool for Claude Code
or another agent: preconditions, commands, output format, typical flows and
error handling. Claude Code picks it up automatically when working in this
project.
Library use
import asyncio
from pathlib import Path
from teams_transcripts import TeamsSession, download_ref, refs_from_recap_url
async def main():
async with TeamsSession() as s:
ref = refs_from_recap_url("https://teams.cloud.microsoft/l/meetingrecap?...")
await download_ref(s, ref, Path("out"), "txt", details=True)
asyncio.run(main())
Anything the user can act on raises TranscriptError.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
TT_CDP_PORT |
9222 |
browser debugging port |
TT_PROFILE_DIR |
platform application-data directory | browser profile |
TT_BROWSER_EXE |
Edge, then Chrome, from the usual locations | browser executable |
TT_STATE_DIR |
~/.teams_transcripts |
where the last listing is cached |
Troubleshooting
- "The browser did not expose its debugging port": another window is
already using the dedicated profile without the port. Close it, then run
browseragain. - "Could not capture a Teams token": the Teams tab in that window is not signed in. Sign in there, then retry.
- "Could not capture the meeting-content token": open any meeting's Recap tab in that window, then retry.
- HTTP 403 when listing or downloading: the recording belongs to a tenant or site you cannot read. The Recap tab would be empty for you too.
- Not available through this tool: the attendance report of who actually
joined and when, collaborative Loop notes, and Copilot AI notes.
--detailsgives the invited list from the calendar and who actually spoke per the transcript. - Recurring meetings share one thread identifier. Each occurrence's transcript is listed separately with its own start time.
Development
git clone https://github.com/divyavanmahajan/msteams-transcripts
cd msteams-transcripts
pip install -e ".[dev]"
python -m pytest
The tests cover the pure functions in model.py and formats.py and need
neither a browser nor the network.
Hatchling owns the version, read from __version__ in
src/teams_transcripts/__init__.py. To release:
hatch version patch # or minor, major, or an explicit 1.2.3
git commit -am "Release 0.1.1"
git tag v0.1.1
git push --follow-tags
Pushing the tag runs .github/workflows/publish.yml, which refuses to continue
if the tag and the package version disagree, then builds, publishes to PyPI
through trusted publishing and creates a GitHub release. Trusted publishing is
configured once on PyPI under Publishing, naming this repository, the
publish.yml workflow and the pypi environment; no API token is stored.
Running the workflow by hand offers TestPyPI as the target.
Licence
MIT. Transcripts are meeting content belonging to the people in them; this tool only fetches what you already have access to, and what you do with it afterwards is your responsibility.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file msteams_transcripts-1.0.0.tar.gz.
File metadata
- Download URL: msteams_transcripts-1.0.0.tar.gz
- Upload date:
- Size: 22.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
684dd078025113e82ca3556e0bad2d068eb977b3048580771910270530dcddc6
|
|
| MD5 |
0fa359d3f6ab5eaf043c576b024431d3
|
|
| BLAKE2b-256 |
9bfba5a13822913f5ed14253d9d1865286a3aa82b21769a4ff824b0a3df5de88
|
Provenance
The following attestation bundles were made for msteams_transcripts-1.0.0.tar.gz:
Publisher:
publish.yml on divyavanmahajan/msteams-transcripts
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
msteams_transcripts-1.0.0.tar.gz -
Subject digest:
684dd078025113e82ca3556e0bad2d068eb977b3048580771910270530dcddc6 - Sigstore transparency entry: 2880138315
- Sigstore integration time:
-
Permalink:
divyavanmahajan/msteams-transcripts@ab7b48e86f3228f7131ced6defdf43fb1638cf74 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/divyavanmahajan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ab7b48e86f3228f7131ced6defdf43fb1638cf74 -
Trigger Event:
push
-
Statement type:
File details
Details for the file msteams_transcripts-1.0.0-py3-none-any.whl.
File metadata
- Download URL: msteams_transcripts-1.0.0-py3-none-any.whl
- Upload date:
- Size: 25.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc290a9806e04d6f6a2ff75083e32f143cc02b7274cfa8dc0a740a9d249c5a72
|
|
| MD5 |
711b72363c21bbcf26955b09179927a6
|
|
| BLAKE2b-256 |
cbfddc6e97876c70d8ac2cd485b1c3a32cd7a1f882a9d20979038a4578b96e1b
|
Provenance
The following attestation bundles were made for msteams_transcripts-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on divyavanmahajan/msteams-transcripts
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
msteams_transcripts-1.0.0-py3-none-any.whl -
Subject digest:
bc290a9806e04d6f6a2ff75083e32f143cc02b7274cfa8dc0a740a9d249c5a72 - Sigstore transparency entry: 2880138326
- Sigstore integration time:
-
Permalink:
divyavanmahajan/msteams-transcripts@ab7b48e86f3228f7131ced6defdf43fb1638cf74 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/divyavanmahajan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ab7b48e86f3228f7131ced6defdf43fb1638cf74 -
Trigger Event:
push
-
Statement type: