Granola Configuration
Choose which Granola folders a connection reads, declare whether each is a team space or a private one, and map meeting participants to MemorySync users so a note becomes the right person's memory.
Operations on this page
| Operation | Method and path | Python | Node.js |
|---|---|---|---|
| List selectable folders | GET …/{connection_id}/granola/available-folders | connections.granola.available_folders | connections.granola.availableFolders |
| List selected folders | GET …/{connection_id}/granola/folders | connections.granola.folders | connections.granola.folders |
| Select folders | POST …/{connection_id}/granola/folders | connections.granola.add_folders | connections.granola.addFolders |
| Deselect a folder | DELETE …/{connection_id}/granola/folders/{folder_id} | connections.granola.remove_folder | connections.granola.removeFolder |
| Read the exclusion policy | GET …/{connection_id}/granola/exclusion-policy | connections.granola.exclusion_policy | connections.granola.exclusionPolicy |
| Replace the exclusion policy | PUT …/{connection_id}/granola/exclusion-policy | connections.granola.set_exclusion_policy | connections.granola.setExclusionPolicy |
| List identity mappings | GET …/{connection_id}/granola/identities | connections.granola.identities | connections.granola.identities |
| Link an identity | POST …/{connection_id}/granola/identities/link | connections.granola.link_identity | connections.granola.linkIdentity |
| Re-run identity matching | POST …/{connection_id}/granola/identities/relink | connections.granola.relink_identity | connections.granola.relinkIdentity |
| Read settings | GET …/{connection_id}/granola/settings | connections.granola.settings | connections.granola.settings |
| Update settings | PUT …/{connection_id}/granola/settings | connections.granola.set_settings | connections.granola.setSettings |
Authentication and scope
| Requirement | Contract |
|---|---|
| Credential | An API key sent as X-API-Key. Connector operations are not end-user scoped. |
| Read scope | integrations:read for every GET. |
| Write scope | integrations:write for every POST, PUT, PATCH and DELETE. |
| Tenant | Derived from the authenticated key. There is no tenant parameter to pass or to get wrong. |
X-End-User-ID | Not used. A connection belongs to the organization, not to one end user. |
Before you start
Granola is connected with an API key: use create_with_api_key on Connection Lifecycle. Everything here assumes connection["status"] == "connected".
1. See which folders are available
Lists the folders the key can see, annotated for a picker. It also reports whether private notes are enabled and how much of the participant roster is already matched — both of which decide what is safe to select.
import osfrom memorysync import MemorySyncClientclient = MemorySyncClient(api_key=os.environ["MEMORYSYNC_API_KEY"],base_url="https://api.memorysync.io",)available = client.connections.granola.available_folders("conn_8f21a4")print(available["total"], "folders")print("private notes enabled:", available["private_notes_enabled"])print("identity coverage:", available["identity_coverage"])for folder in available["folders"]:print(folder["id"], folder["name"], folder["selectable"], folder["reason"])
| Response field | Meaning |
|---|---|
folders | Each with id, name, path, parent_folder_id, approved, approved_visibility, selectable and reason. |
total | How many were found. |
private_notes_enabled | Whether this deployment permits indexing private folders at all. |
identity_coverage | A map of counts describing how much of the roster is mapped. |
2. Select folders, and declare their visibility
Each selected folder carries a visibility. This is the most consequential field on the page: it tells MemorySync whether a folder is a team space everyone can see or a restricted one shared with a few people, and that decides who the notes inside it can be surfaced to.
import osfrom memorysync import MemorySyncClientclient = MemorySyncClient(api_key=os.environ["MEMORYSYNC_API_KEY"],base_url="https://api.memorysync.io",)result = client.connections.granola.add_folders("conn_8f21a4",folders=[{"id": "fold_a1", "name": "Product Team", "visibility": "workspace"},{"id": "fold_b2", "name": "Leadership", "visibility": "private"},],)print(result["sync_triggered"], result["webhook_registered"])for approved in result["approved"]:print(approved["folder_id"], approved["visibility"], approved["state"])for rejected in result["rejected"]:print("rejected:", rejected)
| Request field | Contract |
|---|---|
folders | Required. A list of objects, each with id, and optionally name, parent_folder_id and visibility. |
visibility | Defaults to workspace. Use private for a folder shared with a subset of people. |
| Response field | Meaning |
|---|---|
approved | The folder records that were stored. |
rejected | Entries refused, with a reason. Surface these. |
sync_triggered | Whether selecting started a sync. |
webhook_registered | Whether a webhook was registered so later notes arrive without polling. |
3. Read back what is selected
Another bare-array response — iterate it directly.
import osfrom memorysync import MemorySyncClientclient = MemorySyncClient(api_key=os.environ["MEMORYSYNC_API_KEY"],base_url="https://api.memorysync.io",)for folder in client.connections.granola.folders("conn_8f21a4"):print(folder["folder_id"], folder["folder_name"], folder["visibility"], folder["state"])print(" notes synced:", folder["notes_synced"], "indexed:", folder["notes_indexed"])
| Field | Meaning |
|---|---|
id | The approval record. folder_id is Granola's own id. |
folder_name, folder_path, parent_folder_id | Where it sits. All nullable. |
visibility | Workspace or private, as declared. |
state, state_detail, is_active | Approval state and whether future syncs read it. |
notes_synced | Lifetime tally of notes read across every run. |
notes_indexed | Distinct notes currently stored. This is the real note count. |
last_updated_at, last_synced_at, authorized_at | Timestamps. |
4. Block folders by name pattern
The same pattern mechanism as Slack, applied to folder names: globs, case-insensitive, a leading # tolerated, up to 200 patterns.
import osfrom memorysync import MemorySyncClientclient = MemorySyncClient(api_key=os.environ["MEMORYSYNC_API_KEY"],base_url="https://api.memorysync.io",)client.connections.granola.set_exclusion_policy("conn_8f21a4",tenant_patterns=["1:1*", "interview*", "personal*"],)policy = client.connections.granola.exclusion_policy("conn_8f21a4")print(policy["tenant_patterns"], policy["effective_patterns"])
| Field | Meaning |
|---|---|
tenant_patterns | Request and response. What you set; the only field PUT reads. |
deployment_patterns | Response only. The operator-set floor. |
effective_patterns | Response only. Both combined. |
5. Map participants to MemorySync users
A Granola note is a meeting with participants. Without a mapping, a note is not attributed to a person, so querying as that person will not find it. Mapping is by email.
import osfrom memorysync import MemorySyncClientclient = MemorySyncClient(api_key=os.environ["MEMORYSYNC_API_KEY"],base_url="https://api.memorysync.io",)roster = client.connections.granola.identities("conn_8f21a4")print(roster["total"], "matched:", roster["matched"], "unmatched:", roster["unmatched"])for identity in roster["identities"]:print(identity["granola_email"], identity["is_external"], identity["match_method"])client.connections.granola.link_identity("conn_8f21a4",granola_email="sam@example.com",memorysync_user_id=4821,)
| Field | Meaning |
|---|---|
granola_email | Request and response. The participant's email in Granola. Required on link. |
memorysync_user_id | The numeric MemorySync user to attribute notes to. Omit or send null to unlink. |
granola_name, memorysync_email | Response only. For display. |
is_external | Response only. Whether the participant is outside your organization. |
match_method | Response only. How the mapping was arrived at — automatic or explicit. |
| Roster field | Meaning |
|---|---|
identities | The participants. |
total, matched, unmatched | Coverage. Drive an onboarding checklist from unmatched. |
6. Re-run the matching
Re-matches the participant roster so newly added people are picked up. It takes no arguments and creates no mappings by itself — explicit links stay explicit.
import osfrom memorysync import MemorySyncClientclient = MemorySyncClient(api_key=os.environ["MEMORYSYNC_API_KEY"],base_url="https://api.memorysync.io",)roster = client.connections.granola.relink_identity("conn_8f21a4")print(roster["matched"], "of", roster["total"])
7. Decide whether transcripts are indexed
The settings surface is one switch plus three read-only values. index_transcripts is the significant one: a transcript is far longer than a summary and produces far more content, most of it conversational filler.
import osfrom memorysync import MemorySyncClientclient = MemorySyncClient(api_key=os.environ["MEMORYSYNC_API_KEY"],base_url="https://api.memorysync.io",)settings = client.connections.granola.settings("conn_8f21a4")print(settings["index_transcripts"], settings["history_days"])print(settings["max_notes_per_sync"], settings["webhook_registered"])client.connections.granola.set_settings("conn_8f21a4", index_transcripts=False)
| Field | Meaning |
|---|---|
index_transcripts | The only writable field. PUT requires it. |
history_days | Read-only. How far back a first sync reaches. |
max_notes_per_sync | Read-only. The ceiling on one run. |
webhook_registered | Read-only. Whether new notes arrive by webhook rather than polling. |
8. Deselect a folder
Stops future syncs reading it. Notes and memories already imported are kept.
import osfrom memorysync import MemorySyncClientclient = MemorySyncClient(api_key=os.environ["MEMORYSYNC_API_KEY"],base_url="https://api.memorysync.io",)client.connections.granola.remove_folder("conn_8f21a4", "fold_b2")
Errors and next action
| Status | Meaning | Next action |
|---|---|---|
401 | Missing, malformed or inactive API key. | Check server configuration without printing the key. |
403 | The key lacks integrations:read or integrations:write. | Grant the scope on the key, or use a key that has it. |
404 | The connection, object or job is not visible to this tenant. | Confirm the identifier belongs to this organization. |
409 | The connection is in a state that forbids the operation. | Read the connection status first and act on it. |
429 | Rate limited, either by MemorySync or by the upstream provider. | Back off; do not tighten a polling loop in response. |
5xx | Service failure. | Treat a write outcome as uncertain and reconcile by reading the connection back. |