Edge harvesters
Some signal can only be read from the device it lives on — Granola meeting transcripts, for instance, sit in ~/Library/Application Support/Granola/cache-v3.json on whichever Mac is running Granola. The host can’t reach them over the network.
The edge harvester pattern solves this by inverting the data flow: the device pushes to the host, authenticated with a per-device bearer token.
The pairing flow
Section titled “The pairing flow”A workspace can register multiple devices per edge connector. Each gets its own bearer token. Pairing happens once:
Desktop Host │ │ │ 1. POST /api/v1/harvester-devices/ │ │ pairing-codes │ │ { connector, deviceName } │ │ ──────────────────────────────────────► │ │ │ │ ◄────────────────────────────────────── │ │ { code: "ABCD-1234" } │ │ │ │ 2. User reads the code aloud / types it │ │ on the host's terminal │ │ │ │ 3. POST /api/v1/harvester-devices/claim │ │ { code: "ABCD-1234" } │ │ ──────────────────────────────────────► │ │ │ │ ◄────────────────────────────────────── │ │ { token: "<plaintext, only sent │ │ once>" } │ │ │ │ 4. Push data with Authorization: Bearer │ │ POST /api/v1/granola/sync │ │ ──────────────────────────────────────► │What the host stores
Section titled “What the host stores”The harvester_devices table stores only the sha256 of each device’s push token. The plaintext is returned at claim time and never persisted. If a device is lost, you can soft-revoke via DELETE /api/v1/harvester-devices/:id (sets revoked_at); the next push from that device gets 401.
The auth check
Section titled “The auth check”Every push route calls authenticateHarvesterDevice(request, connectorName) which does a timing-safe sha256 comparison against every non-revoked row for the workspace + connector. The check is constant-time so probing for valid tokens via timing isn’t viable.
Strict mode
Section titled “Strict mode”The HARVESTER_DEVICE_AUTH_REQUIRED env flag (default off) gates strict enforcement:
- Off: if no device has ever been paired for this workspace/connector, unauthenticated pushes are accepted with a deprecation warning. Lets a fresh install run Granola without going through pairing first.
- On: any push without a valid bearer token gets 401, regardless of pairing history.
Flip to on once all your devices have completed pairing.
Manifest requirements
Section titled “Manifest requirements”Connectors with authType: "edge" MUST populate edgeHarvesterDocs in their manifest:
export const manifest: ConnectorManifest = { name: "granola", displayName: "Granola", description: "Sync meeting notes from the Granola macOS app.", tier: "core", authType: "edge", edgeHarvesterDocs: { setupSummary: "Run Granola on a Mac and pair the device through the Desktop UI.", pushEndpoint: "/api/v1/granola/sync", pushIntervalLabel: "every 15 minutes", }, filterSchema: { /* … */ },};The Admin SPA’s Edge Harvesters tab and the Desktop pairing UI render from these fields — there’s no hardcoded per-connector UI.
Full spec
Section titled “Full spec”See the edge harvesters spec in the repo for the complete protocol including pairing-code TTL, error envelopes, and the full revocation flow.