Export transcripts and AI summaries from plaud.ai recordings.
Use when user mentions Plaud, plaud.ai, meeting recordings export,
or wants to download/sync transcripts from their Plaud device.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Export transcripts and AI summaries from plaud.ai recordings.
Use when user mentions Plaud, plaud.ai, meeting recordings export,
or wants to download/sync transcripts from their Plaud device.
allowed-tools
["Bash","Write","Read","AskUserQuestion"]
Plaud Connector: Export transcripts from plaud.ai
Download transcripts, speaker-labeled segments, and AI summaries from a Plaud account. Works with the unofficial plaud.ai web API.
First: get credentials
Before doing anything, ask the user for their Plaud credentials. They need one of:
Bearer token (most common, required for Google SSO users)
Email + password (for users with a regular Plaud account)
If they don't know how to get a token, walk them through it:
Open DevTools (Cmd+Option+I on Mac, F12 on Windows) -> Network tab
Click any recording to trigger an API request
Find a request to api.plaud.ai, click it, copy the full Authorization header value
EU accounts use a different API host (https://api-euc1.plaud.ai). Ask the user if they're on an EU account, or check if their token came from api-euc1.plaud.ai in the network tab.
Once you have credentials, set them as environment variables before running any scripts:
export PLAUD_TOKEN="bearer eyJ..."# OR
PLAUD_EMAIL=
PLAUD_PASSWORD=
PLAUD_API_BASE=
export
"user@example.com"
export
"password"
# EU accounts only:
export
"https://api-euc1.plaud.ai"
Two modes of operation
Mode A: CLI (if plaud-connector is installed)
Check first:
which plaud 2>/dev/null && echo"CLI available" || echo"CLI not installed"
If installed, these commands do everything:
plaud sync -o output # full sync: generate missing transcripts + download all
plaud list # list all recordings
plaud generate --dry-run # preview what needs transcription
plaud generate --wait# trigger transcription and wait
plaud download -o output # download transcripts as JSON + Markdown
OUTPUT = "output"
recordings = list_recordings()
tags = {t["id"]: t["name"] for t in get_tags()}
file_ids = [r["id"] for r in recordings]
exported, skipped = 0, 0for i inrange(0, len(file_ids), 20):
for rec in get_details(file_ids[i:i+20]):
tag_ids = rec.get("filetag_id_list") or []
folder = next((tags[t] for t in tag_ids if t in tags), "Unsorted")
dt = datetime.fromtimestamp(rec["start_time"] / 1000, tz=timezone.utc)
safe = re.sub(r'[<>:"/\\|?*]', '-', rec["filename"])
stem = f"{dt:%Y-%m-%d}_{safe}"
out = Path(OUTPUT) / folder
out.mkdir(parents=True, exist_ok=True)
if (out / f"{stem}.json").exists():
skipped += 1continue
transcript = rec.get("trans_result") or []
summary = parse_ai_content(rec.get("ai_content"))
dur_ms = rec.get("duration", 0)
(out / f"{stem}.json").write_text(json.dumps({
"id": rec["id"], "filename": rec["filename"],
"start_time": rec["start_time"], "duration_ms": dur_ms,
"transcript": [{"speaker": s.get("speaker", "?"), "content": s.get("content", ""),
"start_time": s.get("start_time", 0), "end_time": s.get("end_time", 0)}
for s in transcript],
"summary": summary,
}, indent=2, ensure_ascii=False))
lines = [f"# {rec['filename']}", "", f"**Date:** {dt:%Y-%m-%d %H:%M UTC}",
f"**Duration:** {fmt_duration(dur_ms)}", "", "## Transcript", ""]
for s in transcript:
ts = fmt_duration(s.get("start_time", 0))
lines += [f"**{s.get('speaker', '?')}** [{ts}]: {s.get('content', '')}", ""]
lines += ["## Summary", "", summary, ""]
(out / f"{stem}.md").write_text("\n".join(lines))
print(f" + {rec['filename']}")
exported += 1print(f"\nDone! {exported} exported, {skipped} skipped (already exist)")
Generate missing transcripts and wait
recordings = list_recordings()
missing = [r for r in recordings ifnot r.get("is_trans") and r.get("duration", 0) // 1000 >= 10]
ifnot missing:
print("All recordings already have transcripts!")
else:
print(f"{len(missing)} recordings without transcripts:\n")
for r in missing:
dur = r.get("duration", 0) // 1000print(f" {dur:>5}s {r.get('filename', '?')}")
start_transcription(r["id"])
print(f"\nTriggered {len(missing)} transcriptions. Polling...")
pending = {r["id"]: r.get("filename", "?") for r in missing}
while pending:
time.sleep(15)
for fid inlist(pending):
if poll_transcription(fid).get("status") == 1:
print(f" Done: {pending.pop(fid)}")
if pending:
print(f" Waiting on {len(pending)}...")
print("\nAll transcriptions complete!")
EU accounts use https://api-euc1.plaud.ai instead of https://api.plaud.ai
Tokens expire. If you get 401 errors, the user needs to grab a fresh token from their browser.
Triggering transcription is two steps: PATCH sets config, then POST to /ai/transsumm with is_reload=1 starts the job. PATCH alone does nothing.
ai_content field can be plain markdown, or JSON containing {"markdown": "..."}, {"content": {"markdown": "..."}}, or {"summary": "..."}. The parse_ai_content helper handles all variants.
Some transcript segments may lack speaker or content keys. Always use .get() with defaults.
Don't send more than 20 IDs at once to /file/list.
All GET requests need a random r= query parameter for cache busting.