Bisque API API

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.

Operations 41

GET /api/presentations/spec The html-presentation/v1 authoring format #
GET /api/presentations/spec/{part} One module of the authoring format #
GET /api/presentations/context Read a presentation as text #
POST /api/presentations/context Read up to 20 presentations in one request #
POST /api/presentations/context/batch Read up to 20 presentations in one request #
GET /api/oembed oEmbed for a watch URL #
GET /api/presentations Your presentations, newest first, cursor-paginated #
POST /api/presentations/create Publish a presentation from HTML #
GET /api/presentations/status Narration and publish progress #
POST /v1/presentations Create a presentation and put its slides up now #
GET /v1/presentations/{presentationId} Read a live creation to resume it #
PATCH /v1/presentations/{presentationId} Add slides to a live creation #
GET /v1/presentations/{presentationId}/surveys/{surveyId}/response Wait for the owner's answer to a survey #
PATCH /v1/presentations/{presentationId}/slides/{slideKey} Give one slide its narration #
POST /v1/presentations/{presentationId}:finalize Finish the creation #
POST /api/presentations/publish-narrated Publish with audio you synthesized locally (deprecated) #
POST /mcp Presentations MCP server #
POST /ask Ask a question (NLWeb 0.55) #
GET /ask Ask a question (NLWeb 0.55), GET form #
POST /docs/mcp Documentation MCP server #
POST /oauth/register Dynamic client registration (RFC 7591) #
POST /oauth/token Token endpoint (authorization_code + PKCE, refresh_token) #
POST /oauth/revoke Revoke a token (RFC 7009) #
GET /oauth/userinfo OIDC-style userinfo for openid-scoped tokens #
GET /.well-known/oauth-authorization-server RFC 8414 authorization-server metadata, with the auth.md `agent_auth` block #
GET /.well-known/oauth-protected-resource RFC 9728 protected-resource metadata for the MCP server #
GET /.well-known/mcp.json MCP manifest: the presentations server's address, transport, tools, and the widget each tool renders #
GET /.well-known/did.json DID document for did:web:bisque.today — the identity the ARD catalog's trustManifest cites #
GET /.well-known/http-message-signatures-directory Web Bot Auth key directory (Ed25519 JWKs) for requests Bisque's agents sign #
GET /auth.md How an agent obtains, uses, and revokes a credential #
GET /llms.txt What this site is, when to use it, and where the docs are #
GET /api/v1/presentations/spec The html-presentation/v1 authoring format #
GET /api/v1/presentations/spec/{part} One module of the authoring format #
GET /api/v1/presentations/context Read a presentation as text #
POST /api/v1/presentations/context Read up to 20 presentations in one request #
POST /api/v1/presentations/context/batch Read up to 20 presentations in one request #
GET /api/v1/presentations Your presentations, newest first, cursor-paginated #
POST /api/v1/presentations/create Publish a presentation from HTML #
GET /api/v1/presentations/status Narration and publish progress #
POST /api/v1/presentations/publish-narrated Publish with audio you synthesized locally (deprecated) #
GET /api/v1/oembed oEmbed for a watch URL #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/bisque-cloud:bisque-api-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

bisque-cloud-api-openapi.json Raw ↑
{
  "openapi": "3.1.0",
  "info": {
    "title": "Bisque API",
    "version": "1.0.0",
    "x-api-version": "1",
    "x-versioning": {
      "scheme": "path",
      "current": "/api/v1",
      "unversionedAlias": "/api",
      "deprecationHeaders": [
        "Deprecation",
        "Sunset"
      ],
      "minimumNoticeDays": 90,
      "policy": "https://bisque.today/docs/api/versioning"
    },
    "x-lifecycle": {
      "stability": "stable",
      "currentVersion": "1",
      "versionedBy": "path",
      "pathPrefixes": {
        "presentations": "/api/v1/presentations",
        "presentationsUnversionedAlias": "/api/presentations",
        "tools": "/v1"
      },
      "compatibleChanges": [
        "new optional request fields",
        "new response fields",
        "new endpoints",
        "new enum values on fields documented as open"
      ],
      "breakingChanges": {
        "shipsAs": "a new path prefix; the old one keeps serving",
        "minimumNoticeDays": 90,
        "signals": {
          "Deprecation": "RFC 9745 — the date the old path was deprecated",
          "Sunset": "RFC 8594 — the date the old path stops serving",
          "Link": "rel=\"successor-version\" — the new path"
        }
      },
      "deprecated": [],
      "changelog": "https://bisque.today/docs/api/versioning"
    },
    "x-sandbox": {
      "kind": "dry-run",
      "description": "POST /api/presentations/create with `dryRun: true` (or the MCP tool create_presentation with dryRun) runs every check a real publish runs — format, slug and org conflicts, tier cap, credit preflight — against live data and writes nothing. The 200 reply carries status `validated` and the URL a real create would publish at. Reads (/api/presentations/context) need no credential and touch nothing.",
      "request": {
        "path": "/api/presentations/create",
        "field": "dryRun"
      },
      "documentation": "https://bisque.today/docs/api#sandbox"
    },
    "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.\n\n**Retries:** send an `Idempotency-Key` header on every POST. A retry with the same key and body replays the first response (marked `Idempotent-Replayed: true`); the same key with a different body is a 422.\n\n**Rate limits:** responses carry `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` (seconds) and `RateLimit-Policy`; a 429 adds `Retry-After`.\n\n**Versioning:** this is version 1 of the presentations API, addressed by path (`/api/presentations/...`; the agent tool API is `/v1/...`). Fields are only added, never removed or retyped, within a version. A breaking change ships under a new path prefix and the old one keeps serving for at least 90 days, during which its responses carry `Deprecation` and `Sunset` headers (RFC 9745 / RFC 8594) naming the date. `info.x-lifecycle` states the same policy field by field.\n\n**Sandbox:** `POST /api/presentations/create` with `dryRun: true` runs every check a real publish runs and writes nothing — the reply is a 200 with status `validated` and the URL a real create would publish at. Use it to test an integration without touching a channel.\n\n**Batch:** `POST /api/presentations/context` (alias `/api/presentations/context/batch`) reads up to 20 presentations in one request; each item succeeds or fails on its own.",
    "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/claude-system-card"
          },
          {
            "name": "handle",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Channel handle (leading @ tolerated)."
          },
          {
            "name": "slug",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The presentation.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "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`). Wait `Retry-After` seconds.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window frees up."
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Read"
        ],
        "operationId": "getPresentationContextBatch",
        "summary": "Read up to 20 presentations in one request",
        "description": "The batch form of the GET. Send `{\"urls\": [...]}`, `{\"items\": [{\"url\"} | {\"handle\",\"slug\"}, ...]}`, or a bare JSON array of watch URLs; each item resolves on its own, so one bad address fails its row and not the batch. Every item costs one slot of the same per-IP window as a GET (60 per 10 minutes anonymous, 300 with a credential); items past the window come back as `CONTEXT_RATE_LIMITED` rows rather than failing the request.",
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "x-batch": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PresentationContextBatchRequest"
              },
              "examples": {
                "urls": {
                  "value": {
                    "urls": [
                      "https://bisque.today/p/siderakis/claude-system-card",
                      "https://bisque.today/p/siderakis/bun-1-3"
                    ]
                  }
                },
                "items": {
                  "value": {
                    "items": [
                      {
                        "url": "https://bisque.today/p/siderakis/claude-system-card"
                      },
                      {
                        "handle": "siderakis",
                        "slug": "bun-1-3"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One row per item, in request order.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PresentationContextBatchResult"
                }
              }
            }
          },
          "400": {
            "description": "The body was not one of the three accepted shapes, was empty, or named more than 20 presentations.",
            "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"
                }
              }
            }
          },
          "429": {
            "description": "The window had no room for even the first item. Wait `Retry-After` seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/presentations/context/batch": {
      "post": {
        "tags": [
          "Read"
        ],
        "operationId": "getPresentationContextBatchAlias",
        "summary": "Read up to 20 presentations in one request",
        "description": "The batch form of the GET. Send `{\"urls\": [...]}`, `{\"items\": [{\"url\"} | {\"handle\",\"slug\"}, ...]}`, or a bare JSON array of watch URLs; each item resolves on its own, so one bad address fails its row and not the batch. Every item costs one slot of the same per-IP window as a GET (60 per 10 minutes anonymous, 300 with a credential); items past the window come back as `CONTEXT_RATE_LIMITED` rows rather than failing the request.",
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "x-batch": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PresentationContextBatchRequest"
              },
              "examples": {
                "urls": {
                  "value": {
                    "urls": [
                      "https://bisque.today/p/siderakis/claude-system-card",
                      "https://bisque.today/p/siderakis/bun-1-3"
                    ]
                  }
                },
                "items": {
                  "value": {
                    "items": [
                      {
                        "url": "https://bisque.today/p/siderakis/claude-system-card"
                      },
                      {
                        "handle": "siderakis",
                        "slug": "bun-1-3"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One row per item, in request order.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PresentationContextBatchResult"
                }
              }
            }
          },
          "400": {
            "description": "The body was not one of the three accepted shapes, was empty, or named more than 20 presentations.",
            "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"
                }
              }
            }
          },
          "429": {
            "description": "The window had no room for even the first item. Wait `Retry-After` seconds.",
            "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": {
      "get": {
        "tags": [
          "Publish"
        ],
        "operationId": "listPresentations",
        "summary": "Your presentations, newest first, cursor-paginated",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "The `nextCursor` from the previous page. Opaque."
          },
          {
            "name": "visibility",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "public",
                "unlisted",
                "private",
                "org"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PresentationPage"
                }
              }
            }
          },
          "400": {
            "description": "Bad `visibility` or a cursor this endpoint did not issue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/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. With `dryRun: true` the same checks run and nothing is written: the reply is a 200 with status `validated` (the sandbox).",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePresentationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dry run passed (`dryRun: true`): every check succeeded and nothing was published. `webUrl` is where a real create would publish.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatePresentationResult"
                }
              }
            }
          },
          "202": {
            "description": "Accepted; narration queued.",
            "headers": {
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "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"
                }
              }
            }
          }
        }
      }
    },
    "/v1/presentations": {
      "post": {
        "tags": [
          "Publish"
        ],
        "operationId": "createPresentationRun",
        "summary": "Create a presentation and put its slides up now",
        "description": "Start a creation: send the HTML and the assets, and the slides are viewable at the returned `webUrl` straight away — silent until narration arrives. The response names the slides that still need narrating (`needsAudio`) and the ones whose audio carried forward from last time (`reused`), with an upload target for each asset and each slide. Narrate each slide with the `bisque-voice` CLI, PUT the MP3 to its target, then PATCH the slide. While the creation is open it can take more slides (`PATCH /v1/presentations/{presentationId}`): the presentation document carries `live: { revision, startedAt }`, `revision` rises on every append, and the watch page appends the new slides in place. API-key callers only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublishNarratedRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The creation is open and the slides are live.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/presentations/{presentationId}": {
      "get": {
        "tags": [
          "Publish"
        ],
        "operationId": "getLivePresentation",
        "summary": "Read a live creation to resume it",
        "description": "The open creation as it stands, for its owner picking it up from a new agent session: every slide published so far as `indexHtml`, which slides have narration, the voice and speed they were narrated with, the assets it holds, the owner's answer to every survey so far, and `live.revision`. Send that revision back as `baseRevision` on the next append; an append written against an older one is refused with `LIVE_REVISION_CONFLICT`. Reading changes nothing. API-key callers only.",
        "parameters": [
          {
            "name": "presentationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The live creation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Another account opened this creation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not a presentation you can change, or not live: finalized or never opened live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Another publish superseded this creation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Publish"
        ],
        "operationId": "appendPresentationSlides",
        "summary": "Add slides to a live creation",
        "description": "Send the whole HTML again with new sections appended. Existing slides keep their keys and order (`LIVE_APPEND_ONLY` otherwise); unchanged slides carry their audio forward, assets the body leaves out are kept, and the response names the slides that need narrating with an upload target for each, exactly as create does. Add `baseRevision`, the `live.revision` the slides were written against, and an append that fell behind is refused (`LIVE_REVISION_CONFLICT`) rather than overwriting slides it never saw; an append waits up to 20 seconds for another append or narration upload in progress, and is refused with `LIVE_APPEND_IN_PROGRESS` if it is still going. `live.revision` on the presentation rises by one and anyone watching sees the new slides without a reload. API-key callers only.",
        "parameters": [
          {
            "name": "presentationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true

# --- truncated at 32 KB (116 KB total) ---
# Full source: https://artifacts.apis.io/bisque-cloud/openapi/_original/bisque-cloud-api-openapi.json