Skip to main content

Granola

Granola covers meetings Fathom's bot doesn't catch — for example, calls where the Fathom bot got ejected or wasn't invited. It's a secondary, gap-filling source: Fathom stays the primary meeting-truth connector, and Granola notes that overlap an existing non-Granola meeting are deliberately skipped.

How it works

granola-sync polls the official Granola API (public-api.granola.ai/v1). As of when this was last checked, Granola has no webhook product — polling is the only feed, which is why this connector runs more frequently than a typical reconciliation poller.

Each run:

  1. Lists notes updated within the lookback window (lookbackHours, 1–720, default 72 — the wide default window exists because Granola hides a note from the API until its summary and transcript generation finish, so a note can appear "late" relative to when the meeting actually happened).
  2. For each note, checks whether it overlaps (±30 minutes) an existing non-Granola meeting source (coveredByExistingMeeting). If it does, the note is skipped — this is the gap-filler rule that keeps Fathom primary and prevents duplicate meeting records when both tools recorded the same call.
  3. Ingests the remainder via the same shared ingestMeeting helper Fathom uses.
  4. Emits activity_events (soft-fail) and writes a connector_checkpoints row.

Outbound requests to the Granola API retry on 429/5xx responses up to 3 attempts, honoring the retry-after header when present.

The function also runs within a wall-clock time budget (timeBudgetMs, 10s–140s, default 110s), the same bounded-ingest pattern used by Fathom and HubSpot.

Configuration

SettingValue
SecretGRANOLA_API_KEY — edge secret, falling back to a Vault row if unset
OnboardingClient generates a workspace API key in Granola → Settings → Connectors → API keys
StatusBlocked on that client key until it's provided; otherwise built and ready

Cron cadence

TaskScheduleParams
sable.granola.pollTwice hourly, :05 and :35 (5,35 * * * *){limit:25, lookbackHours:72}

Table mapping

TableWhat's written
sourcestype='meeting', via ingestMeeting — same schema as Fathom-sourced meetings
decisions, promises, tasksExtracted per meeting, same pipeline as Fathom
activity_eventsIngest activity trail (soft-fail emission)
connector_checkpointsHealth, high-water-mark bookkeeping for the granola row

sources is also read (not just written) — the coverage check queries existing meeting sources to decide whether a Granola note overlaps one before ingesting it.

Request shapes

{ "dryRun": true }
{ "limit": 25, "lookbackHours": 72 }

Why polling instead of a webhook

Every other meeting connector in this section (Fathom) is built around a signed webhook as the primary path, with polling as reconciliation. Granola inverts that: polling is the only feed, because the Granola API doesn't offer a webhook product to subscribe to. That's also why this connector's schedule runs twice as often as Fathom's reconciliation sweep (every 30 minutes versus every 4 hours) — without a push notification, the polling cadence is the only lever available to keep latency reasonable.

This also shapes the lookback window. A webhook-fed connector only needs to reconcile a short trailing window, because most items arrive promptly on delivery. Granola's 72-hour default lookback exists because notes can appear late relative to when the meeting happened — Granola doesn't surface a note via its API until summary and transcript generation finish server-side, which can lag the actual call by hours. A shorter lookback would silently miss those late arrivals rather than catching them on a subsequent poll.

What can go wrong

  • Granola is enabled but nothing ingests. Check that GRANOLA_API_KEY is actually set — this connector ships "Built, needs client key," meaning the code path is live but blocked until the client generates and hands over a workspace API key from their Granola settings.
  • A meeting seems to be missing even though Granola recorded it. If Fathom also recorded that call, this is expected — the gap-filler rule intentionally skips a Granola note that overlaps an existing non-Granola meeting within 30 minutes, so Fathom's version stays the single source of truth for that meeting.
  • A note appears days after the meeting happened. This is normal for Granola: the API doesn't expose a note until its summary and transcript finish generating on Granola's side. The 72-hour lookback window exists specifically to catch these late arrivals on a later poll rather than requiring an immediate re-run.
  • Repeated 429s during a poll. The client already retries up to 3 times honoring retry-after; if failures persist past that, check connector_checkpoints for the granola row's error and confirm the workspace API key hasn't been rotated or revoked on Granola's side.

Where the code lives

  • supabase/functions/granola-sync/index.ts — poller and ingest
  • supabase/functions/fathom-sync/ingest.ts — shared ingestMeeting logic used by both Granola and Fathom
  • trigger/connectors.tssable.granola.poll schedule