# Wave API — integration docs for agents Base URL: https://api.wave.co OpenAPI: https://api.wave.co/v1/openapi.json Interactive reference: https://api.wave.co/reference Wave records and transcribes meetings, phone calls, and audio. The Wave API lets external agents read that data (sessions, transcripts, summaries) and write back metadata (title, notes, tags, favorite). --- ## Authentication All requests use Bearer tokens: `Authorization: Bearer wave_api_...` Tokens are created at https://app.wave.co/settings/integrations/api with per-scope permissions. Request only what you need. Scopes: - sessions:read — list/get session metadata, stats, bulk export, folders - sessions:search — semantic search - sessions:write — PATCH title/notes/tags/favorite - sessions:delete — DELETE a session - transcripts:read — GET transcript; also required for bulk `include_transcript: true` - media:read — signed audio/video URLs - account:read — GET /v1/account - webhooks:manage — register and manage webhooks Rate limits: 60 requests/minute, 10,000/day per token. 429 returned on breach. Error shape: ```json { "error": { "code": "not_found", "message": "Session not found" } } ``` --- ## Endpoints ### GET /v1/sessions — list sessions Query params: `limit` (≤100, default 20), `cursor` (ISO 8601, pass the previous response's `next_cursor`), `since` (ISO 8601), `type` (meeting, recording, phone, desktop, import, youtube, podcast), `folder` (folder id or case-insensitive name — see "Folders" below). Only sessions with summaries are returned (i.e. finished processing). Example response: ```json { "sessions": [ { "id": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM", "title": "Weekly product sync", "timestamp": "2025-04-18T16:00:00.000Z", "duration_seconds": 1812, "type": "meeting", "platform": "zoom" } ], "next_cursor": "2025-04-17T20:30:00.000Z", "has_more": true } ``` ### GET /v1/sessions/{id} — session detail Returns a session's metadata including structured markdown summary, free-text notes, tags, and favorite flag. Transcript is NOT included here. Example response: ```json { "id": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM", "title": "Weekly product sync", "timestamp": "2025-04-18T16:00:00.000Z", "duration_seconds": 1812, "type": "meeting", "platform": "zoom", "language": "en", "summary": "## Overview\n…\n## Action items\n- [ ] JR: send legal the draft spec (Fri)", "notes": "Pre-read doc in Notion; follow up on legal timeline.", "tags": ["work", "roadmap"], "favorite": false } ``` The `summary` field is already structured markdown (Overview / Key points / Action items). Prefer passing it to downstream LLMs over paying inference on the raw transcript. ### GET /v1/sessions/{id}/transcript — full transcript Returns BOTH a flat string and timed segments in one payload. Use `transcript` for LLM input; use `segments[]` for UIs that need timestamps. `start`/`end` are seconds as floats, not ISO timestamps. Example response: ```json { "id": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM", "transcript": "Josh: Alright, let's kick off…\nSam: Sure — I sent the draft last night.", "segments": [ { "speaker": "Josh", "start": 12.48, "end": 15.92, "text": "Alright, let's kick off…" }, { "speaker": "Sam", "start": 16.04, "end": 18.31, "text": "Sure — I sent the draft last night." } ] } ``` ### POST /v1/sessions/search — semantic search Body: `{ "query": "retail media strategy", "limit": 10 }` (limit ≤ 50) Each result includes a `snippet` — agents can triage without pulling the full session. Example response: ```json { "query": "retail media strategy", "results": [ { "id": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM", "title": "Weekly product sync", "timestamp": "2025-04-18T16:00:00.000Z", "type": "meeting", "similarity": 0.87, "snippet": "…we want to lead with retail media strategy in Q3…" } ], "total": 1 } ``` ### POST /v1/sessions/bulk — bulk export Body: `{ "session_ids": [...up to 50], "include_transcript": true, "include_summary": true }` Full transcripts come back in one request — ideal for backfilling a knowledge base. `include_transcript: true` requires the `transcripts:read` scope. ### GET /v1/sessions/stats — counts + totals Query params: `since`, `until` (ISO 8601; default last 30 days). Returns totals and breakdowns by type and platform. ### PATCH /v1/sessions/{id} — write-back metadata Body fields (any subset): `title` (≤500 chars), `notes` (≤50,000 chars), `tags` (array, ≤20 items, each ≤100 chars), `favorite` (boolean). Example request: ```json { "tags": ["work", "roadmap"], "favorite": true } ``` Fires `session.updated` webhook. ### DELETE /v1/sessions/{id} — delete session Permanent. Storage files cleaned up asynchronously. Fires `session.deleted` webhook. ### GET /v1/sessions/{id}/media — signed media URLs Returns `{ audio_url, video_url, expires_at }`. URLs expire after 1 hour. ### GET /v1/account — whoami Returns `{ user_id, subscription_active, session_count }`. ### GET /v1/folders — list folders Returns the user's folders. Users organize sessions into folders in the Wave app; agents consume them as a scoping primitive. Pair with `GET /v1/sessions?folder=…`. Example response: ```json { "folders": [ { "id": "fld_01HX4R…", "name": "work", "color": "#6D28D9", "session_count": 82 }, { "id": "fld_01HX5S…", "name": "personal", "color": "#F59E0B", "session_count": 14 } ] } ``` --- ## Webhooks Register at `POST /v1/webhooks` with `{ url, events: [...] }`. The signing `secret` is returned once at creation. Max 5 webhooks per user. Events: - `session.completed` — fires once, after a session finishes processing and the summary, notes, and tags are queryable. This is the "session is ready to use" event. Subscribe to this one. - `session.updated` — session metadata was changed (title, summary, notes, tags, favorite). Fires for edits from any source: the API, mobile app, or web app. - `session.deleted` — session was deleted (soft-delete or hard-delete of a completed session). Delivery payload: ```json { "id": "evt_01HX4R7MT8PQZ6VCN9ABYF2KJM", "event": "session.completed", "created_at": "2025-04-21T17:14:08.120Z", "data": { "session": { "id": "sess_…", "title": "…", "summary": "…", "…": "…" } } } ``` Headers for verification: - `X-Wave-Webhook-Id` — unique event ID - `X-Wave-Webhook-Timestamp` — unix timestamp - `X-Wave-Webhook-Signature` — HMAC-SHA256 over `"${id}.${timestamp}.${body}"` using the webhook secret Retries: failed deliveries are retried with exponential backoff for up to 24 hours by an internal cron. --- ## Quick-start (Python stdlib) ```python import os, json, urllib.request TOKEN = os.environ["WAVE_API_KEY"] BASE = "https://api.wave.co/v1" def wave(path, method="GET", body=None): req = urllib.request.Request( f"{BASE}{path}", method=method, data=json.dumps(body).encode() if body else None, headers={ "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", }, ) with urllib.request.urlopen(req) as r: return json.loads(r.read()) # List the 20 most recent sessions sessions = wave("/sessions")["sessions"] # Pull transcript + summary for the first one sid = sessions[0]["id"] detail = wave(f"/sessions/{sid}") transcript = wave(f"/sessions/{sid}/transcript") # Semantic search hits = wave("/sessions/search", "POST", {"query": "retail media strategy"}) # Bulk backfill (transcripts in one request) bulk = wave("/sessions/bulk", "POST", { "session_ids": [s["id"] for s in sessions[:20]], "include_transcript": True, "include_summary": True, }) ``` --- ## Integration notes for multi-context accounts If a single Wave account mixes work and personal sessions, the recommended default-deny pattern is: 1. Ask the user to create a folder (in the Wave app) for the scope your agent should see — typically "work". 2. Ask them to drag qualifying sessions into that folder. (Folders already exist as a first-class concept in the Wave iOS, Android, macOS, and web clients.) 3. In the agent, always pass `?folder=work` to `GET /v1/sessions` and any listing flow. Any session not in the folder is out-of-scope by construction. 4. For `POST /v1/sessions/search` and `POST /v1/sessions/bulk` (which don't accept `folder`), first call `GET /v1/folders`, cache the target folder's membership (or cross-reference against the sessions you've already listed), and filter results client-side. This gives you cheap filtered listing in one round-trip and keeps work/personal separation auditable — no server-side tags required. --- Contact: https://wave.co Status: production