openapi: 3.1.0
info:
  title: ScholarCal Integration API
  version: 1.0.0
  description: Public, user-session, Zoom OAuth, and incoming webhook contracts used by ScholarCal.
servers:
  - url: https://scholarcal.com
    description: Production
  - url: http://localhost:3001
    description: Local development
tags:
  - name: Zoom OAuth
  - name: Meetings
  - name: Public API
  - name: Agent Planning
  - name: MCP
  - name: Webhooks
paths:
  /api/integrations/zoom/oauth/start:
    get:
      tags: [Zoom OAuth]
      summary: Begin Zoom authorization
      security: [{ sessionCookie: [] }]
      responses:
        '302': { description: Redirect to Zoom authorization }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /api/integrations/zoom/oauth/callback:
    get:
      tags: [Zoom OAuth]
      summary: Complete Zoom authorization
      parameters:
        - { name: code, in: query, required: true, schema: { type: string } }
        - { name: state, in: query, required: true, schema: { type: string } }
      responses:
        '302': { description: Zoom connection stored; redirect to ScholarCal settings }
        '400': { description: Missing or invalid OAuth state }
  /api/integrations/zoom/deauthorization:
    post:
      tags: [Webhooks]
      summary: Receive Zoom app deauthorization
      description: Verifies Zoom v0 HMAC headers with a five-minute replay window, then deletes the matching Zoom connection and tokens.
      security: [{ zoomWebhookSignature: [] }]
      parameters:
        - { name: x-zm-request-timestamp, in: header, required: true, schema: { type: string } }
        - { name: x-zm-signature, in: header, required: true, schema: { type: string, pattern: '^v0=' } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ZoomDeauthorization' }
      responses:
        '200': { description: Connection data deleted }
        '401': { description: Missing, stale, or invalid signature }
  /api/meetings/schedule:
    post:
      tags: [Meetings]
      summary: Schedule a Google Meet, Teams, or Zoom meeting
      security: [{ sessionCookie: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ScheduleMeeting' }
      responses:
        '201':
          description: Provider meeting and ScholarCal event created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ScheduledMeetingResponse' }
        '400': { description: Invalid request }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '502': { description: Provider or persistence failure }
  /api/meetings/start:
    post:
      tags: [Meetings]
      summary: Start native capture or an optional external meeting assistant
      security: [{ sessionCookie: [] }, { scholarCalApiKey: [] }]
      responses:
        '200': { description: Capture session started }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /api/meetings/provider/callback:
    post:
      tags: [Webhooks]
      summary: Receive transcript and status from the trusted external recorder
      security: [{ recorderHmac: [] }]
      parameters:
        - { name: x-meetings-signature, in: header, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                meeting_id: { type: string, format: uuid }
                external_session_id: { type: string }
                event: { type: string, enum: [transcript.ready, summary.ready, recommendations.ready, meeting.failed] }
                transcript: { type: object }
      responses:
        '200': { description: Callback applied }
        '401': { description: Invalid HMAC signature }
  /api/public/v1/events:
    post:
      tags: [Public API]
      summary: Create a calendar event
      security: [{ scholarCalApiKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PublicEventCreate' }
      responses:
        '201': { description: Event created }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /api/public/v1/calendar/availability:
    get:
      tags: [Public API]
      summary: Get availability in a time window
      security: [{ scholarCalApiKey: [] }]
      parameters:
        - { name: start, in: query, required: true, schema: { type: string, format: date-time } }
        - { name: end, in: query, required: true, schema: { type: string, format: date-time } }
      responses:
        '200': { description: Availability result }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /api/public/v1/agents/negotiate:
    post:
      tags: [Public API]
      summary: Propose an agent-negotiated meeting
      security: [{ scholarCalApiKey: [] }]
      responses:
        '201': { description: Proposal created }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /api/public/v1/context:
    get:
      tags: [Agent Planning]
      summary: Read a private, redacted planning snapshot
      description: Returns free windows, actionable tasks, accessible calendar names/roles, and relevant preference memories for the authenticated owner. The maximum range is 31 days.
      security: [{ scholarCalApiKey: [] }]
      parameters:
        - { name: start, in: query, schema: { type: string, format: date-time } }
        - { name: end, in: query, schema: { type: string, format: date-time } }
        - { name: query, in: query, schema: { type: string, maxLength: 500 } }
      responses:
        '200':
          description: Owner-scoped planning context
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PlanningContextResponse' }
        '400': { description: Invalid or overlong range }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { description: "agent:read scope required" }
  /api/public/v1/assistant/schedule:
    post:
      tags: [Agent Planning]
      summary: Plan and schedule a natural-language request
      description: Stable API-key adapter over ScholarCal's canonical assistant scheduler. The result can be completed, proposed, awaiting input, conflicted, or running; clients must inspect stage and status before claiming success.
      security: [{ scholarCalApiKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [message, idempotency_key]
              properties:
                message: { type: string, minLength: 1, maxLength: 8000 }
                idempotency_key: { type: string, format: uuid }
      responses:
        '200': { description: Scheduling result or proposal }
        '400': { description: Validation failed }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '409': { description: Scheduling conflict or command already in progress }
  /api/assistant/workflow/{workflow_id}:
    get:
      tags: [Agent Planning]
      summary: Inspect a workflow and its step receipts
      security: [{ scholarCalApiKey: [] }]
      parameters:
        - { name: workflow_id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200': { description: Workflow status and steps }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { description: Workflow not found }
  /api/public/v1/assistant/workflows/{workflow_id}/undo:
    post:
      tags: [Agent Planning]
      summary: Cancel events created by a completed workflow
      description: Available for seven days after workflow creation. A 207 response reports a partial undo and includes per-event receipts.
      security: [{ scholarCalApiKey: [] }]
      parameters:
        - { name: workflow_id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200': { description: Every workflow-created event was canceled }
        '207': { description: Some events could not be canceled }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { description: Workflow not found }
        '410': { description: Seven-day undo window expired }
  /api/mcp:
    post:
      tags: [MCP]
      summary: ScholarCal Streamable HTTP MCP endpoint
      description: Stateless MCP endpoint. Authenticate with a scoped ScholarCal API key; individual tools enforce agent:read or agent:write.
      security: [{ scholarCalApiKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, additionalProperties: true }
      responses:
        '200': { description: MCP JSON-RPC response or stream }
        '401': { $ref: '#/components/responses/Unauthorized' }
components:
  securitySchemes:
    sessionCookie:
      type: apiKey
      in: cookie
      name: sb-access-token
      description: Supabase-authenticated ScholarCal web session. Actual cookie name may include the project reference.
    scholarCalApiKey:
      type: http
      scheme: bearer
      bearerFormat: sc-API-key
      description: Bearer API key beginning with sc-. Keys are SHA-256 hashed at rest and scope checked.
    zoomWebhookSignature:
      type: apiKey
      in: header
      name: x-zm-signature
      description: HMAC-SHA256 of v0:{timestamp}:{raw_body} using ZOOM_WEBHOOK_SECRET_TOKEN.
    recorderHmac:
      type: apiKey
      in: header
      name: x-meetings-signature
      description: HMAC-SHA256 of the raw body using MEETINGS_WEBHOOK_SECRET.
  responses:
    Unauthorized:
      description: Authentication failed
  schemas:
    PublicEventCreate:
      type: object
      additionalProperties: false
      required: [title, start_time, end_time]
      properties:
        title: { type: string, minLength: 1, maxLength: 255 }
        description: { type: string }
        location: { type: string }
        start_time: { type: string, format: date-time }
        end_time: { type: string, format: date-time }
        provider: { type: string, enum: [local, google, microsoft], default: local }
        force: { type: boolean, default: false }
    PlanningTask:
      type: object
      required: [id, title, priority, status]
      properties:
        id: { type: string, format: uuid }
        title: { type: string }
        due_at: { type: [string, 'null'], format: date-time }
        priority: { type: string, enum: [low, medium, high] }
        status: { type: string }
        estimated_hours: { type: [number, 'null'] }
        source_type: { type: [string, 'null'] }
    PlanningContextResponse:
      type: object
      required: [success, data]
      properties:
        success: { const: true }
        data:
          type: object
          required: [timezone, range, free_windows, urgent_tasks, upcoming_tasks, calendars, preferences]
          properties:
            timezone: { type: string }
            range:
              type: object
              properties:
                start: { type: string, format: date-time }
                end: { type: string, format: date-time }
            free_windows: { type: array, items: { type: object, additionalProperties: true } }
            urgent_tasks: { type: array, items: { $ref: '#/components/schemas/PlanningTask' } }
            upcoming_tasks: { type: array, items: { $ref: '#/components/schemas/PlanningTask' } }
            calendars: { type: array, items: { type: object, additionalProperties: true } }
            preferences: { type: array, items: { type: object, additionalProperties: true } }
    ZoomDeauthorization:
      type: object
      required: [event, payload]
      properties:
        event: { const: app_deauthorized }
        event_ts: { type: integer }
        payload:
          type: object
          required: [account_id, user_id, client_id, deauthorization_time, signature]
          properties:
            account_id: { type: string }
            user_id: { type: string }
            client_id: { type: string }
            deauthorization_time: { type: string, format: date-time }
            signature: { type: string }
    ScheduleMeeting:
      type: object
      required: [title, start_time, end_time, timezone, provider, recipients, connection_ids, idempotency_key]
      properties:
        title: { type: string, maxLength: 300 }
        description: { type: string, maxLength: 4000 }
        start_time: { type: string, format: date-time }
        end_time: { type: string, format: date-time }
        timezone: { type: string }
        provider: { type: string, enum: [google, microsoft, zoom] }
        recipients: { type: array, maxItems: 100, items: { type: string, format: email } }
        connection_ids: { type: array, maxItems: 100, items: { type: string, format: uuid } }
        idempotency_key: { type: string, format: uuid }
    ScheduledMeetingResponse:
      type: object
      properties:
        success: { const: true }
        event: { type: object }
        conference:
          type: object
          properties:
            type: { type: string, enum: [meet, teams, zoom] }
            link: { type: string, format: uri }
            id: { type: [string, "null"] }
        invitation_warnings: { type: array, items: { type: string } }
