Skip to main content

A local FastAPI mock of the Granola API for testing ingestion pipelines without a real Granola workspace.

Project description

mock-granola-api

This is a public-source project — see CONTRIBUTING.md for the PR policy.

A local FastAPI mock of the Granola API (meeting notes, transcripts, folders, webhooks) for testing ingestion pipelines without a real Granola workspace. It mirrors the real API's schemas, ID formats, error shapes, pagination, rate limits, and signed webhook delivery closely enough that client code written against it runs unmodified against the real API — only a base URL and an API key change.

On top of the real API surface, an /admin/* control-plane (mock-only, no auth) lets you script synthetic data — seed random notes, create specific notes with exact content, edit them, and trigger the same events (note.generated, note.edited, note.regenerated, note.access_granted) a real Granola workspace would fire.

Quickstart

make run    # starts the mock at http://127.0.0.1:8000
make test   # runs the test suite

API surface

Public API (/v1/*, requires Authorization: Bearer grn_...) — identical to the real Granola API:

Method Path Description
GET /v1/notes list, paginated, filterable by date/folder
GET /v1/notes/{id} one note; ?include=transcript to include it
GET /v1/folders list, paginated
POST /v1/webhook-endpoints register a URL for note events
GET /v1/webhook-endpoints list registered endpoints
DELETE /v1/webhook-endpoints/{id} remove one

Admin control-plane (/admin/*, mock-only, no auth) — for generating synthetic data, not something a real ingestion client would ever call:

Method Path Description
POST /admin/seed bulk-generate count random Faker notes/folders
POST /admin/reset wipe all state (optionally reseed)
POST /admin/folders create one folder
POST /admin/notes create one note with exact content (see below)
PATCH /admin/notes/{id} edit a note, fires note.edited
POST /admin/notes/{id}/regenerate fires note.regenerated
POST /admin/notes/{id}/grant-access fires note.access_granted
GET /admin/webhook-deliveries inspect delivery attempts/retries

POST /admin/notes accepts a transcript field — a list of {text, speaker_name?, source?, diarization_label?} turns — for exact transcript content instead of random Faker filler. See app/models.py::AdminTranscriptTurnInput.

Calling this API from a Python script

examples/granola_client.py is a small, dependency-light client (httpx only) that talks to /v1/* — the same surface the real Granola API exposes. It never touches /admin/*, so it works identically against the mock and the real thing:

from granola_client import GranolaClient

with GranolaClient.from_env() as client:
    for summary in client.iter_all_notes(page_size=10):
        note = client.get_note(summary["id"], include_transcript=True)
        print(note["id"], note["title"], note["owner"]["name"])

examples/ingest_notes.py is that snippet as a runnable script. Try it against the mock:

make run                                    # terminal 1
make acme-demo                              # terminal 2: seeds some notes
GRANOLA_ENV=dev GRANOLA_API_KEY=grn_dev_dummy_key \
    uv run --frozen python examples/ingest_notes.py

(make ingest-demo does the same with GRANOLA_ENV/GRANOLA_API_KEY pre-set.)

The dev/prod switch

All environment-specific behavior lives in one place: GranolaClient.from_env(). Calling code never branches on environment — it just constructs the client and calls the same methods either way:

Env var dev prod
GRANOLA_ENV dev (default) prod
GRANOLA_API_BASE_URL optional override optional override
GRANOLA_API_KEY any grn_... string a real workspace key

Left unset, GRANOLA_API_BASE_URL defaults to http://localhost:8000 in dev and https://public-api.granola.ai in prod — set it explicitly only when the mock lives somewhere else (e.g. a docker-compose service name, see below). GRANOLA_API_KEY only needs to look like a real key in dev — the mock checks the grn_... format only, never a real registry; prod needs an actual workspace key issued by Granola.

This is the same pattern a real Google Drive connector for a different data source would establish: build and validate everything against synthetic data first, and only point at the real API once the connector is proven — no code changes between the two, only environment.

Packaging for Docker

Because the only difference between environments is env vars, the ingestion image is the same artifact in both places:

# docker-compose.dev.yml — mock + ingestion, for local/dev/CI
services:
  granola-mock:
    build: ./mock-granola-api
    ports: ["8000:8000"]

  ingestion:
    image: ghcr.io/your-org/ingestion:latest
    environment:
      GRANOLA_ENV: dev
      GRANOLA_API_BASE_URL: http://granola-mock:8000
      GRANOLA_API_KEY: grn_dev_dummy_key
    depends_on: [granola-mock]
# docker-compose.prod.yml — same ingestion image, no mock container at all
services:
  ingestion:
    image: ghcr.io/your-org/ingestion:latest
    environment:
      GRANOLA_ENV: prod
      GRANOLA_API_KEY: ${GRANOLA_API_KEY}  # real key, injected at deploy time

Context: why this exists

Built to test an ingestion pipeline that lists Granola as an inflow source but has no live connector yet — a deal-note.md-style skill only parses a manually pasted Granola export. examples/acme_demo.py demonstrates the full loop this mock enables instead: seed VC-deal-shaped synthetic notes, register a webhook and confirm signed delivery, fetch through the real public endpoints, and render the result both as the plain-text export deal-note.md already parses and as a wiki-dealflow frontmatter stub (title/status/sector/ask/portfolio_company/updated) — proving a real connector could be built and fully tested here, against synthetic data only, before it ever sees a real Granola API key.

Development

make test           # pytest
ruff check .         # lint
ruff format .        # format
pyright .            # types

See app/ for the mock's implementation and tests/ for coverage of pagination, auth, rate limiting, webhook signing/retries, and the admin API.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mock_granola_api-0.1.0.tar.gz (20.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mock_granola_api-0.1.0-py3-none-any.whl (18.4 kB view details)

Uploaded Python 3

File details

Details for the file mock_granola_api-0.1.0.tar.gz.

File metadata

  • Download URL: mock_granola_api-0.1.0.tar.gz
  • Upload date:
  • Size: 20.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.4

File hashes

Hashes for mock_granola_api-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d4e13b00a5c89c7abb41f0aecf4c75e9718535f20d02566706cb42c565dc4c54
MD5 bba0aabe756a0a69b06303999e679f12
BLAKE2b-256 376b92624b87934ec119d8730fef57e71dcc205f34bcd5b9b69cca7f131c51ad

See more details on using hashes here.

File details

Details for the file mock_granola_api-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for mock_granola_api-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9b3bb020a6c2047e24c36784b44b7f9991c3d3cf3e59815ef3b6fd4d7ca03b89
MD5 034936dc467e7108ecb25b5c331e657f
BLAKE2b-256 fe0f5bb61fc7d7796cd585df5716083d6a99372c09a0cf365aac117672cbfcf6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page