Skip to content

Granola Sensor ​

What it ingests ​

AI meeting notes from the Granola public API (https://public-api.granola.ai). One note becomes:

Granola dataOntology entity / edgeHydration
The noteMeeting, named <title> (<start time>)deterministic
The transcriptCreativeWork (source_type: transcript), Meeting PRODUCED itdeterministic
Attendees and the note ownerPerson via identity hints, ATTENDED the Meetingdeterministic
Calendar invitees who are not attendeesPerson, INVITED_TO the Meetingdeterministic
Bullets under the summary's Decisions headingDecision, Meeting DECIDED_ON itdeterministic
Bullets under Next steps / Action items / To-dosActionItem, Meeting PRODUCED it; Person ASSIGNED_TO it when the item starts with an attendee's name (Dana: send the deck, Dana to send the deck)deterministic
The calendar event Granola matchedthe existing calendar Event gets RELATED_TO the Meetingdeterministic
Topics, projects, organizations, technologiesfrom the transcriptLLM

The signal is note. Every row above is a binding you can turn off from the source's bindings.

Merged with your calendar. Granola records the provider's calendar event id on each note. When a Google Calendar or Outlook Calendar source has already ingested that event, the Granola Meeting is linked to it (Event RELATED_TO Meeting), the same join Google Meet recordings use. The calendar entry and its notes then share one neighborhood. A note with no calendar event, or one whose event was never ingested, keeps its Meeting without the link. There is no fuzzy title or time match.

Decisions and action items come from Granola's own summary, so they cost no tokens and match what the note shows. The LLM pass over the transcript adds topics and anything the summary left out. A decision both paths find lands on one Decision, not two.

Each note is stored once, in full, in sensor_events.content: the AI summary followed by the speaker-tagged transcript. Secrets spoken in a meeting (API keys, tokens, private-key blocks) are redacted before storage. The owner's private notes are never read.

Granola is the first AI-notetaker source. Otter and Fireflies use the same mapping, so a meeting looks the same in the graph whichever notetaker recorded it.

Credential ​

References a granola_api_key credential by id. The secret is a Granola API key (it starts with grn_), sent as Authorization: Bearer <key>.

bash
curl -sS -X POST https://your-corveil-host/api/credentials \
  -H "Authorization: Bearer <admin>" -H "Content-Type: application/json" \
  -d '{"type":"granola_api_key","name":"acme-granola","secret":"grn_…"}'

What the sensor can see depends on the kind of key:

KeyWho can create itWhat it reads
Workspace keyWorkspace admins, Business or Enterprise planPublic notes (shared with the whole workspace or in the Team space), plus spaces granted Granola API access. Never expires. Recommended.
Personal keyAny member on Business; on Enterprise, members an admin allowsThe creator's own notes, notes shared with them, and notes in private folders shared with them

Granola only returns notes that have finished generating their AI summary and transcript. A note still processing is picked up on a later poll.

Config fields ​

KeyRequiredNotes
folder_idnoA Granola folder id (fol_ + 14 characters). Ingests that folder and its sub-folders only. Blank reads every note the key can see.
space_idnoA Granola space id (spa_ + 14 characters). Workspace keys only.

A malformed id is rejected when you save the source.

There is no default_entity_type. This source emits several primary types (Meeting, transcript, decisions, action items), so one override would mistype the rest.

Provider-side setup ​

  1. Open the Granola desktop app and go to Settings → Connectors → API keys.
  2. Click Create new key. To create a workspace key you must be a workspace admin on a Business or Enterprise plan.
  3. Pick the access scopes. For a workspace key, grant API access to each space you want Corveil to read.
  4. Click Generate API Key and copy the key. Store only the grn_… value, not Bearer ….
  5. On Enterprise, if members cannot create keys, an admin enables it under Settings → Workspace → General → API access for members.
  6. Optional: to limit ingest to one folder, copy its id from Granola and set folder_id.

No webhooks, OAuth app, or admin consent are needed.

Wire-up ​

POST /api/sensors

json
{
  "sensor_type": "granola",
  "name": "Acme Granola",
  "config": {
    "folder_id": "fol_4y6LduVdwSKC27"
  },
  "credentials": { "granola_credential": "<granola_api_key credential id>" }
}

Verify ​

Run Test connection. It lists one note with the key, so a revoked or mistyped key fails at save rather than on the first poll.

Once polling, the per-poll summary log shows notes_listed, events, and the skip counts (skipped_deleted, skipped_gone, skipped_empty). In the ontology, look for Meeting entities with notetaker: granola. Each one's neighborhood should hold its attendees, its transcript, and its calendar Event when the calendar source has that event.

How incremental sync works ​

  • The first poll seeds a watermark and ingests nothing. History comes from the one-time backfill (SENSOR_BACKFILL_SINCE, default 72 hours), which reads notes by creation date.
  • Each poll lists notes with updated_after set to the last watermark minus a 10-minute overlap, then fetches each note with its transcript. The overlap catches notes whose timestamps trail the poll.
  • Edited notes are re-ingested. The event id includes the note's updated_at, so a regenerated summary or a retitled note lands as a new event and refreshes the same Meeting (the Meeting is keyed on the note id, granola:note:<id>).
  • Long transcripts are paged. When Granola refuses to inline a transcript (413 TRANSCRIPT_TOO_LARGE), the sensor reads it from /v1/notes/{id}/transcript page by page.
  • A busy window is never skipped. One poll reads at most 600 notes. If there are more, the watermark stays where it was and the next poll re-reads the window. A backfill that hits its ceiling fails and is retried.
  • Deletes. Notes marked deleted, and notes that return 404 between the list and the fetch, are skipped. Entities from earlier ingests are not removed.
  • Rate limits. Granola allows 25 requests per 5 seconds and 5 per second sustained, per user or workspace. The sensor paces itself under that. On a 429 it waits (Retry-After, or 5 seconds) and retries up to 3 times, then stops the poll without moving the watermark.

Known limits ​

  • Speakers. Granola names a speaker only when it identified them. Otherwise the transcript shows the note owner (me), Other participant, or a diarization label such as Speaker A (iOS).
  • Action-item owners are linked only when the item starts with an attendee's full name, a first name no other attendee shares, or their email. Legal: review the MSA is kept as an action item with no owner.
  • Calendar merge needs the calendar source. Without a Google Calendar or Outlook Calendar source, Meetings are not linked to Events. For Outlook, the link depends on Granola recording the same event id Outlook Calendar stores.
  • Not included: folders and spaces as entities, Granola webhooks, OAuth, the owner's private notes, and audit events.