Granola Sensor
What it ingests
AI meeting notes from the Granola public API (https://public-api.granola.ai). One note becomes:
| Granola data | Ontology entity / edge | Hydration |
|---|---|---|
| The note | Meeting, named <title> (<start time>) | deterministic |
| The transcript | CreativeWork (source_type: transcript), Meeting PRODUCED it | deterministic |
| Attendees and the note owner | Person via identity hints, ATTENDED the Meeting | deterministic |
| Calendar invitees who are not attendees | Person, INVITED_TO the Meeting | deterministic |
| Bullets under the summary's Decisions heading | Decision, Meeting DECIDED_ON it | deterministic |
| Bullets under Next steps / Action items / To-dos | ActionItem, 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 matched | the existing calendar Event gets RELATED_TO the Meeting | deterministic |
| Topics, projects, organizations, technologies | from the transcript | LLM |
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>.
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:
| Key | Who can create it | What it reads |
|---|---|---|
| Workspace key | Workspace admins, Business or Enterprise plan | Public notes (shared with the whole workspace or in the Team space), plus spaces granted Granola API access. Never expires. Recommended. |
| Personal key | Any member on Business; on Enterprise, members an admin allows | The 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
| Key | Required | Notes |
|---|---|---|
folder_id | no | A Granola folder id (fol_ + 14 characters). Ingests that folder and its sub-folders only. Blank reads every note the key can see. |
space_id | no | A 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
- Open the Granola desktop app and go to Settings → Connectors → API keys.
- Click Create new key. To create a workspace key you must be a workspace admin on a Business or Enterprise plan.
- Pick the access scopes. For a workspace key, grant API access to each space you want Corveil to read.
- Click Generate API Key and copy the key. Store only the
grn_…value, notBearer …. - On Enterprise, if members cannot create keys, an admin enables it under Settings → Workspace → General → API access for members.
- 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
{
"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_afterset 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}/transcriptpage 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
429it 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 asSpeaker 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 MSAis 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.