{
  "openapi": "3.1.0",
  "info": {
    "title": "Wave API",
    "version": "1.0.0",
    "description": "API for accessing Wave AI session data, transcripts, and managing webhooks. Build AI integrations, CRM syncs, analytics dashboards, and productivity tools.",
    "contact": {
      "name": "Wave Developer Support",
      "url": "https://wave.co/developers",
      "email": "support@wave.co"
    }
  },
  "servers": [
    {
      "url": "https://api.wave.co",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Sessions",
      "description": "List, search, update, and delete sessions. PATCH supports structured action items with optimistic concurrency."
    },
    {
      "name": "Folders",
      "description": "Create, rename, recolour, pin, reorder and delete folders, and manage non-exclusive session membership. Use with `GET /v1/sessions?folder=…` for filtered listing."
    },
    {
      "name": "Transcripts",
      "description": "Access session transcripts with speaker segments"
    },
    {
      "name": "Media",
      "description": "Get signed URLs for audio and video files"
    },
    {
      "name": "Account",
      "description": "User profile and subscription info"
    },
    {
      "name": "Events",
      "description": "Per-token cursor feed. Poll and ack instead of running a webhook receiver; same event shapes."
    },
    {
      "name": "Webhooks",
      "description": "Register and manage webhook endpoints"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Wave API token (wave_api_...), minted at https://app.wave.co/settings/integrations. Tokens carry only the scopes granted at creation; each operation lists the ones it requires. The scope vocabulary is machine-readable as scopes_supported in RFC 9728 protected-resource metadata at https://api.wave.co/.well-known/oauth-protected-resource. Available scopes: `sessions:read` — List and read session metadata, summaries, and action items. `sessions:write` — Update session titles, notes, tags, and structured action items. `sessions:delete` — Delete a session and its recording. `sessions:search` — Run semantic search across the account's sessions. `transcripts:read` — Read full transcripts with speaker segments. `media:read` — Mint signed URLs for session audio and video. `account:read` — Read the token owner's profile and subscription state. `folders:read` — List and read folders. `folders:write` — Create, rename, recolour and delete folders and manage session membership. `events:read` — Poll the per-token event cursor feed and acknowledge events. `webhooks:manage` — Register webhook endpoints, rotate secrets, and send test deliveries."
      }
    },
    "schemas": {
      "APIError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ],
        "example": {
          "error": {
            "code": "not_found",
            "message": "Session not found"
          }
        }
      },
      "WebhookEventPayload": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "event": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "created_at": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "session": {
                "$ref": "#/components/schemas/SessionDetail"
              }
            },
            "required": [
              "session"
            ]
          }
        },
        "required": [
          "id",
          "event",
          "created_at",
          "data"
        ],
        "example": {
          "id": "evt_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "event": "session.completed",
          "created_at": "2025-04-21T17:14:08.120Z",
          "data": {
            "session": {
              "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\nQuarterly roadmap review…",
              "notes": null,
              "tags": [
                "work",
                "roadmap"
              ],
              "favorite": false
            }
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "session.completed",
          "session.updated",
          "session.deleted",
          "session.action_items.updated"
        ]
      },
      "SessionDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "timestamp": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_seconds": {
            "type": "number"
          },
          "type": {
            "type": "string"
          },
          "platform": {
            "type": [
              "string",
              "null"
            ]
          },
          "language": {
            "type": [
              "string",
              "null"
            ]
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "favorite": {
            "type": "boolean"
          },
          "folder_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "action_items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionItem"
            }
          },
          "action_items_version": {
            "type": "number"
          },
          "phone": {
            "$ref": "#/components/schemas/PhoneMetadata"
          }
        },
        "required": [
          "id",
          "title",
          "timestamp",
          "duration_seconds",
          "type",
          "platform",
          "language",
          "summary",
          "notes"
        ],
        "example": {
          "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\nQuarterly roadmap review with the retail-media pod.\n\n## Key points\n- Shifting Q3 priorities to Instacart rollout\n- Blocked on legal review for sponsored SKUs\n\n## Action items\n- [ ] JR: send legal the draft spec (Fri)\n- [ ] Sam: mock up the new placement UI",
          "notes": "Pre-read doc in Notion; follow up on legal timeline.",
          "tags": [
            "work",
            "roadmap"
          ],
          "favorite": false
        }
      },
      "ActionItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "completed": {
            "type": "boolean"
          },
          "source": {
            "type": "string",
            "enum": [
              "agent",
              "user"
            ]
          },
          "assignee": {
            "type": [
              "string",
              "null"
            ]
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Due date as an ISO calendar date (YYYY-MM-DD), or null. Natural-language input is normalized to this form on write."
          },
          "source_start": {
            "type": [
              "number",
              "null"
            ],
            "description": "Transcript offset in seconds where the item was committed to. Set by Wave when extracting items; preserved across edits."
          },
          "user_edited_at": {
            "type": [
              "number",
              "null"
            ],
            "description": "Epoch milliseconds of the last time a client changed this item's text or due_date. null when never edited since extraction."
          },
          "created_at": {
            "type": "number"
          },
          "updated_at": {
            "type": "number"
          }
        },
        "required": [
          "id",
          "text",
          "completed",
          "source",
          "created_at",
          "updated_at"
        ],
        "example": {
          "id": "ai_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "text": "Send legal the draft spec by Friday",
          "completed": false,
          "source": "agent",
          "assignee": "JR",
          "due_date": "2025-04-25",
          "user_edited_at": null,
          "created_at": 1745258048120,
          "updated_at": 1745258048120
        }
      },
      "PhoneMetadata": {
        "type": "object",
        "properties": {
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "inbound",
              "outbound",
              null
            ]
          },
          "to_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "from_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ringing",
              "in_progress",
              "completed",
              "missed",
              "failed",
              null
            ]
          },
          "ongoing": {
            "type": "boolean"
          },
          "provider": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_name": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "direction",
          "to_number",
          "from_number",
          "status",
          "ongoing",
          "provider",
          "contact_name"
        ],
        "description": "Call metadata, present only on sessions of type \"phone\". For inbound (bridge) calls the other party is unknown: to_number is the user’s verified caller id and from_number is null.",
        "example": {
          "direction": "outbound",
          "to_number": "+15551234567",
          "from_number": "+15559876543",
          "status": "completed",
          "ongoing": false,
          "provider": "telnyx",
          "contact_name": "Acme Co."
        }
      },
      "EventEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "seq": {
            "type": "number"
          },
          "event": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "created_at": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "properties": {
              "session": {
                "$ref": "#/components/schemas/SessionDetail"
              }
            },
            "required": [
              "session"
            ]
          }
        },
        "required": [
          "id",
          "seq",
          "event",
          "created_at",
          "data"
        ],
        "example": {
          "id": "evt_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "seq": 4281,
          "event": "session.completed",
          "created_at": "2025-04-21T17:14:08.120Z",
          "data": {
            "session": {
              "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\nQuarterly roadmap review…",
              "notes": null,
              "tags": [
                "work",
                "roadmap"
              ],
              "favorite": false
            }
          }
        }
      },
      "SessionListResponse": {
        "type": "object",
        "properties": {
          "sessions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SessionListItem"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "has_more": {
            "type": "boolean"
          }
        },
        "required": [
          "sessions",
          "next_cursor",
          "has_more"
        ],
        "example": {
          "sessions": [
            {
              "id": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM",
              "title": "Weekly product sync",
              "timestamp": "2025-04-18T16:00:00.000Z",
              "duration_seconds": 1812,
              "type": "meeting",
              "platform": "zoom"
            },
            {
              "id": "sess_01HX3P8YVJ9WX1MKBQ2NTD5VGH",
              "title": "Customer call — Acme Co.",
              "timestamp": "2025-04-17T20:30:00.000Z",
              "duration_seconds": 2455,
              "type": "meeting",
              "platform": "google_meet"
            }
          ],
          "next_cursor": "2025-04-17T20:30:00.000Z",
          "has_more": true
        }
      },
      "SessionListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "timestamp": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_seconds": {
            "type": "number"
          },
          "type": {
            "type": "string"
          },
          "platform": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "$ref": "#/components/schemas/PhoneMetadata"
          }
        },
        "required": [
          "id",
          "title",
          "timestamp",
          "duration_seconds",
          "type",
          "platform"
        ],
        "example": {
          "id": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "title": "Weekly product sync",
          "timestamp": "2025-04-18T16:00:00.000Z",
          "duration_seconds": 1812,
          "type": "meeting",
          "platform": "zoom"
        }
      },
      "FolderListResponse": {
        "type": "object",
        "properties": {
          "folders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Folder"
            }
          }
        },
        "required": [
          "folders"
        ],
        "example": {
          "folders": [
            {
              "id": "fld_01HX4R7MT8PQZ6VCN9ABYF2KJM",
              "name": "work",
              "color": "#6D28D9",
              "session_count": 82,
              "created_at": "2026-09-01T14:02:11.000Z",
              "updated_at": "2026-09-05T09:41:37.000Z",
              "pinned": true,
              "sort_order": "newest",
              "order": 0,
              "last_opened_at": "2026-09-06T08:15:02.000Z"
            },
            {
              "id": "fld_01HX5S8NV9QR0XDJ2KBC7ZG3HT",
              "name": "personal",
              "color": "#F59E0B",
              "session_count": 14,
              "created_at": "2026-08-12T18:30:00.000Z",
              "updated_at": "2026-08-12T18:30:00.000Z"
            }
          ]
        }
      },
      "Folder": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "color": {
            "type": [
              "string",
              "null"
            ]
          },
          "session_count": {
            "type": "number"
          },
          "created_at": {
            "type": "string",
            "description": "ISO 8601. Absent on folders created before timestamps were introduced."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO 8601. Bumped on rename, recolour, pin/reorder/sort changes, and membership changes. Absent on folders never mutated since timestamps were introduced."
          },
          "pinned": {
            "type": "boolean",
            "description": "Pinned folders sort first everywhere. Absent means not pinned."
          },
          "sort_order": {
            "$ref": "#/components/schemas/FolderSortOrder"
          },
          "order": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991,
            "description": "Manual position (integer from 0 to 2^53 - 1). Folders with `order` sort ascending before folders without it. Absent means no manual position."
          },
          "last_opened_at": {
            "type": "string",
            "description": "ISO 8601. When the folder's contents were last viewed in a Wave client. Absent if never recorded."
          }
        },
        "required": [
          "id",
          "name",
          "color",
          "session_count"
        ],
        "example": {
          "id": "fld_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "name": "work",
          "color": "#6D28D9",
          "session_count": 82,
          "created_at": "2026-09-01T14:02:11.000Z",
          "updated_at": "2026-09-05T09:41:37.000Z",
          "pinned": true,
          "sort_order": "newest",
          "order": 0,
          "last_opened_at": "2026-09-06T08:15:02.000Z"
        }
      },
      "FolderSortOrder": {
        "type": "string",
        "enum": [
          "newest",
          "oldest",
          "title"
        ],
        "description": "Remembered sort for the sessions inside a folder: `newest` (session timestamp descending; the default when absent), `oldest`, or `title`."
      },
      "CreateFolderRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "pattern": "\\S",
            "description": "Folder name. Trimmed; 1–100 characters, not blank."
          },
          "color": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^#[0-9a-fA-F]{6}$",
            "description": "Hex colour like `#6D28D9`, or null for no colour."
          }
        },
        "required": [
          "name"
        ],
        "example": {
          "name": "client-calls",
          "color": "#6D28D9"
        }
      },
      "PatchFolderRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "pattern": "\\S",
            "description": "New folder name. Trimmed; 1–100 characters, not blank."
          },
          "color": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^#[0-9a-fA-F]{6}$",
            "description": "Hex colour like `#4D5EF6`, or null to clear the colour."
          },
          "pinned": {
            "type": "boolean",
            "description": "Pin (true) or unpin (false) the folder. Pinned folders sort first everywhere."
          },
          "sort_order": {
            "$ref": "#/components/schemas/FolderSortOrder"
          },
          "order": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991,
            "description": "Manual position, integer from 0 to 2^53 - 1. Folders with `order` sort ascending before folders without it."
          }
        },
        "description": "At least one of `name` / `color` / `pinned` / `sort_order` / `order` is required (see `anyOf`). Omitted fields are left unchanged.",
        "anyOf": [
          {
            "required": [
              "name"
            ]
          },
          {
            "required": [
              "color"
            ]
          },
          {
            "required": [
              "pinned"
            ]
          },
          {
            "required": [
              "sort_order"
            ]
          },
          {
            "required": [
              "order"
            ]
          }
        ],
        "example": {
          "name": "Customer research",
          "color": "#4D5EF6",
          "pinned": true,
          "sort_order": "oldest",
          "order": 2
        }
      },
      "FolderMembershipResponse": {
        "type": "object",
        "properties": {
          "session_id": {
            "type": "string"
          },
          "folder_id": {
            "type": "string"
          },
          "folder_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "session_id",
          "folder_id",
          "folder_ids"
        ],
        "example": {
          "session_id": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "folder_id": "fld_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "folder_ids": [
            "fld_01HX4R7MT8PQZ6VCN9ABYF2KJM",
            "fld_01HX5S8NV9QR0XDJ2KBC7ZG3HT"
          ]
        }
      },
      "PatchSessionResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "updated_fields": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "updated_at": {
            "type": "string"
          },
          "action_items_version": {
            "type": "number"
          }
        },
        "required": [
          "id",
          "updated_fields",
          "updated_at"
        ],
        "example": {
          "id": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "updated_fields": [
            "tags",
            "favorite",
            "action_items"
          ],
          "updated_at": "2025-04-21T17:14:08.120Z",
          "action_items_version": 3
        }
      },
      "PatchSessionRequest": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 500
          },
          "notes": {
            "type": "string",
            "maxLength": 50000
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 100
            },
            "maxItems": 20
          },
          "favorite": {
            "type": "boolean"
          },
          "action_items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionItemInput"
            },
            "maxItems": 200
          }
        },
        "example": {
          "tags": [
            "work",
            "roadmap"
          ],
          "favorite": true,
          "action_items": [
            {
              "text": "Send legal the draft spec by Friday",
              "assignee": "JR",
              "due_date": "2025-04-25"
            },
            {
              "text": "Mock up the new placement UI",
              "assignee": "Sam"
            }
          ]
        }
      },
      "ActionItemInput": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          },
          "completed": {
            "type": "boolean"
          },
          "source": {
            "type": "string",
            "enum": [
              "agent",
              "user"
            ]
          },
          "assignee": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "description": "Due date. ISO YYYY-MM-DD preferred; natural language such as \"Friday\", \"next Tuesday\" or \"April 25\" is accepted and normalized to ISO relative to today. null or \"\" clears the date; omit the field to keep the current one. Unparseable values are rejected with 400."
          }
        },
        "required": [
          "text"
        ],
        "example": {
          "text": "Send legal the draft spec by Friday",
          "assignee": "JR",
          "due_date": "2025-04-25"
        }
      },
      "ActionItemsResponse": {
        "type": "object",
        "properties": {
          "session_id": {
            "type": "string"
          },
          "action_items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionItem"
            }
          },
          "version": {
            "type": "number"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "session_id",
          "action_items",
          "version",
          "updated_at"
        ],
        "example": {
          "session_id": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "action_items": [
            {
              "id": "ai_01HX4R7MT8PQZ6VCN9ABYF2KJM",
              "text": "Send legal the draft spec by Friday",
              "completed": false,
              "source": "agent",
              "assignee": "JR",
              "due_date": "2025-04-25",
              "user_edited_at": null,
              "created_at": 1745258048120,
              "updated_at": 1745258048120
            }
          ],
          "version": 3,
          "updated_at": "2025-04-21T17:14:08.120Z"
        }
      },
      "DeleteSessionResponse": {
        "type": "object",
        "properties": {
          "deleted": {
            "type": "boolean"
          },
          "id": {
            "type": "string"
          }
        },
        "required": [
          "deleted",
          "id"
        ],
        "example": {
          "deleted": true,
          "id": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM"
        }
      },
      "TranscriptResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "transcript": {
            "type": "string"
          },
          "segments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Segment"
            }
          }
        },
        "required": [
          "id",
          "transcript"
        ],
        "example": {
          "id": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "transcript": "Josh: Alright, let’s kick off the roadmap review.\nSam: Sure — I sent the draft last night.\nJosh: Great, want to start with retail media?",
          "segments": [
            {
              "speaker": "Josh",
              "start": 12.48,
              "end": 15.92,
              "text": "Alright, let’s kick off the roadmap review."
            },
            {
              "speaker": "Sam",
              "start": 16.04,
              "end": 18.31,
              "text": "Sure — I sent the draft last night."
            },
            {
              "speaker": "Josh",
              "start": 18.5,
              "end": 21.02,
              "text": "Great, want to start with retail media?"
            }
          ]
        }
      },
      "Segment": {
        "type": "object",
        "properties": {
          "speaker": {
            "type": "string"
          },
          "start": {
            "type": "number"
          },
          "end": {
            "type": "number"
          },
          "text": {
            "type": "string"
          }
        },
        "required": [
          "speaker",
          "start",
          "end",
          "text"
        ],
        "example": {
          "speaker": "Josh",
          "start": 12.48,
          "end": 15.92,
          "text": "Alright, let’s kick off the roadmap review."
        }
      },
      "MediaResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "audio_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "video_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "expires_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "audio_url",
          "video_url",
          "expires_at"
        ],
        "example": {
          "id": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "audio_url": "https://storage.googleapis.com/table-talk-b87f8.appspot.com/Users/uid_.../audio.m4a?X-Goog-Signature=…",
          "video_url": null,
          "expires_at": "2025-04-21T18:14:08.000Z"
        }
      },
      "SearchResponse": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          },
          "total": {
            "type": "number"
          }
        },
        "required": [
          "query",
          "results",
          "total"
        ],
        "example": {
          "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, starting with the Instacart rollout…"
            }
          ],
          "total": 1
        }
      },
      "SearchResult": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "timestamp": {
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "type": "string"
          },
          "similarity": {
            "type": "number"
          },
          "snippet": {
            "type": "string"
          },
          "phone": {
            "$ref": "#/components/schemas/PhoneMetadata"
          }
        },
        "required": [
          "id",
          "title",
          "timestamp",
          "type",
          "snippet"
        ],
        "example": {
          "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, starting with the Instacart rollout…"
        }
      },
      "SearchRequest": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "minLength": 1
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 20
          },
          "tag_mode": {
            "type": "string",
            "enum": [
              "any",
              "all"
            ]
          }
        },
        "required": [
          "query"
        ],
        "example": {
          "query": "retail media strategy",
          "limit": 10,
          "tags": [
            "work"
          ],
          "tag_mode": "any"
        }
      },
      "StatsResponse": {
        "type": "object",
        "properties": {
          "total_sessions": {
            "type": "number"
          },
          "total_duration_seconds": {
            "type": "number"
          },
          "average_duration_seconds": {
            "type": "number"
          },
          "sessions_by_type": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          },
          "sessions_by_platform": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          },
          "period": {
            "type": "object",
            "properties": {
              "since": {
                "type": "string"
              },
              "until": {
                "type": "string"
              }
            },
            "required": [
              "since",
              "until"
            ]
          }
        },
        "required": [
          "total_sessions",
          "total_duration_seconds",
          "average_duration_seconds",
          "sessions_by_type",
          "sessions_by_platform",
          "period"
        ],
        "example": {
          "total_sessions": 57,
          "total_duration_seconds": 89432,
          "average_duration_seconds": 1569,
          "sessions_by_type": {
            "meeting": 38,
            "phone": 11,
            "recording": 8
          },
          "sessions_by_platform": {
            "zoom": 22,
            "google_meet": 12,
            "phone": 11,
            "desktop": 8,
            "none": 4
          },
          "period": {
            "since": "2025-03-22T00:00:00.000Z",
            "until": "2025-04-21T00:00:00.000Z"
          }
        }
      },
      "BulkExportResponse": {
        "type": "object",
        "properties": {
          "sessions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BulkExportSession"
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "error": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "error"
              ]
            }
          }
        },
        "required": [
          "sessions",
          "errors"
        ],
        "example": {
          "sessions": [
            {
              "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\nQuarterly roadmap review…",
              "transcript": "Josh: Alright, let’s kick off the roadmap review.…"
            }
          ],
          "errors": [
            {
              "id": "sess_does_not_exist",
              "error": "not_found"
            }
          ]
        }
      },
      "BulkExportSession": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "timestamp": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_seconds": {
            "type": "number"
          },
          "type": {
            "type": "string"
          },
          "platform": {
            "type": [
              "string",
              "null"
            ]
          },
          "language": {
            "type": [
              "string",
              "null"
            ]
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "transcript": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "$ref": "#/components/schemas/PhoneMetadata"
          }
        },
        "required": [
          "id",
          "title",
          "timestamp",
          "duration_seconds",
          "type",
          "platform",
          "language"
        ],
        "example": {
          "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\nQuarterly roadmap review…",
          "transcript": "Josh: Alright, let’s kick off the roadmap review.\nSam: Sure — I sent the draft last night.…"
        }
      },
      "BulkExportRequest": {
        "type": "object",
        "properties": {
          "session_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "maxItems": 50
          },
          "include_transcript": {
            "type": "boolean"
          },
          "include_summary": {
            "type": "boolean"
          }
        },
        "required": [
          "session_ids"
        ],
        "example": {
          "session_ids": [
            "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM",
            "sess_01HX3P8YVJ9WX1MKBQ2NTD5VGH"
          ],
          "include_transcript": true,
          "include_summary": true
        }
      },
      "AccountResponse": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "string"
          },
          "subscription_active": {
            "type": "boolean"
          },
          "session_count": {
            "type": "number"
          }
        },
        "required": [
          "user_id",
          "subscription_active",
          "session_count"
        ],
        "example": {
          "user_id": "uid_V7hTqM3aRe5xJ8pL2nKbS6wY",
          "subscription_active": true,
          "session_count": 421
        }
      },
      "EventsListResponse": {
        "type": "object",
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EventEnvelope"
            }
          },
          "next_cursor": {
            "type": [
              "number",
              "null"
            ]
          },
          "has_more": {
            "type": "boolean"
          }
        },
        "required": [
          "events",
          "next_cursor",
          "has_more"
        ],
        "example": {
          "events": [
            {
              "id": "evt_01HX4R7MT8PQZ6VCN9ABYF2KJM",
              "seq": 4281,
              "event": "session.completed",
              "created_at": "2025-04-21T17:14:08.120Z",
              "data": {
                "session": {
                  "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…",
                  "notes": null,
                  "tags": [
                    "work"
                  ],
                  "favorite": false
                }
              }
            }
          ],
          "next_cursor": 4281,
          "has_more": false
        }
      },
      "AckEventsResponse": {
        "type": "object",
        "properties": {
          "acknowledged_cursor": {
            "type": "number"
          }
        },
        "required": [
          "acknowledged_cursor"
        ],
        "example": {
          "acknowledged_cursor": 4281
        }
      },
      "AckEventsRequest": {
        "type": "object",
        "properties": {
          "cursor": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "cursor"
        ],
        "example": {
          "cursor": 4281
        }
      },
      "WebhookListResponse": {
        "type": "object",
        "properties": {
          "webhooks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookResponse"
            }
          }
        },
        "required": [
          "webhooks"
        ],
        "example": {
          "webhooks": [
            {
              "id": "wh_01HX4R7MT8PQZ6VCN9ABYF2KJM",
              "url": "https://example.com/wave/webhook",
              "events": [
                "session.completed"
              ],
              "active": true,
              "failure_count": 0,
              "last_delivery_at": "2025-04-21T15:02:11.000Z",
              "last_delivery_status": 200,
              "created_at": "2025-04-20T18:32:14.000Z",
              "updated_at": "2025-04-21T15:02:11.000Z"
            }
          ]
        }
      },
      "WebhookResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "active": {
            "type": "boolean"
          },
          "failure_count": {
            "type": "number"
          },
          "last_delivery_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_delivery_status": {
            "type": [
              "number",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "secret": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "active",
          "failure_count",
          "last_delivery_at",
          "last_delivery_status",
          "created_at",
          "updated_at"
        ],
        "example": {
          "id": "wh_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "url": "https://example.com/wave/webhook",
          "events": [
            "session.completed",
            "session.updated"
          ],
          "active": true,
          "failure_count": 0,
          "last_delivery_at": "2025-04-21T15:02:11.000Z",
          "last_delivery_status": 200,
          "created_at": "2025-04-20T18:32:14.000Z",
          "updated_at": "2025-04-21T15:02:11.000Z",
          "secret": "whsec_7j3LxGf9qA2pRn5bXcV1MdKt"
        }
      },
      "CreateWebhookRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            },
            "minItems": 1
          }
        },
        "required": [
          "url",
          "events"
        ],
        "example": {
          "url": "https://example.com/wave/webhook",
          "events": [
            "session.completed",
            "session.updated"
          ]
        }
      },
      "PatchWebhookRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            },
            "minItems": 1
          },
          "active": {
            "type": "boolean"
          }
        },
        "example": {
          "events": [
            "session.completed",
            "session.updated",
            "session.deleted"
          ],
          "active": true
        }
      },
      "WebhookTestResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "event": {
            "type": "string"
          },
          "webhook_id": {
            "type": "string"
          },
          "delivered_at": {
            "type": "string"
          }
        },
        "required": [
          "success",
          "event",
          "webhook_id",
          "delivered_at"
        ],
        "example": {
          "success": true,
          "event": "session.completed",
          "webhook_id": "wh_01HX4R7MT8PQZ6VCN9ABYF2KJM",
          "delivered_at": "2025-04-21T17:14:08.120Z"
        }
      }
    },
    "parameters": {}
  },
  "paths": {
    "/v1/sessions": {
      "get": {
        "summary": "List sessions",
        "description": "List sessions with cursor-based pagination. Only returns completed sessions (with summaries). Response shape is stable; every session includes `id`, `title`, `timestamp`, `duration_seconds`, `type`, and `platform`.",
        "tags": [
          "Sessions"
        ],
        "security": [
          {
            "bearerAuth": [
              "sessions:read"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Max results (default 20, max 100)",
              "example": "20"
            },
            "required": false,
            "description": "Max results (default 20, max 100)",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "ISO 8601 date for pagination (sessions older than this)",
              "example": "2025-04-17T20:30:00.000Z"
            },
            "required": false,
            "description": "ISO 8601 date for pagination (sessions older than this)",
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "ISO 8601 date (sessions newer than this)",
              "example": "2025-04-01T00:00:00.000Z"
            },
            "required": false,
            "description": "ISO 8601 date (sessions newer than this)",
            "name": "since",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter by session type (meeting, recording, phone, desktop, import, youtube, podcast)",
              "example": "meeting"
            },
            "required": false,
            "description": "Filter by session type (meeting, recording, phone, desktop, import, youtube, podcast)",
            "name": "type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter by folder id or case-insensitive name. Restricts results to sessions in that folder. Ideal for default-deny work/personal separation on a single account.",
              "example": "work"
            },
            "required": false,
            "description": "Filter by folder id or case-insensitive name. Restricts results to sessions in that folder. Ideal for default-deny work/personal separation on a single account.",
            "name": "folder",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter by tag (exact, case-sensitive). Repeatable (`?tag=a&tag=b`) or comma-separated (`?tag=a,b`). Pair with `tag_mode=all` to require every tag.",
              "example": "roadmap"
            },
            "required": false,
            "description": "Filter by tag (exact, case-sensitive). Repeatable (`?tag=a&tag=b`) or comma-separated (`?tag=a,b`). Pair with `tag_mode=all` to require every tag.",
            "name": "tag",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`any` (default) returns sessions matching at least one of the listed tags; `all` requires every listed tag.",
              "example": "any"
            },
            "required": false,
            "description": "`any` (default) returns sessions matching at least one of the listed tags; `all` requires every listed tag.",
            "name": "tag_mode",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Session list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (bad tag_mode, etc.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          },
          "404": {
            "description": "Folder not found (only when `folder` param is passed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "getSessions"
      }
    },
    "/v1/folders": {
      "get": {
        "summary": "List folders",
        "description": "List all folders for the authenticated user. Each folder carries the sessions the user has organized into it in the Wave app. Folders are non-exclusive — a session can belong to many folders at once. Use the folder `id` or `name` with `GET /v1/sessions?folder=…` to restrict a session list to one folder. Requires the `folders:read` scope; `sessions:read` is also accepted for backward compatibility. `created_at` / `updated_at` and the preferences `pinned` / `sort_order` / `order` / `last_opened_at` are included when present; absence means the default (not pinned, newest first, no manual position, never opened). Folders are returned in display order: pinned first, then `order` ascending (folders with an `order` before those without), then name.",
        "tags": [
          "Folders"
        ],
        "security": [
          {
            "bearerAuth": [
              "folders:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Folder list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FolderListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "getFolders"
      },
      "post": {
        "summary": "Create folder",
        "description": "Create a new folder. Name collisions (case-insensitive) return the existing folder — the endpoint is safe to retry. `name` is trimmed and must be 1–100 characters; `color` is a hex string like `#6D28D9` or null. The folder is stamped with `created_at` / `updated_at`. Requires the `folders:write` scope.",
        "tags": [
          "Folders"
        ],
        "security": [
          {
            "bearerAuth": [
              "folders:write"
            ]
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateFolderRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Folder created (or returned if it already existed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Folder"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "postFolders"
      }
    },
    "/v1/folders/{id}": {
      "get": {
        "summary": "Get folder",
        "description": "Fetch one folder by id, including its session count, `created_at` / `updated_at`, and the preferences `pinned` / `sort_order` / `order` / `last_opened_at` when present. Requires the `folders:read` scope; `sessions:read` is also accepted.",
        "tags": [
          "Folders"
        ],
        "security": [
          {
            "bearerAuth": [
              "folders:read"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Folder ID",
              "example": "fld_01HX4R7MT8PQZ6VCN9ABYF2KJM"
            },
            "required": true,
            "description": "Folder ID",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Folder",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Folder"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          },
          "404": {
            "description": "Folder not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "getFoldersById"
      },
      "patch": {
        "summary": "Update folder",
        "description": "Rename, recolour, pin, reorder, or change the session sort of a folder. At least one of `name` (trimmed, 1–100 characters), `color` (hex like `#4D5EF6`, or null to clear), `pinned` (boolean), `sort_order` (`newest` | `oldest` | `title`), or `order` (integer >= 0, manual position) is required; omitted fields are unchanged. Bumps `updated_at`. Membership is managed separately via the session-folder endpoints. Requires the `folders:write` scope.",
        "tags": [
          "Folders"
        ],
        "security": [
          {
            "bearerAuth": [
              "folders:write"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Folder ID",
              "example": "fld_01HX4R7MT8PQZ6VCN9ABYF2KJM"
            },
            "required": true,
            "description": "Folder ID",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchFolderRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated folder",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Folder"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          },
          "404": {
            "description": "Folder not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "patchFoldersById"
      },
      "delete": {
        "summary": "Delete folder",
        "description": "Delete a folder. Only the folder is removed — the sessions in it are never deleted, and their folder membership is cleaned up automatically. Requires the `folders:write` scope.",
        "tags": [
          "Folders"
        ],
        "security": [
          {
            "bearerAuth": [
              "folders:write"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Folder ID",
              "example": "fld_01HX4R7MT8PQZ6VCN9ABYF2KJM"
            },
            "required": true,
            "description": "Folder ID",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Folder deleted (no body)"
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          },
          "404": {
            "description": "Folder not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "deleteFoldersById"
      }
    },
    "/v1/sessions/{id}/folders/{folderId}": {
      "post": {
        "summary": "Add session to folder",
        "description": "Link a session to a folder. Folders are non-exclusive — calling this for a session already in the folder is a no-op, and a session can sit in any number of folders simultaneously. Bumps the folder's `updated_at`. Requires the `folders:write` scope.",
        "tags": [
          "Folders"
        ],
        "security": [
          {
            "bearerAuth": [
              "folders:write"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Session ID"
            },
            "required": true,
            "description": "Session ID",
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "description": "Folder ID"
            },
            "required": true,
            "description": "Folder ID",
            "name": "folderId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Session added to folder",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FolderMembershipResponse"
                }
              }
            }
          },
          "404": {
            "description": "Session or folder not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "postSessionsByIdFoldersByFolderId"
      },
      "delete": {
        "summary": "Remove session from folder",
        "description": "Unlink a session from a folder. The session itself is not deleted. Bumps the folder's `updated_at`. Requires the `folders:write` scope.",
        "tags": [
          "Folders"
        ],
        "security": [
          {
            "bearerAuth": [
              "folders:write"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Session ID"
            },
            "required": true,
            "description": "Session ID",
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "description": "Folder ID"
            },
            "required": true,
            "description": "Folder ID",
            "name": "folderId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Session removed from folder",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FolderMembershipResponse"
                }
              }
            }
          },
          "404": {
            "description": "Folder not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "deleteSessionsByIdFoldersByFolderId"
      }
    },
    "/v1/sessions/{id}": {
      "get": {
        "summary": "Get session details",
        "description": "Get detailed information about a specific session, including summary (structured markdown), notes, tags, and favorite flag. Transcript is fetched separately via `/v1/sessions/{id}/transcript`.",
        "tags": [
          "Sessions"
        ],
        "security": [
          {
            "bearerAuth": [
              "sessions:read"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Session ID",
              "example": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM"
            },
            "required": true,
            "description": "Session ID",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Session details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionDetail"
                }
              }
            }
          },
          "404": {
            "description": "Session not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "getSessionsById"
      },
      "patch": {
        "summary": "Update session",
        "description": "Update session metadata (title, notes, tags, favorite, action_items). At least one field is required. Requires the `sessions:write` scope.\n\n**Action items round-trip.** Pass a full `action_items` array to replace the list. Each item needs `text`; `id`, `source`, `assignee`, `due_date`, and `completed` are optional. Items with a known `id` preserve their original `created_at`, `source` and `source_start`. Omit `id` for new items and the server assigns one.\n\n`due_date` is stored as an ISO calendar date (`YYYY-MM-DD`). Send ISO, or natural language such as \"Friday\", \"next Tuesday\" or \"April 25\" and the server normalizes it relative to today; `null` or `\"\"` clears it, and an unparseable value is rejected with `400 invalid_request`. When a known item's `text` or normalized `due_date` changes, the server stamps `user_edited_at` (epoch ms) so Wave's extraction pipeline leaves hand-edited items alone.\n\nFor optimistic concurrency on `action_items`, send `If-Match: <action_items_version>`; the request is rejected with `409 conflict` if another writer has bumped the version in between. Skip the header to force-overwrite.",
        "tags": [
          "Sessions"
        ],
        "security": [
          {
            "bearerAuth": [
              "sessions:write"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Session ID",
              "example": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM"
            },
            "required": true,
            "description": "Session ID",
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "description": "Expected current `action_items_version` (optional). Returns 409 if it disagrees.",
              "example": "2"
            },
            "required": false,
            "description": "Expected current `action_items_version` (optional). Returns 409 if it disagrees.",
            "name": "If-Match",
            "in": "header"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchSessionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Session updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PatchSessionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          },
          "404": {
            "description": "Session not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          },
          "409": {
            "description": "action_items_version conflict (If-Match mismatch)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "patchSessionsById"
      },
      "delete": {
        "summary": "Delete session",
        "description": "Permanently delete a session. Storage files are cleaned up asynchronously.",
        "tags": [
          "Sessions"
        ],
        "security": [
          {
            "bearerAuth": [
              "sessions:delete"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Session ID",
              "example": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM"
            },
            "required": true,
            "description": "Session ID",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Session deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteSessionResponse"
                }
              }
            }
          },
          "404": {
            "description": "Session not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "deleteSessionsById"
      }
    },
    "/v1/sessions/{id}/action-items": {
      "get": {
        "summary": "Get session action items",
        "description": "Return the structured action_items for a session plus the current `version`. Use the returned `version` as `If-Match` on a subsequent PATCH to reject concurrent edits.",
        "tags": [
          "Sessions"
        ],
        "security": [
          {
            "bearerAuth": [
              "sessions:read"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Session ID"
            },
            "required": true,
            "description": "Session ID",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Action items list + version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionItemsResponse"
                }
              }
            }
          },
          "404": {
            "description": "Session not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "getSessionsByIdActionItems"
      }
    },
    "/v1/sessions/{id}/transcript": {
      "get": {
        "summary": "Get session transcript",
        "description": "Returns the full transcript as a flat string plus optional `segments[]` with per-turn speaker labels and start/end times in seconds (floats, not ISO timestamps). Use the flat string for LLM input; use segments for timestamped UIs.",
        "tags": [
          "Transcripts"
        ],
        "security": [
          {
            "bearerAuth": [
              "transcripts:read"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Session ID",
              "example": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM"
            },
            "required": true,
            "description": "Session ID",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Transcript",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptResponse"
                }
              }
            }
          },
          "404": {
            "description": "Session not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "getSessionsByIdTranscript"
      }
    },
    "/v1/sessions/{id}/media": {
      "get": {
        "summary": "Get session media URLs",
        "description": "Get time-limited signed URLs for session audio and video files. URLs expire after 1 hour.",
        "tags": [
          "Media"
        ],
        "security": [
          {
            "bearerAuth": [
              "media:read"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Session ID",
              "example": "sess_01HX4R7MT8PQZ6VCN9ABYF2KJM"
            },
            "required": true,
            "description": "Session ID",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Signed media URLs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaResponse"
                }
              }
            }
          },
          "404": {
            "description": "Session not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "getSessionsByIdMedia"
      }
    },
    "/v1/sessions/search": {
      "post": {
        "summary": "Search sessions",
        "description": "Semantic search across all sessions using vector similarity. Each result includes a `snippet` — useful for triage without pulling the full session. Pass `tags` + `tag_mode` to restrict the vector search to sessions carrying specific tags (exact, case-sensitive).",
        "tags": [
          "Sessions"
        ],
        "security": [
          {
            "bearerAuth": [
              "sessions:search"
            ]
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            }
          }
        },
        "operationId": "postSessionsSearch"
      }
    },
    "/v1/sessions/stats": {
      "get": {
        "summary": "Get session statistics",
        "description": "Get aggregated session statistics for a date range (default: last 30 days).",
        "tags": [
          "Sessions"
        ],
        "security": [
          {
            "bearerAuth": [
              "sessions:read"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Start date (ISO 8601, default: 30 days ago)",
              "example": "2025-03-22T00:00:00.000Z"
            },
            "required": false,
            "description": "Start date (ISO 8601, default: 30 days ago)",
            "name": "since",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "End date (ISO 8601, default: now)",
              "example": "2025-04-21T00:00:00.000Z"
            },
            "required": false,
            "description": "End date (ISO 8601, default: now)",
            "name": "until",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Session statistics",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatsResponse"
                }
              }
            }
          }
        },
        "operationId": "getSessionsStats"
      }
    },
    "/v1/sessions/bulk": {
      "post": {
        "summary": "Bulk export sessions",
        "description": "Fetch multiple sessions by ID in a single request. Max 50 session IDs per request. Set `include_transcript: true` to include full transcripts in the response (requires the `transcripts:read` scope). Ideal for backfilling a knowledge base from history.",
        "tags": [
          "Sessions"
        ],
        "security": [
          {
            "bearerAuth": [
              "sessions:read"
            ]
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkExportRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bulk export results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkExportResponse"
                }
              }
            }
          }
        },
        "operationId": "postSessionsBulk"
      }
    },
    "/v1/account": {
      "get": {
        "summary": "Get account info",
        "description": "Get user profile information including subscription status and session count.",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "bearerAuth": [
              "account:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Account information",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountResponse"
                }
              }
            }
          }
        },
        "operationId": "getAccount"
      }
    },
    "/v1/events": {
      "get": {
        "summary": "List events since cursor",
        "description": "Pull events the caller has not yet acknowledged. The server tracks a per-token cursor so clients do not have to maintain their own dedupe state — the hard-won lesson that motivated this endpoint. Pass `cursor` explicitly to replay from a known point; omit it to resume from the token's last ack.\n\nUse with `POST /v1/events/ack` to advance the cursor. This is a drop-in alternative (or complement) to webhooks for integrators who prefer polling — same event shapes, same ids.\n\nEvent types match the webhooks subscription list: `session.completed`, `session.updated`, `session.deleted`, `session.action_items.updated`.",
        "tags": [
          "Events"
        ],
        "security": [
          {
            "bearerAuth": [
              "events:read"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Integer seq. Events with seq > cursor are returned. Defaults to the token's last acknowledged position.",
              "example": "4281"
            },
            "required": false,
            "description": "Integer seq. Events with seq > cursor are returned. Defaults to the token's last acknowledged position.",
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Max events (default 50, max 200)",
              "example": "50"
            },
            "required": false,
            "description": "Max events (default 50, max 200)",
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Event list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventsListResponse"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "getEvents"
      }
    },
    "/v1/events/ack": {
      "post": {
        "summary": "Acknowledge events through cursor",
        "description": "Advance the caller's server-side cursor so future `GET /v1/events` calls skip events up to and including `cursor`. Acks are monotonic — a cursor lower than the currently persisted value is a no-op.",
        "tags": [
          "Events"
        ],
        "security": [
          {
            "bearerAuth": [
              "events:read"
            ]
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AckEventsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cursor advanced",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AckEventsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "postEventsAck"
      }
    },
    "/v1/webhooks": {
      "get": {
        "summary": "List webhooks",
        "description": "List all registered webhooks for the authenticated user.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "bearerAuth": [
              "webhooks:manage"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookListResponse"
                }
              }
            }
          }
        },
        "operationId": "getWebhooks"
      },
      "post": {
        "summary": "Create webhook",
        "description": "Register a new webhook endpoint. The signing secret is only returned once at creation time. Max 5 webhooks per user.\n\n**Events**:\n- `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.\n- `session.updated` — session metadata was changed (title, summary, notes, tags, favorite). Fires for edits from any source: the API, mobile app, or web app.\n- `session.action_items.updated` — structured action items on a session were written or edited via PATCH. Carries the full session in `data.session`, including the new `action_items` and `action_items_version`.\n- `session.deleted` — session was deleted (soft-delete or hard-delete of a completed session).",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "bearerAuth": [
              "webhooks:manage"
            ]
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created (includes secret)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "postWebhooks"
      }
    },
    "/v1/webhooks/{id}": {
      "patch": {
        "summary": "Update webhook",
        "description": "Update webhook URL, events, or active status.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "bearerAuth": [
              "webhooks:manage"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Webhook ID",
              "example": "wh_01HX4R7MT8PQZ6VCN9ABYF2KJM"
            },
            "required": true,
            "description": "Webhook ID",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "patchWebhooksById"
      },
      "delete": {
        "summary": "Delete webhook",
        "description": "Delete a webhook and clean up any pending deliveries.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "bearerAuth": [
              "webhooks:manage"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Webhook ID",
              "example": "wh_01HX4R7MT8PQZ6VCN9ABYF2KJM"
            },
            "required": true,
            "description": "Webhook ID",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteSessionResponse"
                }
              }
            }
          }
        },
        "operationId": "deleteWebhooksById"
      }
    },
    "/v1/webhooks/{id}/rotate-secret": {
      "post": {
        "summary": "Rotate webhook secret",
        "description": "Regenerate the webhook signing secret. The new secret is returned in the response.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "bearerAuth": [
              "webhooks:manage"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Webhook ID",
              "example": "wh_01HX4R7MT8PQZ6VCN9ABYF2KJM"
            },
            "required": true,
            "description": "Webhook ID",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Secret rotated (includes new secret)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "postWebhooksByIdRotateSecret"
      }
    },
    "/v1/webhooks/{id}/test": {
      "post": {
        "summary": "Test webhook",
        "description": "Send a test event to the webhook endpoint and report delivery status.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "bearerAuth": [
              "webhooks:manage"
            ]
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Webhook ID",
              "example": "wh_01HX4R7MT8PQZ6VCN9ABYF2KJM"
            },
            "required": true,
            "description": "Webhook ID",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Test delivery result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookTestResponse"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIError"
                }
              }
            }
          }
        },
        "operationId": "postWebhooksByIdTest"
      }
    }
  },
  "webhooks": {},
  "x-scopes": {
    "sessions:read": "List and read session metadata, summaries, and action items.",
    "sessions:write": "Update session titles, notes, tags, and structured action items.",
    "sessions:delete": "Delete a session and its recording.",
    "sessions:search": "Run semantic search across the account's sessions.",
    "transcripts:read": "Read full transcripts with speaker segments.",
    "media:read": "Mint signed URLs for session audio and video.",
    "account:read": "Read the token owner's profile and subscription state.",
    "folders:read": "List and read folders.",
    "folders:write": "Create, rename, recolour and delete folders and manage session membership.",
    "events:read": "Poll the per-token event cursor feed and acknowledge events.",
    "webhooks:manage": "Register webhook endpoints, rotate secrets, and send test deliveries."
  },
  "externalDocs": {
    "description": "Wave developer portal",
    "url": "https://wave.co/developers"
  }
}
