grain
grain.
A meeting recording — list, fetch, rename, tag, or share one.
setup & usage
create an API key in Grain (app → Settings → Integrations → API — see the integration doc), then paste it into oto.
grain gives access to meeting recordings, transcripts and organization data, through 5 tools:
grain_recording(op="list", filter={"attendance": "hosted"}) — see the scope warning below, attendance is what restricts to "mine"grain_recording(op="get", recording_id="...", include={"ai_summary": true, "highlights": true})grain_transcript(recording_id="...", format="txt"|"vtt"|"srt")grain_recording(op="update"|"tag"|"share_user"|"share_team", recording_id="...", ...)grain_recording_file(op="download", recording_id="...")grain_hook(op="create", hook_url="https://...", hook_type="recording_added")grain_hook(op="create", hook_url="https://...", hook_type="highlight_added", include={"transcript": true})grain_org(op="users"|"teams"|"meeting_types")grain_recording(op="list", filter={"team": "...", "meeting_type": "...", "title_search": "onboarding"}) — the three filters combine, confirmed live (ids via grain_org)Verified live on 2026-08-20 on a real workspace: grain_recording(op="list") without an attendance filter returns the share_state="public" recordings of the whole organization, not only those of the token holder — observed concretely: meetings recorded by colleagues, where the PAT holder was not even a participant, came up first (chronological sort). This is the behavior documented by Grain ("Personal notes" + "Public notes: workspace-visible notes"), not a bug — but it is surprising if you expect a "my meetings only" scope.
To scope to "my meetings": pass filter={"attendance": "hosted"} (meetings hosted by the token holder) or "attended" (meetings they took part in) — confirmed live, these two values filter correctly. Without this filter, an agent that lists carelessly can surface a colleague's customer call in an answer.
list_users also returns the full workspace directory (all members, not only the token holder) — normal, it is a directory, not meeting content.
share_with_team expected team_id in the JSON body (like share_with_user), not in the URL path as the doc initially suggested — the documented form really returns 404. Fixed and locked in again by a testgrain_recording(op="get") uses a POST on Grain's side (not GET) — confirmed by both the doc and the live testtag/untag, update, share_*/unshare_*, webhook deletion) return {"success": true}, not the updated object — confirmed livegrain_recording via include={"highlights": true}grain_hook covers Grain's whole webhook surface — list/create/delete is all that exists on Grain's side (no PATCH/update, confirmed on the doc page, this is not partial coverage)grain_hook(op="create"): Grain tests the reachability of hook_url at creation — the URL must answer 2xx immediately or the call fails (confirmed live with a real test URL); for hook_type="highlight_added"|"highlight_updated", include accepts {"transcript": bool, "speakers": bool} (confirmed on the doc); story_* events are the ONLY way to observe Grain Stories — no REST endpoint lists/fetches them directly, the webhook payload IS the datainclude={"hubspot": true} on grain_recording returns {"hubspot_company_ids": [...], "hubspot_deal_ids": [...]}; include={"participants": true} also carries hs_contact_id per participant (null if not linked) — confirmed livegrain_recording_file(op="create_upload_url") gives the URL, sending the bytes happens outside the agent (the oto-core client carries upload_recording_file for scripted use; verified: it is a pre-signed S3 URL on a DIFFERENT host than api.grain.com, the Grain Bearer is never sent to it)this connector accepts multiple comptes: each stored credential becomes a named account (one name per compte), at your level, your team's or your org's.
_account="<name>" on the tool; list them: oto_identity(op='list', connector='grain') (scope='org' or scope='group' for the org's or team's)oto_identity(op='set', connector='grain', identity_id='<name>'); rename: op='rename' with new_name_account) and adding upoutils
utilisation
claude plugin marketplace add otomata-tech/oto-plugin — mcp + skill configurés.pipx install oto-cli puis oto grain …