/

Data Integration

Best Way to Sync Granola to Postgres (2026)

Intergalactic Data Labs

โ€”

โ€”

14 min read

Table of contents

Summarize

Granola meeting notes land in Postgres one of five ways as of October 2026. The options are Airbyte's community source, Bruin's ingestr, Filament's manifest connector, a Zapier trigger, or a script against the public API. Transcript completeness, deletion handling, and how typed the destination tables end up are what decide between them. None of the big hosted ELT vendors ship a Granola connector yet.

Method

Transcripts

Incremental

Deletes

Tables

Airbyte source

Paged, append only

Notes

No

Final tables plus metadata columns

ingestr

Inline JSON column

Notes

No

notes and folders

Filament

Paged, one row per item

Notes

Via full replace

Typed, add-only

Zapier

In trigger payload

New notes only

No

You design

Own script

Your code

Your code

Your code

You design

What the Granola API gives you

Granola's public API, released in February 2026, is a REST interface for notes, transcripts, folders, and webhooks. The changelog shows it moved quickly through versions 1.1 to 1.5 this year, adding folders, access scopes, speaker attribution, a paged transcript endpoint, and private notes.

The API only hands over finished meetings, and only the ones your key can see. Granola's introduction is blunt about the first rule. "The API only returns notes that have a generated AI summary and transcript." Notes still processing are absent from List Notes and return a 404 from Get Note.

Keys come in two shapes. A personal key reads notes you own or that were shared with you, while a workspace key reads public workspace notes and spaces with API access enabled. Notes are private by default, and Granola's API help page says workspace keys cannot read private folders that were never shared.

Four endpoints carry the data. List Notes pages through IDs and titles with an updated_after filter, Get Note adds summaries, attendees, and folder membership. Get Transcript pages through transcript items with speaker and timestamps, and List Folders returns the tree through parent_folder_id.

Two limits shape every tool built on top. Rate limits are 5 requests per second sustained with a short burst allowance of 25, applied per user or workspace, per the introduction.

The second limit is transcript size. Get Note returns a 413 with the code TRANSCRIPT_TOO_LARGE on long meetings, which is why the paged transcript endpoint exists.

Plan gating is the other hard wall. Granola's pricing page lists API access as not included on Basic and included on Business and Enterprise. There is no sandbox, so Granola's own advice is to test against a dedicated folder with a handful of notes.

What decides the right method

Five criteria separate a sync you can trust from one you will rebuild. They come straight from how the Granola API behaves, so they apply to every tool.

Criterion

Why it matters for Granola

Transcript completeness

Inline transcripts can fail with a 413, so only the paged endpoint reads every meeting

Keyless transcript rows

Transcript items have no ID, so an upsert cannot deduplicate them

Deletion handling

updated_after never reports a deleted note, so something must take a fresh snapshot

Request budget

Each note costs one detail call and at least one transcript call per full run inside a shared 5 per second limit

Destination shape

Typed columns and a primary key per table, or one JSON blob you parse later

Deletion deserves the extra sentence. Airbyte's Granola docs state that the API does not return deleted notes or mark them as deleted, so no cursor-based read, from any vendor, will ever remove a row. The method either schedules a full replace or you accept drift.

Upstream retention makes that worse. Granola lets workspaces auto-delete transcripts after as little as one day, and the deletion is permanent. If transcripts matter, the sync is the backup, so it needs to run before the retention window closes.

Every way to sync Granola to Postgres

Two community connectors, one event trigger, one hand-built path, and one typed-table engine actually run this route today. Fivetran, Hevo, Stitch, and Estuary all return nothing for Granola in their connector catalogs, and dlt has no verified source, so those paths mean building the connector yourself.

Tool

Granola connector

Deployment

License

Pricing unit

Airbyte

Yes, alpha, community

Cloud or self-hosted

ELv2

Credits

ingestr

Yes, notes and folders

CLI you run

FSL-1.1-ALv2

Free CLI

Filament

Yes, alpha

Binary, Go library, Helm

Apache 2.0

None from the tool

Zapier

Two triggers

Hosted

Proprietary

Tasks

Fivetran

No, Connector SDK

Hosted

Proprietary

Monthly active rows

dlt

No, REST API source

Library you run

Apache 2.0

Free library, dltHub paid

Airbyte

Airbyte's Granola source reached version 1.0.0 on October 2, 2026, and its metadata file marks it alpha with community support under ELv2. The notes stream is incremental on updated_at, while detailed notes and transcripts are full refresh. It is a real, maintained connector that runs in both Cloud and the self-hosted Core edition.

The limits are documented on the same page. There is no folders stream, every sync re-requests details for every note in range, and notes with an oversized transcript produce no detailed_notes record at all. Deleted notes stay until a full refresh with overwrite. The Postgres destination docs add four airbyte metadata columns and warn that Postgres "is not a data warehouse" and suits volumes under 10 GB.

ingestr

Bruin's ingestr supports Granola as a source for notes and folders, with incremental notes merged on updated_at and a Postgres destination. One CLI command moves both tables. Transcripts arrive inline through include=transcript and land as a JSON column inside the notes table, which the docs qualify with "when Granola returns them". There is no per-segment transcript table, and the license is the Functional Source License, not Apache.

Zapier

Granola's Zapier app offers two triggers, one when a note is added to a folder and one when a note is shared to Zapier. The payload includes the summary in Markdown and the full transcript, per Granola's Zapier guide, which makes it the fastest path to one row per meeting.

The trigger only fires for notes added after the Zap exists, so there is no backfill, no folder sync, and no re-send when a summary is later edited. Zapier bills per task, so cost tracks meeting volume.

Webhooks or your own script

Granola's webhooks fire on note.generated, note.edited, and note.access_granted, carry no note content, and retry for four days. Pair them with a receiver that calls Get Note and Get Transcript, or poll List Notes with updated_after on a schedule. Either way you write the pagination, the 413 fallback, the backoff, the idempotent upsert, and the deletion reconciliation yourself.

Where Filament fits

Filament reads Granola through a YAML HTTP manifest, writes typed Postgres tables, and clears every criterion above except maturity, which its docs state plainly. Here is how it scores against each one, with the connector's source page and the Postgres sink page as the evidence.

Criterion

What Filament does

Transcript completeness

Reads the paged endpoint 100 items at a time, one row per item

Keyless transcript rows

Docs direct full replace for transcripts

Deletion handling

Full replacement removes deleted notes and refreshes folder membership

Request budget

5 requests per second enforced in the manifest, 429 retried honoring Retry-After

Destination shape

Typed tables, primary keys preserved, jsonb for nested objects, add-only evolution

Transcripts are where the design shows. The source page says to "Use full replace for transcripts. Granola does not give transcript items stable IDs, and speaker names and timestamps are not guaranteed to be unique." That is the right call for an API whose transcript schema has no item ID, and it is the same reason Airbyte's transcript stream has no primary key.

The Postgres sink is beta and derives native column types from the source schema, preserving primary keys and NOT NULL constraints. Upserts run as a binary COPY into a temporary table merged with ON CONFLICT inside one transaction, so retries converge. New source fields appear through ADD COLUMN IF NOT EXISTS, and evolution is add-only.

Every batch is also checksummed in memory. Per the integrity and checkpoints page, a CRC32-C mismatch between the pipeline and the sink fails the run, and checkpoints after each written batch let an interrupted incremental run resume.

Maturity is the fifth criterion and the honest caveat. The Granola source is alpha today, per its docs, checked against Granola's API docs and tested with mock responses but not yet against a live account, with the Postgres sink at beta. Filament is Apache 2.0 and pre-1.0, and Galaxy builds and maintains it. No speed claim applies to this route, because Filament's benchmark program has only measured database-to-database full loads.

How to sync Granola to Postgres with Filament

One YAML file, one API key, and two pipelines cover the whole route. The steps below follow the CLI guide and the installation page, with Granola fields substituted by the documented rule that connector fields become config keys and secrets can read from the environment.

Step 1. Create a Granola API key

In Granola open Settings, then Connectors, then API keys, and create a key with the scopes you need. A workspace key covers team folders, while a personal key is the only way to read your own private notes. Export it as GRANOLA_API_KEY and keep a Postgres DSN in POSTGRES_SINK_DSN.

Step 2. Install the CLI
curl -fsSL https://getgalaxy.io/filament/install | sh

curl -fsSL https://getgalaxy.io/filament/install | sh

curl -fsSL https://getgalaxy.io/filament/install | sh

The local CLI runs on file-backed SQLite with an in-process event bus, so it needs no Postgres or NATS of its own. Homebrew users can run brew install galaxy-io/tap/filament instead.

Step 3. Describe the route in filament.yaml

This file lives at ~/.config/filament/filament.yaml and mirrors the documented local config shape.

version: 1

sources:
  granola:
    type: granola
    config:
      api_key: env:GRANOLA_API_KEY

sinks:
  warehouse:
    type: postgres
    config:
      connection_method: url
      dsn: env:POSTGRES_SINK_DSN

pipelines:
  granola-snapshot:
    source:
      ref: granola
    sink:
      ref: warehouse
    resources:
      - notes
      - note_details
      - transcripts
      - folders
    sync_mode: full
    write_mode: replace

  granola-notes-catchup:
    source:
      ref: granola
    sink:
      ref: warehouse
    resources:
      - notes
    sync_mode: incremental
    write_mode

version: 1

sources:
  granola:
    type: granola
    config:
      api_key: env:GRANOLA_API_KEY

sinks:
  warehouse:
    type: postgres
    config:
      connection_method: url
      dsn: env:POSTGRES_SINK_DSN

pipelines:
  granola-snapshot:
    source:
      ref: granola
    sink:
      ref: warehouse
    resources:
      - notes
      - note_details
      - transcripts
      - folders
    sync_mode: full
    write_mode: replace

  granola-notes-catchup:
    source:
      ref: granola
    sink:
      ref: warehouse
    resources:
      - notes
    sync_mode: incremental
    write_mode

version: 1

sources:
  granola:
    type: granola
    config:
      api_key: env:GRANOLA_API_KEY

sinks:
  warehouse:
    type: postgres
    config:
      connection_method: url
      dsn: env:POSTGRES_SINK_DSN

pipelines:
  granola-snapshot:
    source:
      ref: granola
    sink:
      ref: warehouse
    resources:
      - notes
      - note_details
      - transcripts
      - folders
    sync_mode: full
    write_mode: replace

  granola-notes-catchup:
    source:
      ref: granola
    sink:
      ref: warehouse
    resources:
      - notes
    sync_mode: incremental
    write_mode

The snapshot pipeline is the one that removes deleted notes and rebuilds transcripts cleanly. The catch-up pipeline sends the saved updated_at as updated_after with a five-minute lookback under the checkpoint key notes_updated_at, so it stays cheap between snapshots.

Step 4. Discover and run
filament source

filament source

filament source

Discovery lists the five resources the manifest exposes. The first run creates a schema named after the source connection, so granola here, then one typed table per resource, and streams progress until it finishes. Partial data is visible mid-run because batches commit as they apply, which is normal for this sink.

Step 5. Verify in Postgres
SELECT count(*) FROM granola.notes;
SELECT note_id, count(*) AS segments
FROM granola.transcripts
GROUP BY note_id
ORDER BY segments DESC
LIMIT 5

SELECT count(*) FROM granola.notes;
SELECT note_id, count(*) AS segments
FROM granola.transcripts
GROUP BY note_id
ORDER BY segments DESC
LIMIT 5

SELECT count(*) FROM granola.notes;
SELECT note_id, count(*) AS segments
FROM granola.transcripts
GROUP BY note_id
ORDER BY segments DESC
LIMIT 5

The transcripts table holds one row per spoken segment with speaker as jsonb, so attribution and diarization labels are queryable. Summaries sit in note_details.summary_markdown, and attendees and folder_membership are jsonb columns you can unnest.

Step 6. Schedule it

Run the catch-up pipeline hourly and the snapshot nightly, or attach a schedule through the pipelines API. Teams embedding the engine call app.Run from Go per the embedded guide, and Kubernetes with Helm is the documented production path.

Common pitfalls with any tool

Most failed Granola syncs are scope or timing problems, not tool problems. These five show up regardless of which method you pick.

  • An empty result set usually means the key's scopes do not cover the folder, or an admin turned off public folder access for the workspace.

  • A meeting that just ended is missing because the summary has not generated yet, so do not alert on a lag shorter than Granola's processing time.

  • Transcripts duplicate when a tool upserts keyless rows, because repeated keyless rows accumulate on every full read unless the write is a replace.

  • The 5 per second limit is shared across every client on the same key scope, so a sync, an MCP session, and a Zap all draw down the same quota.

  • Private notes come back null from workspace keys, so a sync that needs the owner's own notes has to run under that owner's personal key.

Which method fits which job

Pick by the constraint that breaks first. For a team that wants typed tables, full transcripts, and deletions handled without writing code, Filament is the pick, tested first in a dedicated folder as Granola itself advises.

Job

Pick

Why

Typed tables with full transcripts and folders

Filament

Paged transcripts, replace-based deletion, add-only typed schema

Already on Airbyte Cloud or Core

Airbyte

Maintained alpha source, incremental notes

Notes and folders only, one command

ingestr

Merge on updated_at, transcript as JSON column

One row per new meeting into an existing app

Zapier

Payload carries summary and transcript

Custom shape or near real-time

Webhooks plus script

Three events, four-day retry

Related guides

Frequently asked questions

Does Granola have an API?

Yes. Granola shipped a public REST API in February 2026. It lists notes, returns note details with summaries and attendees, pages through transcripts, lists folders, and reads webhook configuration. Every call is a bearer request to public-api.granola.ai with a key that starts with grn_. Keys require a Business or Enterprise plan, and there is no sandbox environment.

Which Granola plan do I need to sync notes to a database?

Business or Enterprise. Granola's pricing page lists API access as not included on Basic and included on Business and Enterprise. Zapier and the native Notion, Slack, and CRM integrations also sit on Business and above. Enterprise admins can additionally restrict which access scopes members may put on a key.

Can I get full meeting transcripts out of Granola?

Yes, through the paged transcript endpoint. Get Note can return a transcript inline, but returns a 413 with the code TRANSCRIPT_TOO_LARGE when the meeting is long. The dedicated endpoint pages through transcript items 100 at a time, each with speaker, text, start time, and end time, so a tool that uses it reads every meeting in full.

Why does the Granola API return no notes?

Almost always scope. A key only returns notes that match its access scopes, notes are private by default, and workspace keys cannot read private folders. The API also hides any note that has no generated summary and transcript yet. Check the key's scopes, the folder's visibility, and whether the meeting has finished processing before suspecting the tool.

Does Airbyte support Granola?

Yes. Airbyte published a community Granola source at version 1.0.0 on October 2, 2026. It is alpha, manifest-only, and syncs notes incrementally on updated_at, plus detailed notes and transcripts as full refreshes. It has no folders stream, cannot report deletions, and its Postgres destination adds four airbyte metadata columns to every table.

Can Fivetran or dlt sync Granola to Postgres?

Not with a built-in connector. Fivetran's connector catalog has no Granola entry, so you would write one with its Connector SDK. dlt has no Granola verified source, so you would configure its generic REST API source by hand. Both routes mean you own the pagination, the 413 fallback, and the lookback logic yourself.

How does Filament handle Granola transcripts?

It reads them from the paged transcript endpoint, 100 items per request, and writes one row per transcript item with the note ID, speaker, text, and start and end times. Because Granola gives transcript items no stable ID, the docs tell you to write transcripts with full replace so every run produces a clean snapshot rather than accumulating duplicates.

Does Filament sync Granola incrementally?

Notes do. Each run sends the saved updated_at checkpoint as updated_after with a five-minute lookback, under the key notes_updated_at. Note details, transcripts, folders, and webhook configuration are read in full. Incremental reads cannot see deletions, so pair the incremental pipeline with a scheduled full replace run.

What tables does a Granola to Postgres sync produce?

With a typed-table tool you get notes, note_details, transcripts, folders, and optionally webhook_endpoints. Notes carry IDs, titles, owners, and timestamps. Note details add summary text and Markdown, attendees, calendar details, and folder membership as jsonb. Transcripts hold one row per spoken segment. Folders include parent_folder_id so you can rebuild the hierarchy in SQL.

Is Granola's CSV export enough instead of a sync?

Only for a one-off backup of your own notes. The export is emailed within a few hours, is limited to one request per account every 24 hours, includes only notes you own, and has no admin or workspace-wide version. Enterprise plans disable it by default. A team table in Postgres needs the API.

More articles

Stay up to date with what weโ€™re building

Stay up to date with what weโ€™re building

Stay up to date with what weโ€™re building

Questions

Answered

FAQ

What does Galaxy do?

What is Filament?

What is enterprise context management?

What does working with Galaxy look like?

How do you handle security and compliance?

Why does Galaxy build in the open?

Own your knowledge stack

Own your knowledge stack

Copyright ยฉ 2026 Galaxy. All rights reserved.