A comprehensive command-line interface for the Grain API v2 — list and search recordings, pull transcripts and AI summaries, download meeting media, manage webhooks, upload recordings, and export everything about a meeting (summary, video, transcript, and a structured participant list) with a single command.
grain recordings export <id> --out ./meeting
# → ./meeting/summary.md, video.mp4, transcript.json, transcript.txt,
# participants.json, participants.csv, action-items.json, metadata.json
# from source (this repo)
pnpm install
pnpm build
npm link # puts `grain` on your PATH
# or run without installing
node dist/index.js --helpRequires Node.js 18+.
Grain supports three token types (see the API docs):
| Type | Scope | Where to get it |
|---|---|---|
| Personal Access Token (PAT) | acts as you | https://grain.com/app/settings/integrations?tab=api |
| Workspace Access Token (WAT) | all workspace data | same page (needs workspace access) |
| OAuth2 | acts as the authorizing user | register a client with Grain |
grain auth login # prompts for a token (hidden input), stores a PAT
grain auth login --type wat # store a Workspace token
grain auth login --token grain_pat_… # non-interactive
echo "$TOKEN" | grain auth login # from a pipe / secret managergrain auth login --oauth \
--client-id YOUR_CLIENT_ID \
--client-secret YOUR_SECRET \
--redirect-uri http://localhost:8976/callbackThis opens your browser, runs the loopback flow, and stores the access + refresh tokens. Expired access tokens are refreshed automatically before each command.
grain auth status # show the resolved profile (token is masked)
grain auth token # print the resolved access token (for scripts)
grain auth refresh # force an OAuth refresh
grain auth logout # forget the active profile's credentialsTokens can also come from the environment (handy for CI):
export GRAIN_API_TOKEN=grain_pat_…
grain recordings listPlace these after the command (kubectl-style):
| Option | Description |
|---|---|
--token <token> |
API token (overrides profile/env) |
--profile <name> |
configuration profile |
--base-url <url> |
override the API base URL |
--api-version <version> |
Public-Api-Version header (default 2025-10-31) |
-o, --output <format> |
table (default in a terminal), json (default when piped), or csv |
--fields <list> |
comma-separated columns to show (table/csv) |
--no-color |
disable colored output (also honors NO_COLOR) |
--debug |
log HTTP requests to stderr |
--timeout <ms> |
per-request timeout |
--max-retries <n> / --no-retry |
control automatic retries on 429/5xx |
# List (newest first) with filters
grain recordings list --limit 20
grain recordings list --title "all hands" --after 2025-01-01T00:00:00Z
grain recordings list --scope external --team <team-id>
grain recordings list --all -o json # every page as JSON
grain recordings list --include participants,ai_summary
# Get one (with optional extra data)
grain recordings get <id> --include all
# Transcript
grain recordings transcript <id> # structured segments
grain recordings transcript <id> -f txt # plain text
grain recordings transcript <id> -f srt --out subs.srt
# Download the media
grain recordings download <id> --out talk.mp4
# Export a whole meeting (summary + video + transcript + participants)
grain recordings export <id> --out ./meeting
grain recordings export <id> --no-video # skip the media file
grain recordings export --all --after 2025-01-01T00:00:00Z --out ./archive # bulk
# Upload a local file as a new recording
grain recordings upload ./meeting.mp4 # PAT
grain recordings upload ./meeting.mp4 --user-id <uuid> # WAT (owner required)
# Manage
grain recordings update <id> --title "New title"
grain recordings tag <id> product-launch
grain recordings untag <id> product-launch
grain recordings share <id> --user <user-id>
grain recordings share <id> --team <team-id>
grain recordings unshare <id> --team <team-id>For each recording it creates a folder containing:
| File | Contents |
|---|---|
summary.md |
the AI summary (markdown) |
video.mp4 / .mov / .mp3 / .m4a |
the recording media (skip with --no-video) |
transcript.json |
structured transcript segments (speaker, start/end ms, text) |
transcript.txt (+ .vtt/.srt) |
text transcripts (--transcript-format txt,vtt,srt) |
participants.json + participants.csv |
structured participant list |
action-items.json |
AI action items (when present) |
highlights.json |
clips/highlights (when present) |
metadata.json |
the full recording payload |
Bulk mode (--all or filters, no id) exports each meeting into its own <slug>-<id8>/ subfolder.
A missing transcript or summary for one recording is skipped with a warning; the export continues.
grain hooks list
grain hooks list --type recording_added --state enabled
grain hooks create --type recording_added --url https://example.com/hook
grain hooks create --type recording_added --url https://example.com/hook --include participants,ai_summary
grain hooks delete <hook-id>Grain sends a reachability test to the URL on creation (it must return 2xx). Event types:
recording_added|updated|deleted, highlight_added|updated|deleted,
story_added|updated|deleted, upload_status.
grain users # list workspace users
grain teams # list teams
grain meeting-types # list meeting typesConfig lives at ~/.config/grain/config.json (override with GRAIN_CONFIG_DIR or XDG_CONFIG_HOME),
written with 0600 permissions. Use profiles to switch between workspaces/accounts:
grain auth login --profile work --type wat
grain config use work # set the active profile
grain config profiles # list profiles
grain --profile personal recordings list
grain config path # print the config file location
grain config show # dump config (secrets redacted)
grain config set base_url https://api.grain.com- In a terminal you get aligned tables; when piped you get JSON — so
grain recordings list | jq …just works. - Force a format with
-o json|table|csv. - Narrow columns with
--fields id,title,duration. - Human status lines and progress go to stderr; data goes to stdout.
grain recordings list --all -o json | jq '.[].id'
grain users -o csv > users.csv| Variable | Purpose |
|---|---|
GRAIN_API_TOKEN |
API token (overrides the stored profile) |
GRAIN_BASE_URL |
API base URL |
GRAIN_API_VERSION |
Public-Api-Version header |
GRAIN_PROFILE |
default profile |
GRAIN_CONFIG_DIR |
config directory |
NO_COLOR |
disable colored output |
Grain allows 300 requests/minute. The client reads x-ratelimit-* headers, and on 429 (or transient
5xx/network errors) it retries with backoff, honoring Retry-After. Disable with --no-retry or tune
with --max-retries.
| Code | Meaning |
|---|---|
0 |
success |
1 |
API error / runtime failure |
2 |
usage / configuration error |
pnpm install
pnpm dev -- recordings list # run from source with tsx
pnpm typecheck
pnpm test # vitest (unit + live mock-server integration)
pnpm build # bundle to dist/ with tsupMIT