{
  "openapi": "3.1.0",
  "info": {
    "title": "Bisque API",
    "version": "1.0.0",
    "summary": "Publish narrated presentations from an agent, and read any shared presentation as text.",
    "description": "Bisque turns an HTML file an agent wrote into a narrated presentation on a shareable watch link (`/p/{handle}/{slug}`), and lets an agent read any shared presentation back as a transcript.\n\n**Start here:** `GET /api/presentations/spec` returns the authoring format; `GET /api/presentations/context` reads a presentation and needs no credentials. `POST /api/presentations/create` publishes and needs a bearer credential — see `/auth.md` for how an agent obtains one.\n\nThe same tools are available over MCP (Streamable HTTP) at `/mcp`; the docs are queryable over MCP at `/docs/mcp` with no credentials.",
    "termsOfService": "https://bisque.cloud/terms",
    "contact": {
      "name": "Bisque",
      "email": "hello@bisque.today",
      "url": "https://bisque.today/docs/api"
    },
    "x-logo": {
      "url": "https://bisque.today/favicon-512x512.png"
    }
  },
  "externalDocs": {
    "description": "API guide",
    "url": "https://bisque.today/docs/api"
  },
  "servers": [
    {
      "url": "https://bisque.today",
      "description": "Public host (bisque.today). Every path below resolves here."
    },
    {
      "url": "https://bisque.cloud",
      "description": "App host (bisque.cloud). Same API; also the OAuth issuer."
    }
  ],
  "tags": [
    {
      "name": "Read",
      "description": "Public reads. No credential needed."
    },
    {
      "name": "Publish",
      "description": "Create and check presentations. Needs a bearer credential."
    },
    {
      "name": "MCP",
      "description": "JSON-RPC 2.0 over Streamable HTTP (MCP 2025-06-18)."
    },
    {
      "name": "OAuth",
      "description": "OAuth 2.1 authorization server on bisque.cloud; see /auth.md."
    },
    {
      "name": "Discovery",
      "description": "Machine-readable site descriptions."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/presentations/spec": {
      "get": {
        "tags": [
          "Read"
        ],
        "operationId": "getPresentationSpec",
        "summary": "The html-presentation/v1 authoring format",
        "description": "Markdown. Fetch this before writing a presentation; never write the format from memory. Modules are addressable at `/api/presentations/spec/{part}`.",
        "security": [],
        "responses": {
          "200": {
            "description": "The format contract.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/presentations/spec/{part}": {
      "get": {
        "tags": [
          "Read"
        ],
        "operationId": "getPresentationSpecPart",
        "summary": "One module of the authoring format",
        "security": [],
        "parameters": [
          {
            "name": "part",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Module name, with or without `.md`. The index lists the modules."
          }
        ],
        "responses": {
          "200": {
            "description": "The module.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Unknown module.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "part": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/presentations/context": {
      "get": {
        "tags": [
          "Read"
        ],
        "operationId": "getPresentationContext",
        "summary": "Read a presentation as text",
        "description": "Metadata, the per-slide narration transcript with timestamps, and the bundle's `context.md`. Public and unlisted presentations resolve without credentials; send a bearer credential to read your own private ones. Anonymous callers get 60 requests per 10 minutes per IP, authenticated callers 300.",
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "description": "A watch URL, `https://bisque.today/p/{handle}/{slug}`. Either this or `handle` + `slug`.",
            "example": "https://bisque.today/p/siderakis/agent-readiness"
          },
          {
            "name": "handle",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Channel handle (leading @ tolerated)."
          },
          {
            "name": "slug",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The presentation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PresentationContext"
                }
              }
            }
          },
          "400": {
            "description": "Neither `url` nor `handle` + `slug` identified a presentation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "A credential was sent and is unusable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No presentation you can see at that address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (`CONTEXT_RATE_LIMITED`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/oembed": {
      "get": {
        "tags": [
          "Read"
        ],
        "operationId": "getOEmbed",
        "summary": "oEmbed for a watch URL",
        "security": [],
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "oEmbed `video` payload.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Not a Bisque watch URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No public presentation there.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/presentations/create": {
      "post": {
        "tags": [
          "Publish"
        ],
        "operationId": "createPresentation",
        "summary": "Publish a presentation from HTML",
        "description": "Send the `html-presentation/v1` file. Bisque parses it, narrates each slide server-side, and publishes to `webUrl`. The reply is immediate (202); narration finishes in the background — poll `statusUrl`. Re-send with the same `slug` to republish.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePresentationRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted; narration queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatePresentationResult"
                }
              }
            }
          },
          "400": {
            "description": "The HTML or a field failed validation; `error.message` names the problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A create for the same presentation is still running (`CREATE_IN_PROGRESS`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/presentations/status": {
      "get": {
        "tags": [
          "Publish"
        ],
        "operationId": "getPresentationStatus",
        "summary": "Narration and publish progress",
        "parameters": [
          {
            "name": "presentationId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PresentationStatus"
                }
              }
            }
          },
          "400": {
            "description": "`presentationId` missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of your presentations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/presentations/publish-narrated": {
      "post": {
        "tags": [
          "Publish"
        ],
        "operationId": "publishNarratedPresentation",
        "summary": "Publish with audio you synthesized locally",
        "description": "The `present` skill's path: narrate each slide with the `bisque-voice` CLI, then post the HTML plus per-slide MP3s and word timings. Slides you omit from `audio` carry their audio forward from the last publish when their narration text is unchanged. API-key callers only.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublishNarratedRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatePresentationResult"
                }
              }
            }
          },
          "400": {
            "description": "Audio or HTML failed validation; the message names the slide.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "tags": [
          "MCP"
        ],
        "operationId": "presentationsMcp",
        "summary": "Presentations MCP server",
        "description": "Streamable HTTP, stateless. Tools: `get_presentation_spec`, `create_presentation`, `publish_narrated_presentation`, `get_presentation_status`, `list_presentations`, `get_presentation_analytics`, `get_presentation_context`, plus org administration for bisque.team admins. Prompt `present`; resource `bisque://presentations/format-spec`; MCP Apps widget `ui://bisque/presentation-player.html` linked from tools via `_meta.ui.resourceUri`. Canonical URL `https://bisque.cloud/presentations/mcp`. A 401 carries `WWW-Authenticate: Bearer resource_metadata=...` for OAuth discovery.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "presentations"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "examples": {
                "initialize": {
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "initialize",
                    "params": {
                      "protocolVersion": "2025-06-18",
                      "capabilities": {},
                      "clientInfo": {
                        "name": "example",
                        "version": "1.0"
                      }
                    }
                  }
                },
                "listTools": {
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/list"
                  }
                }
              }
            }
          },
          "description": "A JSON-RPC 2.0 request or notification."
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result or error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "202": {
            "description": "Notification accepted."
          },
          "401": {
            "description": "Unauthorized; see `WWW-Authenticate`.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/docs/mcp": {
      "post": {
        "tags": [
          "MCP"
        ],
        "operationId": "docsMcp",
        "summary": "Documentation MCP server",
        "description": "Streamable HTTP, stateless, no credentials. Tools: `list_docs`, `read_doc`, `search_docs`, `get_openapi_spec`; each docs page is also a `bisque://docs/{slug}` resource. Answers 'how does Bisque work' questions from the same markdown the site renders.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "examples": {
                "initialize": {
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "initialize",
                    "params": {
                      "protocolVersion": "2025-06-18",
                      "capabilities": {},
                      "clientInfo": {
                        "name": "example",
                        "version": "1.0"
                      }
                    }
                  }
                },
                "listTools": {
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/list"
                  }
                }
              }
            }
          },
          "description": "A JSON-RPC 2.0 request or notification."
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result or error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "202": {
            "description": "Notification accepted."
          }
        }
      }
    },
    "/oauth/register": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "operationId": "oauthRegister",
        "summary": "Dynamic client registration (RFC 7591)",
        "servers": [
          {
            "url": "https://bisque.cloud"
          }
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "redirect_uris"
                ],
                "properties": {
                  "client_name": {
                    "type": "string"
                  },
                  "redirect_uris": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uri"
                    },
                    "description": "https, or http on loopback."
                  },
                  "token_endpoint_auth_method": {
                    "type": "string",
                    "enum": [
                      "none",
                      "client_secret_post",
                      "client_secret_basic"
                    ],
                    "default": "client_secret_basic"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The client.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "client_id": {
                      "type": "string"
                    },
                    "client_secret": {
                      "type": "string"
                    },
                    "client_name": {
                      "type": "string"
                    },
                    "redirect_uris": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "token_endpoint_auth_method": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad registration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "operationId": "oauthToken",
        "summary": "Token endpoint (authorization_code + PKCE, refresh_token)",
        "servers": [
          {
            "url": "https://bisque.cloud"
          }
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "client_id"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "authorization_code",
                      "refresh_token"
                    ]
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string"
                  },
                  "code_verifier": {
                    "type": "string"
                  },
                  "redirect_uri": {
                    "type": "string",
                    "format": "uri"
                  },
                  "refresh_token": {
                    "type": "string"
                  },
                  "resource": {
                    "type": "string",
                    "format": "uri",
                    "description": "Must be https://bisque.cloud/presentations/mcp when sent."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A token pair.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string",
                      "description": "`bisque_oat_…`, 1 hour."
                    },
                    "token_type": {
                      "type": "string",
                      "const": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer"
                    },
                    "refresh_token": {
                      "type": "string",
                      "description": "`bisque_ort_…`, 90 days, rotates on use."
                    },
                    "scope": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad grant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Client authentication failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/revoke": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "operationId": "oauthRevoke",
        "summary": "Revoke a token (RFC 7009)",
        "description": "Revoking a refresh token also revokes the access tokens issued with it. Unknown tokens return 200.",
        "servers": [
          {
            "url": "https://bisque.cloud"
          }
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "token",
                  "client_id"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "token_type_hint": {
                    "type": "string",
                    "enum": [
                      "access_token",
                      "refresh_token"
                    ]
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The token is no longer usable.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "`token` missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Client authentication failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/userinfo": {
      "get": {
        "tags": [
          "OAuth"
        ],
        "operationId": "oauthUserInfo",
        "summary": "OIDC-style userinfo for openid-scoped tokens",
        "servers": [
          {
            "url": "https://bisque.cloud"
          }
        ],
        "security": [
          {
            "oauth2": [
              "openid"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "`sub` and, with the email scope, `email`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sub": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token lacks the openid scope or is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "oauthAuthorizationServerMetadata",
        "summary": "RFC 8414 authorization-server metadata, with the auth.md `agent_auth` block",
        "security": [],
        "responses": {
          "200": {
            "description": "Metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "oauthProtectedResourceMetadata",
        "summary": "RFC 9728 protected-resource metadata for the MCP server",
        "security": [],
        "responses": {
          "200": {
            "description": "Metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/http-message-signatures-directory": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "webBotAuthDirectory",
        "summary": "Web Bot Auth key directory (Ed25519 JWKs) for requests Bisque's agents sign",
        "security": [],
        "responses": {
          "200": {
            "description": "`keys` array of OKP/Ed25519 JWKs with `kid`, `nbf`, `exp`.",
            "content": {
              "application/http-message-signatures-directory+json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth.md": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "authMd",
        "summary": "How an agent obtains, uses, and revokes a credential",
        "security": [],
        "responses": {
          "200": {
            "description": "Markdown walkthrough.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "llmsTxt",
        "summary": "What this site is, when to use it, and where the docs are",
        "security": [],
        "responses": {
          "200": {
            "description": "Plain text (llmstxt.org).",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A Bisque API key (`bisque_live_…`, from https://bisque.cloud/setup/keys or the `bisque login` device flow) or an OAuth access token (`bisque_oat_…`). Send as `Authorization: Bearer <credential>`. Walkthrough: /auth.md."
      },
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 with PKCE (S256) and dynamic client registration. Issuer https://bisque.cloud.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://bisque.cloud/oauth/authorize",
            "tokenUrl": "https://bisque.cloud/oauth/token",
            "refreshUrl": "https://bisque.cloud/oauth/token",
            "scopes": {
              "presentations": "Publish, list, and read presentations as the user.",
              "openid": "Read the user's subject identifier.",
              "email": "Read the user's email address."
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every error is JSON. `error.code` is stable and upper-snake-case; `error.message` is for people; `requestId` is what to quote in a bug report.",
        "required": [
          "error"
        ],
        "properties": {
          "requestId": {
            "type": "string",
            "format": "uuid"
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "examples": [
                  "UNAUTHORIZED",
                  "INVALID_ACCESS_TOKEN",
                  "INVALID_PRESENTATION_ID",
                  "CONTEXT_RATE_LIMITED",
                  "METHOD_NOT_ALLOWED",
                  "NOT_FOUND"
                ]
              },
              "message": {
                "type": "string"
              },
              "hint": {
                "type": "string",
                "description": "Where to look next, when there is one."
              }
            }
          }
        }
      },
      "OAuthError": {
        "type": "object",
        "description": "RFC 6749 §5.2 error body.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "examples": [
              "invalid_request",
              "invalid_client",
              "invalid_grant",
              "unsupported_grant_type"
            ]
          },
          "error_description": {
            "type": "string"
          }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          },
          "method": {
            "type": "string"
          },
          "params": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": [
          "jsonrpc",
          "id"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          },
          "result": {},
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "integer"
              },
              "message": {
                "type": "string"
              },
              "data": {}
            }
          }
        }
      },
      "CreatePresentationRequest": {
        "type": "object",
        "required": [
          "indexHtml"
        ],
        "properties": {
          "indexHtml": {
            "type": "string",
            "description": "The whole `html-presentation/v1` file."
          },
          "title": {
            "type": "string",
            "description": "Overrides the title parsed from the HTML."
          },
          "slug": {
            "type": "string",
            "description": "URL slug under your channel. Reuse it to republish."
          },
          "presentationId": {
            "type": "string",
            "description": "Republish an existing presentation by id instead of slug."
          },
          "handle": {
            "type": "string",
            "description": "Which of your channels to publish on; defaults to your account's."
          },
          "visibility": {
            "type": "string",
            "enum": [
              "public",
              "unlisted",
              "private",
              "org"
            ],
            "default": "unlisted"
          },
          "org": {
            "type": "string",
            "description": "bisque.team organization slug; publishes into the company space."
          },
          "contextMd": {
            "type": "string",
            "description": "Agent-facing background shipped with the bundle as `context.md`."
          },
          "designMd": {
            "type": "string",
            "description": "Design notes shipped with the bundle."
          },
          "voiceId": {
            "type": "string",
            "description": "Narration voice tag; defaults to the account's saved voice."
          },
          "speechSpeed": {
            "type": "number"
          },
          "voiceStability": {
            "type": "number"
          },
          "assets": {
            "type": "object",
            "additionalProperties": true,
            "description": "Page assets (fonts, images) keyed by bundle path."
          }
        }
      },
      "CreatePresentationResult": {
        "type": "object",
        "required": [
          "requestId",
          "presentationId",
          "webUrl",
          "status",
          "statusUrl"
        ],
        "properties": {
          "requestId": {
            "type": "string"
          },
          "presentationId": {
            "type": "string"
          },
          "webUrl": {
            "type": "string",
            "format": "uri",
            "description": "The watch link, e.g. https://bisque.today/p/{handle}/{slug}."
          },
          "status": {
            "type": "string",
            "enum": [
              "ready",
              "queued"
            ]
          },
          "statusUrl": {
            "type": "string",
            "format": "uri"
          },
          "narration": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "PresentationStatus": {
        "type": "object",
        "required": [
          "requestId",
          "presentationId",
          "publishStatus",
          "ready"
        ],
        "properties": {
          "requestId": {
            "type": "string"
          },
          "presentationId": {
            "type": "string"
          },
          "webUrl": {
            "type": "string",
            "format": "uri"
          },
          "publishStatus": {
            "type": "string"
          },
          "ready": {
            "type": "boolean"
          },
          "visibility": {
            "type": "string"
          },
          "narration": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "PublishNarratedRequest": {
        "type": "object",
        "required": [
          "indexHtml"
        ],
        "description": "`CreatePresentationRequest` plus locally synthesized audio.",
        "allOf": [
          {
            "$ref": "#/components/schemas/CreatePresentationRequest"
          }
        ],
        "properties": {
          "audio": {
            "type": "array",
            "description": "One entry per narrated slide. Omit a slide to carry its last-published audio forward.",
            "items": {
              "type": "object",
              "required": [
                "slideKey",
                "audioBase64",
                "audioWords"
              ],
              "properties": {
                "slideKey": {
                  "type": "string"
                },
                "audioBase64": {
                  "type": "string",
                  "description": "MP3 bytes."
                },
                "contentType": {
                  "type": "string",
                  "const": "audio/mpeg"
                },
                "audioWords": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "description": "Word timings from the synthesizer, in narration order."
                }
              }
            }
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Extra bundle files outside `audio/`."
          }
        }
      },
      "PresentationContext": {
        "type": "object",
        "required": [
          "requestId",
          "presentationId",
          "title",
          "handle",
          "slug",
          "webUrl",
          "visibility",
          "transcript"
        ],
        "properties": {
          "requestId": {
            "type": "string"
          },
          "presentationId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "handle": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "webUrl": {
            "type": "string",
            "format": "uri"
          },
          "visibility": {
            "type": "string"
          },
          "publishedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "transcript": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "slideIndex": {
                  "type": "integer"
                },
                "startSec": {
                  "type": "number"
                },
                "title": {
                  "type": "string"
                },
                "text": {
                  "type": "string",
                  "description": "The slide's narration."
                }
              }
            }
          },
          "transcriptTruncatedSlides": {
            "type": "integer"
          },
          "contextMd": {
            "type": [
              "string",
              "null"
            ]
          },
          "contextTruncated": {
            "type": "boolean"
          }
        }
      }
    }
  }
}