{
  "openapi": "3.0.3",
  "info": {
    "title": "confcall control plane (curated subset)",
    "version": "post-phase-1",
    "description": "Curated OpenAPI subset of confcall's control-plane REST API — the routes an sk_ secret API key, a pk_ publishable key, or a room-scoped JWT can call. Not the full backend-csharp surface (see specs/RFC-Builder-Onboarding-And-Docs.md Phase 5). Every operation's x-auth/x-scope value is pinned against the real [Authorize]/[RequireScope] attributes by backend-csharp/tests/VideoConference.Tests/Contracts/DocsOpenApiContractTests.cs."
  },
  "servers": [
    { "url": "https://meet.confcall.app", "description": "Production" }
  ],
  "security": [
    { "ApiKeyAuth": [] }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "A confcall secret (sk_live_/sk_test_) API key. Publishable (pk_) keys are rejected on every route documented here."
      }
    }
  },
  "paths": {
    "/api/embed/rooms": {
      "post": {
        "operationId": "createEmbedRoom",
        "summary": "Create an embed-flagged room owned by the calling account",
        "x-auth": "apiKey",
        "x-auth-note": "sk_ only — there is no JWT alternative for this route.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "type": "object", "properties": { "roomName": { "type": "string", "nullable": true } } }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Room created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "roomId": { "type": "string" },
                    "embedUrl": { "type": "string" }
                  }
                }
              }
            }
          },
          "401": { "description": "Missing/invalid X-API-Key, or the key has no associated account" }
        }
      }
    },
    "/api/embed/sessions": {
      "post": {
        "operationId": "createEmbedSession",
        "summary": "Mint a short-lived, room-scoped join token for an external user",
        "x-auth": "apiKey",
        "x-auth-note": "sk_ only — there is no JWT alternative for this route.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["roomId", "externalUserId"],
                "properties": {
                  "roomId": { "type": "string" },
                  "externalUserId": { "type": "string" },
                  "displayName": { "type": "string", "nullable": true },
                  "role": { "type": "string", "default": "participant", "enum": ["host", "co-host", "participant"] },
                  "ttlSeconds": { "type": "integer", "minimum": 1 }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Session token minted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": { "type": "string" },
                    "expiresAt": { "type": "string", "format": "date-time" },
                    "embedUrl": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "description": "Missing roomId/externalUserId, non-positive ttlSeconds, or an unrecognized role" },
          "404": { "description": "Room not found, not an embed room, or not owned by this account (same 404 for all three, so this can't be used to probe other accounts' room ids)" },
          "502": { "description": "Token minting failed upstream" }
        }
      }
    },
    "/api/rooms/{roomId}": {
      "get": {
        "operationId": "getRoom",
        "summary": "Read room metadata",
        "x-auth": "public",
        "parameters": [
          { "name": "roomId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Room metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "roomId": { "type": "string" },
                    "roomName": { "type": "string" },
                    "shareableLink": { "type": "string" },
                    "createdAt": { "type": "string", "format": "date-time" },
                    "lastActivityAt": { "type": "string", "format": "date-time", "nullable": true },
                    "isOwner": { "type": "boolean" },
                    "isCoOrganizer": { "type": "boolean" },
                    "isPublic": { "type": "boolean" },
                    "requiresAuth": { "type": "boolean" }
                  }
                }
              }
            }
          },
          "404": { "description": "Room not found" }
        }
      },
      "delete": {
        "operationId": "deleteRoom",
        "summary": "Delete a room",
        "x-auth": "jwtOrApiKey",
        "x-scope": "confcall:rooms",
        "x-auth-note": "Owner-scoped: a JWT must belong to the room's human owner; an sk_ key must belong to the room's account (RFC-Builder-Onboarding-And-Docs Phase 1). A different account's key gets 403.",
        "parameters": [
          { "name": "roomId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "204": { "description": "Deleted" },
          "403": { "description": "Caller does not own this room" },
          "404": { "description": "Room not found" }
        }
      }
    },
    "/api/rooms/{roomId}/ai-agent/invite": {
      "post": {
        "operationId": "inviteAiAgent",
        "summary": "Invite the AI agent into a room",
        "x-auth": "jwtOrApiKey",
        "x-scope": "confcall:rooms",
        "x-auth-note": "Owner/co-organizer (JWT) or the room's owning account (sk_ key). E2EE rooms refuse the agent (409) — confcall never distributes the room key to a server-side party.",
        "parameters": [
          { "name": "roomId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": { "type": "string", "example": "Notetaker" },
                  "inlineConfig": {
                    "type": "object",
                    "properties": { "language": { "type": "string", "example": "en" } }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Agent invited" },
          "403": { "description": "Caller is not an owner/co-organizer, or the key's account does not own the room" },
          "404": { "description": "Room not found" },
          "409": { "description": "Room is E2EE-enabled" },
          "503": { "description": "AI Agent service unavailable" }
        }
      }
    },
    "/api/rooms/{roomId}/ai-agent/remove": {
      "post": {
        "operationId": "removeAiAgent",
        "summary": "Remove the AI agent from a room",
        "x-auth": "jwtOrApiKey",
        "x-scope": "confcall:rooms",
        "parameters": [
          { "name": "roomId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Agent removed" },
          "403": { "description": "Caller is not an owner/co-organizer, or the key's account does not own the room" },
          "404": { "description": "Room not found" },
          "503": { "description": "AI Agent service unavailable" }
        }
      }
    },
    "/api/rooms/{roomId}/ai-agent/status": {
      "get": {
        "operationId": "getAiAgentStatus",
        "summary": "Check whether the AI agent is present in a room",
        "x-auth": "public",
        "x-auth-note": "Degrades to { agentPresent: false, available: false } rather than erroring when the downstream call can't authenticate.",
        "parameters": [
          { "name": "roomId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Agent status" },
          "404": { "description": "Room not found" }
        }
      }
    },
    "/api/rooms/{roomId}/artifacts": {
      "get": {
        "operationId": "listRoomArtifacts",
        "summary": "List session artifacts (summarized) for a room",
        "x-auth": "jwtOrApiKey",
        "x-scope": "confcall:history",
        "x-auth-note": "Owner/co-organizer/invited-member (JWT) or the room's owning account (sk_ key).",
        "parameters": [
          { "name": "roomId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "{ artifacts: ArtifactBundle[] }" },
          "403": { "description": "Caller has no access to this room" }
        }
      }
    },
    "/api/artifacts/{sessionId}": {
      "get": {
        "operationId": "getArtifact",
        "summary": "Get a single session's full transcript, summary, decisions, and action items",
        "x-auth": "jwtOrApiKey",
        "x-scope": "confcall:history",
        "parameters": [
          { "name": "sessionId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Artifact bundle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "conferenceSessionId": { "type": "string" },
                    "roomId": { "type": "string" },
                    "startedAt": { "type": "string", "format": "date-time" },
                    "endedAt": { "type": "string", "format": "date-time", "nullable": true },
                    "turns": { "type": "array", "items": { "type": "object" } },
                    "summaryText": { "type": "string", "nullable": true },
                    "actionItems": { "type": "array", "items": { "type": "object" }, "nullable": true },
                    "outcome": { "type": "string", "nullable": true },
                    "decisions": { "type": "array", "items": { "type": "string" }, "nullable": true },
                    "followUps": { "type": "array", "items": { "type": "string" }, "nullable": true }
                  }
                }
              }
            }
          },
          "403": { "description": "Caller has no access to this session's room" },
          "404": { "description": "Artifact not found" }
        }
      }
    },
    "/api/recordings": {
      "get": {
        "operationId": "listRecordings",
        "summary": "List recordings the caller may view",
        "x-auth": "jwtOrApiKey",
        "x-auth-note": "JWT: union of rooms the user owns/co-hosts/was invited to. sk_ key: every room owned by the key's account.",
        "parameters": [
          { "name": "roomId", "in": "query", "required": false, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "{ recordings: Recording[] }" },
          "403": { "description": "roomId given but caller has no access to that room" }
        }
      }
    },
    "/api/recordings/{id}": {
      "get": {
        "operationId": "getRecording",
        "summary": "Recording detail",
        "x-auth": "jwt",
        "x-auth-note": "JWT-only — not sk_-callable. Use listRecordings or streamRecording for API-key integrations.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": { "description": "Recording detail" },
          "403": { "description": "Caller has no access to this recording" },
          "404": { "description": "Recording not found" }
        }
      }
    },
    "/api/recordings/{id}/stream": {
      "get": {
        "operationId": "streamRecording",
        "summary": "Get a time-limited playback reference for a recording",
        "x-auth": "jwtOrApiKey",
        "x-auth-note": "sk_ key: any recording owned by its own account. Returns a pre-signed URL for S3/Azure-backed recordings, or a signed token for the legacy local-file endpoint.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": { "description": "{ url } or { replayOutOfBand: true, ... } for BYOS-restricted playback" },
          "403": { "description": "Caller/key does not own this recording" },
          "404": { "description": "Recording not found" }
        }
      }
    },
    "/api/recordings/{id}/download": {
      "get": {
        "operationId": "getRecordingDownload",
        "summary": "Get a downloadable reference for a recording",
        "x-auth": "jwt",
        "x-auth-note": "JWT-only — not sk_-callable.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": { "description": "Download reference" },
          "403": { "description": "Caller has no access to this recording" },
          "404": { "description": "Recording not found" }
        }
      }
    },
    "/api/recordings/{id}/file": {
      "get": {
        "operationId": "getRecordingFile",
        "summary": "Stream a legacy/local recording file by signed token",
        "x-auth": "public",
        "x-auth-note": "Authorization is the signed `token` query parameter itself (from streamRecording/getRecordingDownload), not a bearer credential.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } },
          { "name": "token", "in": "query", "required": false, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Recording file bytes" },
          "401": { "description": "Missing/invalid/expired token" },
          "404": { "description": "Recording not found" }
        }
      }
    }
  }
}
