> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nekt.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Granola as a data source

> Bring your Granola meeting notes, AI summaries, transcripts, attendees, folders, spaces and audit log to Nekt.

Granola is an AI notepad for meetings: it transcribes your calls and turns your notes into AI-enhanced meeting notes. This connector reads your Granola workspace through the Granola API and brings every meeting note your API key can access (title, owner, calendar event, AI summary and your own notes), the full transcript of each meeting line by line, the attendees of each meeting, your folders and spaces and, on Enterprise plans, the workspace audit log.

> [!NOTE]
>
> The connector only reads data. It never creates, changes or deletes anything in Granola.

## Configuring Granola as a Source

In the [Sources](https://app.nekt.ai/sources) tab, click on the "Add source" button located on the top right of your screen. Then, select the Granola option from the list of connectors.

Click **Next** and you'll be prompted to add your access.

### 1. Add account access

The connector signs in with a Granola **API key**. API keys are available on Granola **Business** and **Enterprise** plans.

The key decides which notes the connector can read. There are two kinds of key:

* **Personal API key** (any workspace member): when you create it, choose its access scopes. **Personal notes** gives access to the notes you own, notes shared directly with you and notes in private folders shared with you. **Public notes** gives access to the notes visible to everyone in the workspace, such as the notes in the Team space. Select both to read both.
* **Workspace API key** (workspace admins only): belongs to the workspace instead of a person and never expires. It reads the public notes plus the notes of every space that has **Allow Granola API access** turned on.

To create a personal API key:

1. Open the Granola desktop app.
2. Go to **Settings → Connectors → API keys** and click **Create new key**.
3. Choose the note access scopes the key should include and click **Generate API Key**.
4. Copy the key (it starts with `grn_`).

To create a workspace API key, a workspace admin goes to **Settings → Connectors → Workspace API keys** and clicks **Create new key**. See [Granola's guide to API keys](https://docs.granola.ai/help-center/sharing/integrations/granola-api).

> [!WARNING]
>
> On Enterprise plans, a workspace admin chooses which access scopes members can use, in **Settings → Workspace → General → API access for members**. Admins can also turn off **transcript access over the API**: in that case, deselect the Note Transcripts stream, or the notes sync fails with a message asking to turn it back on.

The following configurations are available:

* **API Key**: The Granola API key (personal or workspace).
* **Start Date** (optional): On the first sync, only notes updated on or after this date are read (and audit events recorded after it). Leave it empty to read every note the key can access. Later syncs only read what changed since the previous one.
* **Audit API Key** (optional, advanced): Only needed for the Audit Events stream, on Enterprise plans. A workspace admin creates it in **Settings → Connectors → Audit API keys**. An Audit API key only reads the audit log, so the API Key above is still needed for the other streams. If you don't use the audit log, leave it empty and don't select Audit Events.
* **Notes Time Budget (minutes)** (advanced, default 180): The longest time an incremental sync spends reading notes. Each note needs its own requests and Granola allows 5 requests per second, so catching up after a long pause can take a while. When the budget is reached, the sync stops between two notes, keeps everything it read, and the next sync continues from there (oldest notes first). It never cuts the first sync or a full sync: those always read every note, so on large workspaces use the **Start Date** to keep the first sync short.

Once you're done, click **Next**.

### 2. Select streams

Choose which data streams you want to sync. For faster extractions, select only the streams that are relevant to your analysis. You can select entire groups of streams or pick specific ones.

> Tip: The stream can be found more easily by typing its name.

Select the streams and click **Next**.

### 3. Configure data streams

Customize how you want your data to appear in your catalog. Select the desired layer where the data will be placed, a folder to organize it inside the layer, a name for each table (which will effectively contain the fetched data) and the type of sync.

* **Layer**: choose between the existing layers on your catalog. This is where you will find your new extracted tables as the extraction runs successfully.
* **Folder**: a folder can be created inside the selected layer to group all tables being created from this new data source.
* **Table name**: we suggest a name, but feel free to customize it. You have the option to add a **prefix** to all tables at once and make this process faster!
* **Sync Type**: you can choose between INCREMENTAL and FULL\_TABLE.
  * Incremental: every time the extraction happens, we'll get only the new data, which is good if, for example, you want to keep every record ever fetched.
  * Full table: every time the extraction happens, we'll get the current state of the data, which is good if, for example, you don't want to have deleted data in your catalog.

Once you are done configuring, click **Next**.

### 4. Configure data source

Describe your data source for easy identification within your organization, not exceeding 140 characters.

To define your [Trigger](https://docs.nekt.com/runs/scheduling-and-triggers.md), consider how often you want data to be extracted from this source. This decision usually depends on how frequently you need the new table data updated (every day, once a week, or only at specific times).

Optionally, you can define some additional settings:

* Configure Delta Log Retention and determine for how long we should store old states of this table as it gets updated. Read more about this resource [here](https://docs.nekt.com/get-started/core-concepts/resource-control).
* Determine when to execute an **Additional [Full Sync](https://docs.nekt.com/get-started/core-concepts/types-of-sync#additional-full-sync)**. This will complement the incremental data extractions, ensuring that your data is completely synchronized with your source every once in a while. For Granola we recommend a weekly full sync of Notes, or monthly on large workspaces (see Data Quality Considerations).

Once you are ready, click **Next** to finalize the setup.

### 5. Check your new source

You can view your new source on the [Sources](https://app.nekt.ai/sources) page. If needed, manually trigger the source extraction by clicking on the arrow button. Once executed, your data will appear in your Catalog.

> [!WARNING]
>
> For you to be able to see it on your [Catalog](https://app.nekt.ai/catalog), you need at least one successful source run.

# Streams and Fields

## Available streams

The table below lists every stream, its **slug** (the exact identifier to pass when creating the source via API) and a short description.

| Stream | Slug | Sync | Description |
| - | - | - | - |
| Notes | `notes` | Incremental | Meeting notes with their AI summary, calendar event, attendees, folders and spaces. |
| Note Transcripts | `note_transcripts` | Incremental | The transcript of each note, one row per line, with who said it and when. |
| Note Attendees | `note_attendees` | Incremental | The attendees of each note, one row per attendee. |
| Folders | `folders` | Full table | Folders the API key can access, with their parent folder and space. |
| Spaces | `spaces` | Full table | Spaces the API key can access. |
| Audit Events | `audit_events` | Incremental | The workspace audit log (Enterprise plans, needs the Audit API Key). |

> [!NOTE]
>
> Granola only shares notes that already have a generated AI summary and transcript: a meeting that is still being processed appears on a later sync. Nested information (the calendar event, attendees, photos, folder and space membership, audit details) comes as JSON text; the most useful parts are also separate columns.

## Joining transcripts and attendees to notes

Note Transcripts and Note Attendees are updated together with their note. When a note changes and its new version has fewer transcript lines or fewer attendees, the rows it no longer has cannot be removed from your tables by an incremental sync: they stay, with the **older** `note_updated_at`. Every row of the current version has `note_updated_at` equal to the note's `updated_at`, so always join on both columns to get exactly the current transcript and attendees:

```sql theme={null}
SELECT
  n.id,
  n.title,
  t.item_index,
  t.speaker_name,
  t.text
FROM notes AS n
JOIN note_transcripts AS t
  ON t.note_id = n.id
 AND t.note_updated_at = n.updated_at
ORDER BY n.id, t.item_index
```

The same applies to attendees: `JOIN note_attendees AS a ON a.note_id = n.id AND a.note_updated_at = n.updated_at`.

## Fields by stream

### Notes

> [accordion] Notes
>
> Every note the API key can access that has a generated AI summary. Incremental by last update.
>
> Table: `notes` · Primary key: `id` · Sync: Incremental (`updated_at`)
>
> | Field | Type | Description |
> | :- | :- | :- |
> | `id` | String | Granola ID of the note (starts with `not_`). |
> | `title` | String | Title of the note. |
> | `owner_name` | String | Name of the user who owns the note (the note-taker). |
> | `owner_email` | String | Email of the user who owns the note. |
> | `created_at` | Datetime | When the note was created (UTC). |
> | `updated_at` | Datetime | When the note was last updated (UTC). |
> | `deleted_at` | Datetime | When the note was deleted, or empty. Only keys with access to all workspace notes see deleted notes. |
> | `web_url` | String | Address of the note in the Granola web app. |
> | `calendar_event_title` | String | Title of the calendar event the note was taken for. |
> | `calendar_event_id` | String | ID of the event in the connected calendar (Google or Outlook). |
> | `calendar_event_organiser` | String | Email of the organiser of the calendar event. |
> | `scheduled_start_time` | Datetime | Scheduled start of the calendar event (UTC). |
> | `scheduled_end_time` | Datetime | Scheduled end of the calendar event (UTC). |
> | `calendar_event` | String | The whole calendar event, including the invitees' emails (JSON). |
> | `attendees` | String | Attendees of the meeting, each with name and email (JSON). Also in the Note Attendees stream. |
> | `attachments` | String | Photos attached to the note, each with its address and size (JSON). The addresses change over time and anyone with one can open the photo. |
> | `folder_membership` | String | Folders the note is in, including their parent folders (JSON). |
> | `folder_ids` | Array | IDs of the folders the note is in. |
> | `space_membership` | String | Spaces the note is in (JSON). |
> | `space_ids` | Array | IDs of the spaces the note is in. |
> | `summary_text` | String | AI summary of the meeting, as plain text. |
> | `summary_markdown` | String | AI summary of the meeting, in Markdown. |
> | `private_notes_text` | String | The notes the owner typed during the meeting, as plain text. Only filled when the API key belongs to the note's owner. |
> | `private_notes_markdown` | String | The same notes, in Markdown. Only filled when the API key belongs to the note's owner. |
> | `content_available` | Boolean | False for a deleted note whose content Granola no longer shares: only title, owner and dates are filled. |

> [accordion] Note Transcripts
>
> The transcript of each note, one row per line. Read page by page with each note that changed.
>
> Table: `note_transcripts` · Primary key: `note_id`, `item_index` · Sync: Incremental (`note_updated_at`)
>
> | Field | Type | Description |
> | :- | :- | :- |
> | `note_id` | String | ID of the note the transcript belongs to. |
> | `item_index` | Integer | Position of the line in the transcript, starting at 0. |
> | `speaker_source` | String | Audio the line came from: `microphone` (the note-taker's microphone) or `speaker` (the other participants). iOS transcripts always say `microphone`. |
> | `speaker_attribution` | String | `me` (the note-taker) or `them` (other participants); empty when unknown. |
> | `speaker_diarization_label` | String | Anonymous speaker label such as "Speaker A" (iOS transcripts only). |
> | `speaker_name` | String | Name of the speaker, when Granola could identify them. |
> | `text` | String | What was said. |
> | `start_time` | Datetime | When the line started (UTC). |
> | `end_time` | Datetime | When the line ended (UTC). |
> | `note_updated_at` | Datetime | Last update of the note when its transcript was read. |

> [accordion] Note Attendees
>
> The attendees of each note, one row per attendee. Read together with the note, with no extra request.
>
> Table: `note_attendees` · Primary key: `note_id`, `email` · Sync: Incremental (`note_updated_at`)
>
> | Field | Type | Description |
> | :- | :- | :- |
> | `note_id` | String | ID of the note. |
> | `email` | String | Email of the attendee, in lowercase. |
> | `name` | String | Name of the attendee, when Granola knows it. |
> | `note_updated_at` | Datetime | Last update of the note when its attendees were read. |

### Organisation

> [accordion] Folders
>
> Every folder the API key can access. Read in full on every sync.
>
> Table: `folders` · Primary key: `id` · Sync: Full table
>
> | Field | Type | Description |
> | :- | :- | :- |
> | `id` | String | Granola ID of the folder (starts with `fol_`). |
> | `name` | String | Name of the folder. |
> | `parent_folder_id` | String | ID of the folder this one is nested in, or empty for a top-level folder. |
> | `space_id` | String | ID of the space the folder belongs to, or empty when it is not in a space (or the key cannot access that space). |

> [accordion] Spaces
>
> Every space the API key can access. Read in full on every sync.
>
> Table: `spaces` · Primary key: `id` · Sync: Full table
>
> | Field | Type | Description |
> | :- | :- | :- |
> | `id` | String | Granola ID of the space (starts with `spa_`). |
> | `name` | String | Name of the space. |

### Audit log

> [accordion] Audit Events
>
> The workspace audit log: who did what and when (sign-ins, note views, sharing changes, members joining or leaving, API and MCP use). Enterprise plans only, read with the Audit API Key. Granola keeps one year of events.
>
> Table: `audit_events` · Primary key: `id` · Sync: Incremental (`collected_at`)
>
> | Field | Type | Description |
> | :- | :- | :- |
> | `id` | String | Granola ID of the event (starts with `aud_`). |
> | `action` | String | The action recorded, such as `workspace.member_added`, `document.viewed` or `auth.login`. New actions appear over time. |
> | `occurred_at` | Datetime | When the action happened (UTC). |
> | `collected_at` | Datetime | When Granola recorded the event (UTC); later than `occurred_at` for events Granola learns about afterwards, such as notes viewed offline. |
> | `actor_type` | String | Who performed the action: `user`, `api_key`, `system` or `anonymous`. |
> | `actor_user_id` | String | Granola ID of the user who performed the action. |
> | `actor_email` | String | Email of the user who performed the action; empty if the account no longer exists. |
> | `actor_api_key_id_suffix` | String | Last eight characters of the ID of the API key that performed the action. |
> | `actor` | String | The actor as Granola sent it (JSON). |
> | `data` | String | Details of the action; the fields depend on the action (JSON). |
> | `ip_address` | String | IP address the request came from, when recorded. |
> | `user_agent` | String | User agent of the client, when recorded. |
> | `client_version` | String | Granola app version, when recorded. |

## Implementation Notes

### Data Quality Considerations

* **What the key can see is what you get.** Notes, folders and spaces depend on the key's access scopes and on the spaces with Granola API access turned on. Private notes typed by the owner are only filled when the key belongs to the note's owner.
* **Incremental syncs** read the notes updated since the previous sync, with one extra day of overlap for notes edited offline. Notes already loaded and not changed since are not read again.
* **Notes shared late.** A note shared with you long after its last edit (or a space whose Granola API access is turned on later) may keep its old update date and be missed by incremental syncs. Finding them on every sync would mean listing every note each time, so instead schedule an **Additional Full Sync** of Notes (weekly, or monthly on large workspaces) to pick them up.
* **Long catch-ups continue over several syncs.** When an incremental sync reaches the **Notes Time Budget**, it stops between two notes, keeps what it read and tells you in the sync log; the next sync continues with the rest, oldest notes first. The first sync and full syncs are never cut: they read every note (or every note since the Start Date) in one go, and if one fails the next attempt starts over.
* **Transcripts and attendees are refreshed with their note.** When a note changes, its transcript and attendees are read again. A line or attendee removed in Granola stays in your table with an older `note_updated_at` until a full sync: join on `note_updated_at` as shown in [Joining transcripts and attendees to notes](#joining-transcripts-and-attendees-to-notes).
* **Transcripts can be deleted by Granola.** If your workspace deletes transcripts automatically after a period, notes older than that period have no transcript; the sync log tells you how many notes came without one.
* **Deleted notes** appear with `deleted_at` filled only for keys with access to all workspace notes. When Granola no longer shares the content of a deleted note, the row keeps its title, owner and dates and `content_available` is false.
* **Folders and Spaces** are read completely before anything is loaded: a sync that fails halfway leaves the previous table in place. If one of them comes back completely empty after a sync that had records, that stream fails and its table is left as it was.

### API Limits & Performance

* Granola allows 5 requests per second per user or workspace (shared with your other Granola API integrations). The connector reads one request at a time below that limit and, when Granola asks it to slow down, waits as long as Granola says.
* Each note needs one request for its details, plus one request per 100 transcript lines. As a reference, a first sync of 1,000 one-hour meetings takes roughly half an hour to an hour. Set a **Start Date** to shorten the first sync and full syncs; later syncs only read what changed.

## Troubleshooting

| Error or symptom | What it means | What to do |
| :- | :- | :- |
| "Granola rejected the API key of this source" | The key is wrong or was revoked. | Create a new key in Granola (**Settings → Connectors → API keys**) and update the source. |
| "Granola answered that ... does not exist for the API key of this source" | The wrong kind of key was used (for example an Audit API key in the API Key field), or the feature is not on your plan. | Paste a personal or workspace API key in **API Key**, and the Audit API key only in **Audit API Key**. |
| "Granola does not allow the API key of this source to read 'note\_transcripts'" | A workspace admin turned off transcript access over the API. | Ask an admin to turn it back on, or deselect Note Transcripts so the notes keep syncing. |
| "The 'audit\_events' stream is selected but this source has no Audit API key" | Audit Events needs its own key (Enterprise plans). | Add the Audit API key in the source settings, or deselect Audit Events. |
| "Granola did not return a transcript for N of the M notes read" | Those transcripts were deleted (for example by your retention setting) or are not available. | No action needed. |
| "N notes were deleted in Granola, or stopped being shared with this API key, while this sync was running" | Notes disappeared between being listed and being read. | No action needed; the next sync no longer sees them. |
| "Granola returned no ... at all, although the previous sync read N" | Folders or Spaces came back empty, so that table was left as it was. | Check the key still has access. If they really were removed, run a full sync to accept the empty table; otherwise deselect that stream. |
| "This sync read N of the M Granola notes to update and stopped at its time budget" | The sync reached the Notes Time Budget. | No action needed; the next sync continues. To catch up sooner, raise the budget or run the source more often. |
| "Granola is limiting how fast this source may read" | Granola's rate limit was reached; the connector is waiting. | No action needed. If syncs are too slow, run the source less often or check other integrations using the same Granola API budget. |
| "Granola kept failing while reading ..." or "Granola did not answer ..." | The Granola API returned errors or timed out several times in a row. | Run the source again later. |
| "Granola answered ... in a format this connector does not recognise" | The Granola API changed. | Contact Nekt support. |

## Skills for agents

<Snippet file="agent-skills-intro.mdx" />

> [card] [Download Granola skills file](https://docs.nekt.com/sources/granola.md)
>
> Granola connector documentation as plain markdown, for use in AI agent contexts.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.