Meetily

Back up and batch-process meetings

Export every meeting to Markdown or JSON on a schedule, and run diarization across many meetings.

This reads and moves meeting content

Backing up reads full meeting content and writes it elsewhere (disk, Notion, Obsidian). You are the data controller for that transfer: get consent, scope the token to read for backups (diarization needs write), and treat the destination as untrusted. Never hard-code a token in a script you share; read it from the environment or the loopback token file.

Two batch jobs: back up every meeting to files, and re-diarize a set of meetings. Turn on the Automation API under Settings > Integrations first (see Enable & connect).

Nightly backup

List meetings with pagination (limit clamps to 1..200; page with offset), and export each with GET /v1/meetings/{id}/export?format=json|md|txt. The CLI covers both, so a backup needs no code:

#!/usr/bin/env bash
set -euo pipefail
export MEETILY_PRO_TOKEN="..."          # a read-scoped token
mkdir -p meetily-backup
offset=0
while :; do
  ids=$(meetily-pro meetings list --limit 200 --offset "$offset" --json | jq -r '.meetings[].id')
  [ -z "$ids" ] && break
  for id in $ids; do
    meetily-pro meetings export "$id" --format md > "meetily-backup/$id.md"
  done
  offset=$(( offset + $(printf '%s\n' "$ids" | grep -c .) ))
done

The same over the HTTP API with standard-library Python:

import json, pathlib, urllib.request

MEETILY, TOKEN = "http://127.0.0.1:8420", "..."
out = pathlib.Path("meetily-backup"); out.mkdir(exist_ok=True)

def api(path):
    req = urllib.request.Request(f"{MEETILY}{path}", headers={"Authorization": f"Bearer {TOKEN}"})
    return urllib.request.urlopen(req).read()

offset = 0
while True:
    page = json.loads(api(f"/v1/meetings?limit=200&offset={offset}"))
    meetings = page.get("meetings", [])
    if not meetings:
        break
    for m in meetings:
        (out / f"{m['id']}.md").write_bytes(api(f"/v1/meetings/{m['id']}/export?format=md"))
    offset += len(meetings)

MeetingListResponse also carries total, so you can show progress against it.

Batch re-diarize

Submit a diarization job per meeting and wait for each. A rerun that conflicts with one already running returns 409, so handle that. Diarization needs a write token.

for mid in MEETING_A MEETING_B; do
  job=$(meetily-pro jobs diarization --meeting-id "$mid" --json 2>/dev/null) || { echo "skip $mid (409 or error)"; continue; }
  jid=$(printf '%s' "$job" | jq -r '.job_id')
  # No CLI wait stream - poll the SSE wait endpoint with curl:
  curl -N -s "http://127.0.0.1:8420/v1/jobs/$jid/wait?timeout=1800" \
    -H "Authorization: Bearer $MEETILY_PRO_TOKEN" >/dev/null
  echo "done $mid"
done

Omit --speaker-count for auto mode. A job's terminal states are succeeded, failed, or cancelled - there is no completed state.

Detecting new meetings

You can't subscribe to meeting.* events

meeting.created, meeting.updated, and meeting.deleted are not subscribable webhook events - registering for one is rejected at registration with 400 (there's no emitter behind them). To pick up new meetings for a backup job, poll GET /v1/meetings and diff against what you've already saved, rather than waiting for an event. To react to a summary being ready, subscribe to summary.completed instead - see Post summaries to Slack.

See Meetily APIs for the full meetings, export, and jobs route surface.

Last updated on

On this page