{
  "openapi": "3.1.0",
  "info": {
    "title": "Ollav API",
    "version": "1.3.0",
    "summary": "Share live HTML prototypes and review them with element-pinned comments.",
    "description": "REST API for Ollav. Use it when a user asks to share, preview, get feedback on or review an HTML file, prototype, mockup or generated page: publish the file (POST /api/documents) and give the user the returned url, read open comments (GET /api/review, format=markdown for a readable summary), publish a new version at the same url (POST /api/documents/{slug}/versions) and resolve what you fixed. Every endpoint takes an API key (Authorization: Bearer ol_live_..., or ol_test_... for test mode: an isolated sandbox whose data is never live, never billed and deleted after 30 days; responses say \"livemode\"); state-changing requests also need X-Ollav-Client: agent. Send an Idempotency-Key on POST, PATCH and DELETE so retries are safe. Errors are typed (https://ollav.app/docs/errors) and every response carries a Request-Id. Breaking changes ship as dated versions (Ollav-Version header; your key is pinned to the version current when it was made: https://ollav.app/docs/changelog). MCP: https://app.ollav.app/mcp. Agent quickstart: https://ollav.app/docs/agents",
    "contact": {
      "name": "Ollav",
      "url": "https://ollav.app/"
    },
    "termsOfService": "https://app.ollav.app/terms"
  },
  "externalDocs": {
    "description": "Agent quickstart",
    "url": "https://ollav.app/docs/agents"
  },
  "servers": [
    {
      "url": "https://app.ollav.app",
      "description": "API origin"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Documents",
      "description": "A document is one self-contained HTML file (up to 15 MB) at a stable URL on the content origin. Uploading a file with the same filename as one you already own adds a version instead of a new document."
    },
    {
      "name": "Versions",
      "description": "Every upload of new content is a version. The URL never changes; comments on earlier versions are kept and marked as such."
    },
    {
      "name": "Comments",
      "description": "Element-anchored comments and single-level replies. Top-level comments carry the CSS selector and bounding rect of the element they pin to."
    },
    {
      "name": "Reactions",
      "description": "Emoji reactions on comments and replies."
    },
    {
      "name": "Review",
      "description": "The agent feedback loop: read open threads shaped for editing source, publish a new version, resolve what you fixed."
    },
    {
      "name": "Exports",
      "description": "Data exports (GDPR Art. 15/20) as JSON or a streamed ZIP."
    },
    {
      "name": "Workspaces",
      "description": "Workspaces own documents. An API key is pinned to one workspace; managing workspaces (create, invite, remove) needs a signed-in browser session."
    },
    {
      "name": "Billing",
      "description": "Read-only plan, usage and limits for a workspace. Plan changes happen in the web UI through Stripe Checkout and the Customer Portal."
    },
    {
      "name": "Account",
      "description": "Who the API key belongs to."
    },
    {
      "name": "API keys",
      "description": "Keys are created and revoked in the web UI (https://app.ollav.app/keys). The API lists them by prefix only; a key's secret is shown once, at creation."
    }
  ],
  "paths": {
    "/api/documents": {
      "get": {
        "operationId": "listDocuments",
        "summary": "List documents",
        "description": "Lists your non-archived documents in the key's workspace, newest first, as a bare array. Use GET /api/dashboard instead when you also need version and comment counts. Guests can't list (403 ROLE_REQUIRED).",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Your documents (a bare array).",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DocumentSummary"
                  }
                },
                "example": [
                  {
                    "id": "9908f969-9774-4374-a5bf-d66abcfbe70b",
                    "slug": "qkjgjQCQGz",
                    "title": "Pricing page v2",
                    "filename": "pricing.html",
                    "visibility": "workspace",
                    "createdAt": 1793107356443,
                    "url": "https://content.ollav.app/p/qkjgjQCQGz"
                  }
                ]
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/documents\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\""
          }
        ]
      },
      "post": {
        "operationId": "shareHtml",
        "x-mcp-tool": "share_html",
        "summary": "Upload (share) an HTML document",
        "description": "Publishes one self-contained HTML file and returns its share url. Give that url to the user. Send JSON `{html, title?, filename?, visibility?}`, multipart/form-data with a `file` field, or a raw text/html body (title, filename and visibility then come from the query string or X-Title/X-Filename/X-Visibility headers). If you already own a document with the same filename and the content changed, this adds a version to it (201, versioned: true); identical content returns the existing version (200, deduped: true). To update a known document prefer POST /api/documents/{slug}/versions. `warnings` lists relative asset paths that won't load; it never blocks the upload. New documents expire after 90 days unless extended or made permanent. Publishing (this and new versions together) is limited to 30 an hour per account (looser per IP): over it, 429 RATE_LIMITED with error.retry_after in seconds. A multipart upload takes one file (400 TOO_MANY_FILES).",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadRequest"
              },
              "example": {
                "html": "<!DOCTYPE html><html><head><title>Pricing page v2</title></head><body><h1>Pricing</h1></body></html>",
                "filename": "pricing.html",
                "visibility": "workspace"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The .html file."
                  },
                  "title": {
                    "type": "string"
                  },
                  "visibility": {
                    "$ref": "#/components/schemas/Visibility"
                  }
                }
              },
              "example": {
                "file": "pricing.html",
                "visibility": "workspace"
              }
            },
            "text/html": {
              "schema": {
                "type": "string",
                "description": "The HTML itself."
              },
              "example": "<!DOCTYPE html><html><body><h1>Pricing</h1></body></html>"
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deduplicated. You already uploaded exactly this content; nothing changed.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadResult"
                },
                "example": {
                  "url": "https://content.ollav.app/p/qkjgjQCQGz",
                  "slug": "qkjgjQCQGz",
                  "documentId": "9908f969-9774-4374-a5bf-d66abcfbe70b",
                  "versionId": "b8cfa354-0c7c-4102-bf14-69b49de24ca3",
                  "hash": "859d1148aaf2854ea2a59a25b89063395b0ded22230bdb4c7aad72d2a83b537a",
                  "deduped": true,
                  "versioned": false,
                  "warnings": [],
                  "livemode": true
                }
              }
            }
          },
          "201": {
            "description": "A new document, or a new version of the document with the same filename.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadResult"
                },
                "example": {
                  "url": "https://content.ollav.app/p/qkjgjQCQGz",
                  "slug": "qkjgjQCQGz",
                  "documentId": "9908f969-9774-4374-a5bf-d66abcfbe70b",
                  "versionId": "b8cfa354-0c7c-4102-bf14-69b49de24ca3",
                  "hash": "859d1148aaf2854ea2a59a25b89063395b0ded22230bdb4c7aad72d2a83b537a",
                  "deduped": false,
                  "versioned": false,
                  "visibility": "workspace",
                  "warnings": [],
                  "livemode": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://app.ollav.app/api/documents\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"html\":\"<!DOCTYPE html><html><head><title>Pricing page v2</title></head><body><h1>Pricing</h1></body></html>\",\"filename\":\"pricing.html\",\"visibility\":\"workspace\"}'"
          }
        ]
      }
    },
    "/api/documents/{slug}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "get": {
        "operationId": "getDocument",
        "x-mcp-tool": "get_document",
        "summary": "Get a document",
        "description": "Returns one document's metadata with every stored version (newest first). For the document's owner or a workspace admin; anyone else gets the same 404 as for a missing slug. Archived documents are returned too (archivedAt is set). Versions older than the plan's retention window are hidden, not deleted.",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "The document with its versions.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentDetail"
                },
                "example": {
                  "id": "9908f969-9774-4374-a5bf-d66abcfbe70b",
                  "workspaceId": "0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90",
                  "slug": "qkjgjQCQGz",
                  "title": "Pricing page v2",
                  "filename": "pricing.html",
                  "ownerId": "e970d2fd-66e4-4fa0-bac2-144f81995e32",
                  "ownerEmail": "ada@example.com",
                  "visibility": "workspace",
                  "currentVersionId": "07120538-9e66-460f-9f26-22f3910a213a",
                  "createdAt": 1793107356443,
                  "updatedAt": 1793107356781,
                  "archivedAt": null,
                  "expiresAt": 1800883356443,
                  "isPermanent": 0,
                  "expiryWarnedAt": null,
                  "url": "https://content.ollav.app/p/qkjgjQCQGz",
                  "versions": [
                    {
                      "id": "07120538-9e66-460f-9f26-22f3910a213a",
                      "workspaceId": "0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90",
                      "documentId": "9908f969-9774-4374-a5bf-d66abcfbe70b",
                      "hash": "97602aa74d0b26c2fcc8494d28a82c13430712aeacc549f67b7ce3c1dcf74221",
                      "r2Key": "docs/97602aa74d0b26c2fcc8494d28a82c13430712aeacc549f67b7ce3c1dcf74221",
                      "size": 53,
                      "createdAt": 1793107356781
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/documents/qkjgjQCQGz\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\""
          }
        ]
      },
      "patch": {
        "operationId": "updateDocumentVisibility",
        "summary": "Change who can view a document",
        "description": "Sets the document's visibility. Only use `public` when the user explicitly asked to share outside their company: anyone with the URL can then view it without signing in. Commenting stays members-only whatever the visibility. Owner or workspace admin; needs the upload scope.",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "visibility"
                ],
                "properties": {
                  "visibility": {
                    "$ref": "#/components/schemas/Visibility"
                  }
                }
              },
              "example": {
                "visibility": "link"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "visibility"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "visibility": {
                      "$ref": "#/components/schemas/Visibility"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "visibility": "link"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X PATCH \"https://app.ollav.app/api/documents/qkjgjQCQGz\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"visibility\":\"link\"}'"
          }
        ]
      },
      "delete": {
        "operationId": "deleteDocument",
        "summary": "Delete a document (session only)",
        "description": "Permanently deletes the document, its versions, comments, reactions and images, and erases stored files nothing else references. Needs a signed-in browser session and the document's title or slug typed in `confirm`; an API key gets 403 FORBIDDEN. Agents: archive instead (POST /api/documents/{slug}/archive), or ask the user to delete it in the web UI.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "confirm"
                ],
                "properties": {
                  "confirm": {
                    "type": "string",
                    "description": "The document's title or slug."
                  }
                }
              },
              "example": {
                "confirm": "qkjgjQCQGz"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deleted.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deleted",
                    "r2"
                  ],
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    },
                    "r2": {
                      "type": "object",
                      "required": [
                        "deleted",
                        "pending"
                      ],
                      "description": "Stored files erased now, and queued for the nightly retry.",
                      "properties": {
                        "deleted": {
                          "type": "integer"
                        },
                        "pending": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "deleted": true,
                  "r2": {
                    "deleted": 3,
                    "pending": 0
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X DELETE \"https://app.ollav.app/api/documents/qkjgjQCQGz\" \\\n  --cookie \"ollav_session=$OLLAV_SESSION\" \\\n  -H \"X-Ollav-Client: agent\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"confirm\":\"qkjgjQCQGz\"}'"
          }
        ]
      }
    },
    "/api/documents/{slug}/archive": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "post": {
        "operationId": "archiveDocument",
        "summary": "Archive a document",
        "description": "Takes the document offline: its URL answers 410 and it leaves listings. Nothing is deleted; undo with POST /api/documents/{slug}/unarchive. Owner or workspace admin; needs the upload scope.",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Archived.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArchiveResult"
                },
                "example": {
                  "ok": true,
                  "archived": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://app.ollav.app/api/documents/qkjgjQCQGz/archive\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\""
          }
        ]
      }
    },
    "/api/documents/{slug}/unarchive": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "post": {
        "operationId": "unarchiveDocument",
        "summary": "Unarchive a document",
        "description": "Brings an archived document back online at the same URL. Counts against the plan's document limit again, so it can answer 402.",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Restored.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArchiveResult"
                },
                "example": {
                  "ok": true,
                  "archived": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://app.ollav.app/api/documents/qkjgjQCQGz/unarchive\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\""
          }
        ]
      }
    },
    "/api/documents/{slug}/expiry": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "post": {
        "operationId": "setDocumentExpiry",
        "summary": "Extend, set or remove a document's expiry",
        "description": "Documents are archived automatically when they expire (90 days after upload by default). `extend` sets expiry to 90 days from now, `permanent` removes it, `custom` sets `expiresAt` (Unix ms, in the future). Test-mode documents are deleted 30 days after upload: for them `extend` sets that date, and `permanent` or a later `custom` date is a 400. Owner or workspace admin; needs the upload scope.",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action"
                ],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "extend",
                      "permanent",
                      "custom"
                    ]
                  },
                  "expiresAt": {
                    "type": "integer",
                    "description": "Unix ms; required when action is custom."
                  }
                }
              },
              "example": {
                "action": "extend"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new expiry.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "isPermanent",
                    "expiresAt"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "isPermanent": {
                      "type": "boolean"
                    },
                    "expiresAt": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "Unix ms; null when permanent."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "isPermanent": false,
                  "expiresAt": 1800883357891
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://app.ollav.app/api/documents/qkjgjQCQGz/expiry\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"action\":\"extend\"}'"
          }
        ]
      }
    },
    "/api/documents/{slug}/export": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "get": {
        "operationId": "exportDocument",
        "summary": "Export one document as JSON",
        "description": "One JSON bundle: metadata, every version with its HTML inlined, and every comment. For everything in an account or workspace (with images and snapshots) use POST /api/exports. Owner or workspace admin; needs the read scope.",
        "tags": [
          "Exports"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "The bundle.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentExport"
                },
                "example": {
                  "exportedAt": "2026-10-27T13:22:38.141Z",
                  "document": {
                    "id": "9908f969-9774-4374-a5bf-d66abcfbe70b",
                    "slug": "qkjgjQCQGz",
                    "title": "Pricing page v2",
                    "filename": "pricing.html",
                    "visibility": "workspace",
                    "createdAt": "2026-10-27T13:22:36.443Z",
                    "updatedAt": "2026-10-27T13:22:37.969Z",
                    "archivedAt": null,
                    "expiresAt": null,
                    "isPermanent": true,
                    "url": "https://content.ollav.app/p/qkjgjQCQGz"
                  },
                  "versions": [
                    {
                      "id": "f2d1c6e2-6960-488b-a198-6d4f33ab1841",
                      "hash": "c0fb1a4309519139808296b940c600f23fa26a67fd1732f7610528fd95298909",
                      "r2Key": "docs/c0fb1a4309519139808296b940c600f23fa26a67fd1732f7610528fd95298909",
                      "size": 35,
                      "createdAt": "2026-10-27T13:22:37.459Z",
                      "isCurrent": true,
                      "html": "<html><body><p>v3</p></body></html>"
                    }
                  ],
                  "comments": [
                    {
                      "id": "e7cb98b7-c5e3-4048-8590-e7c1b1dea787",
                      "parentId": null,
                      "selector": "h1",
                      "elementText": "Pricing",
                      "authorId": "e970d2fd-66e4-4fa0-bac2-144f81995e32",
                      "body": "Make the headline blue",
                      "createdAt": "2026-10-27T13:22:38.297Z",
                      "resolvedAt": null,
                      "resolvedBy": null,
                      "versionId": "f2d1c6e2-6960-488b-a198-6d4f33ab1841"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/documents/qkjgjQCQGz/export\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\""
          }
        ]
      }
    },
    "/api/dashboard": {
      "get": {
        "operationId": "listDocumentsWithCounts",
        "x-mcp-tool": "list_documents",
        "summary": "List documents with version and comment counts",
        "description": "Your documents in the key's workspace, newest first, with version, comment and unresolved-comment counts: the quickest way to find which document has open feedback. Includes archived documents (archivedAt set). `sharedWithMe` is filled for browser sessions only.",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "The key's workspace and its documents.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Dashboard"
                },
                "example": {
                  "workspace": {
                    "id": "0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90",
                    "name": "Acme",
                    "role": "owner"
                  },
                  "documents": [
                    {
                      "id": "9908f969-9774-4374-a5bf-d66abcfbe70b",
                      "slug": "qkjgjQCQGz",
                      "title": "Pricing page v2",
                      "filename": "pricing.html",
                      "visibility": "workspace",
                      "createdAt": 1793107356443,
                      "updatedAt": 1793107357969,
                      "archivedAt": null,
                      "expiresAt": 1800883356443,
                      "isPermanent": 0,
                      "versionCount": 3,
                      "commentCount": 2,
                      "unresolvedCount": 1,
                      "url": "https://content.ollav.app/p/qkjgjQCQGz"
                    }
                  ],
                  "sharedWithMe": []
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/dashboard\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\""
          }
        ]
      }
    },
    "/api/documents/{slug}/versions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "get": {
        "operationId": "listVersions",
        "summary": "List a document's versions",
        "description": "Every visible version, newest first, numbered from 1 (the first upload), with which one is current. Versions older than the plan's retention window are hidden, not deleted. Owner or workspace admin; needs the read scope.",
        "tags": [
          "Versions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "The versions.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionList"
                },
                "example": {
                  "document": {
                    "slug": "qkjgjQCQGz",
                    "title": "Pricing page v2",
                    "versionCount": 2
                  },
                  "versions": [
                    {
                      "id": "07120538-9e66-460f-9f26-22f3910a213a",
                      "hash": "97602aa74d0b26c2fcc8494d28a82c13430712aeacc549f67b7ce3c1dcf74221",
                      "size": 53,
                      "createdAt": "2026-10-27T13:22:36.781Z",
                      "isCurrent": true,
                      "versionNumber": 2
                    },
                    {
                      "id": "b8cfa354-0c7c-4102-bf14-69b49de24ca3",
                      "hash": "859d1148aaf2854ea2a59a25b89063395b0ded22230bdb4c7aad72d2a83b537a",
                      "size": 99,
                      "createdAt": "2026-10-27T13:22:36.443Z",
                      "isCurrent": false,
                      "versionNumber": 1
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/documents/qkjgjQCQGz/versions\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\""
          }
        ]
      },
      "post": {
        "operationId": "publishNewVersion",
        "x-mcp-tool": "publish_new_version",
        "summary": "Publish a new version",
        "description": "Replaces what the document's URL shows with new HTML; the URL stays the same and earlier comments are kept, marked \"earlier version\". Use this after editing in response to feedback, then resolve the threads you fixed. Same body formats as POST /api/documents. Identical content to the current version returns 200 with deduped: true. Needs the upload scope; archived documents answer 404 ARCHIVED, documents taken down for breaking the Terms 410 DOCUMENT_REMOVED. Counts towards the 30 publishes an hour per account (429 RATE_LIMITED).",
        "tags": [
          "Versions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "html"
                ],
                "properties": {
                  "html": {
                    "type": "string",
                    "description": "The full new HTML."
                  },
                  "title": {
                    "type": "string",
                    "description": "Overrides the <title> tag."
                  }
                }
              },
              "example": {
                "html": "<!DOCTYPE html><html><head><title>Pricing page v3</title></head><body><h1 style='color:blue'>Pricing</h1></body></html>"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "title": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "file": "pricing.html"
              }
            },
            "text/html": {
              "schema": {
                "type": "string"
              },
              "example": "<!DOCTYPE html><html><body><h1>Pricing</h1></body></html>"
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deduplicated. Same content as the current version; nothing changed.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NewVersionResult"
                },
                "example": {
                  "deduped": true,
                  "slug": "qkjgjQCQGz",
                  "url": "https://content.ollav.app/p/qkjgjQCQGz",
                  "versionId": "f2d1c6e2-6960-488b-a198-6d4f33ab1841",
                  "hash": "c0fb1a4309519139808296b940c600f23fa26a67fd1732f7610528fd95298909",
                  "warnings": [],
                  "livemode": true
                }
              }
            }
          },
          "201": {
            "description": "New version published.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NewVersionResult"
                },
                "example": {
                  "deduped": false,
                  "slug": "qkjgjQCQGz",
                  "url": "https://content.ollav.app/p/qkjgjQCQGz",
                  "documentId": "9908f969-9774-4374-a5bf-d66abcfbe70b",
                  "versionId": "f2d1c6e2-6960-488b-a198-6d4f33ab1841",
                  "hash": "c0fb1a4309519139808296b940c600f23fa26a67fd1732f7610528fd95298909",
                  "warnings": [],
                  "livemode": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "410": {
            "$ref": "#/components/responses/Gone"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://app.ollav.app/api/documents/qkjgjQCQGz/versions\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"html\":\"<!DOCTYPE html><html><head><title>Pricing page v3</title></head><body><h1 style='\\''color:blue'\\''>Pricing</h1></body></html>\"}'"
          }
        ]
      }
    },
    "/api/comments": {
      "get": {
        "operationId": "getComments",
        "x-mcp-tool": "get_comments",
        "summary": "List a document's comments",
        "description": "Every comment thread on the document, resolved ones included, with replies, reactions and image attachments. For acting on feedback prefer GET /api/review, which returns open threads shaped for finding the element in your source. Needs the read scope.",
        "tags": [
          "Comments"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SlugQuery"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Threads, oldest first.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommentList"
                },
                "example": {
                  "slug": "qkjgjQCQGz",
                  "currentVersionId": "f2d1c6e2-6960-488b-a198-6d4f33ab1841",
                  "docOwnerId": "e970d2fd-66e4-4fa0-bac2-144f81995e32",
                  "viewerCanManage": true,
                  "count": 1,
                  "unresolvedCount": 1,
                  "comments": [
                    {
                      "id": "e7cb98b7-c5e3-4048-8590-e7c1b1dea787",
                      "documentId": "9908f969-9774-4374-a5bf-d66abcfbe70b",
                      "versionId": "f2d1c6e2-6960-488b-a198-6d4f33ab1841",
                      "parentId": null,
                      "selector": "h1",
                      "fallbackRect": {
                        "x": 24,
                        "y": 80,
                        "w": 640,
                        "h": 48
                      },
                      "elementText": "Pricing",
                      "authorId": "e970d2fd-66e4-4fa0-bac2-144f81995e32",
                      "body": "Make the headline blue",
                      "createdAt": "2026-10-27T13:22:38.297Z",
                      "resolvedAt": null,
                      "resolvedBy": null,
                      "author": {
                        "email": "ada@example.com",
                        "name": "Ada"
                      },
                      "isCurrentVersion": true,
                      "reproduceNote": null,
                      "breadcrumbs": null,
                      "markEmoji": null,
                      "attachment": null,
                      "snapshotUrl": null,
                      "reactions": [
                        {
                          "reaction": "👍",
                          "count": 1,
                          "mine": false
                        }
                      ],
                      "replies": []
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/comments?slug=qkjgjQCQGz\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\""
          }
        ]
      },
      "post": {
        "operationId": "addComment",
        "x-mcp-tool": "add_comment",
        "summary": "Post a comment or reply",
        "description": "A top-level comment pins to an element: send `selector` (CSS) and `rect` (its bounding box, page coordinates). A reply sends `parentId` (a top-level comment's id) and no selector or rect; replies are one level deep. `markEmoji` without `body` places an emoji mark as a reaction to the element. Members only, whatever the document's visibility; needs the comment scope. Limited to 120 comments and replies an hour per account (looser per IP): over it, 429 RATE_LIMITED with error.retry_after in seconds.",
        "tags": [
          "Comments"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CommentRequest"
              },
              "examples": {
                "topLevel": {
                  "summary": "A comment pinned to an element",
                  "value": {
                    "slug": "qkjgjQCQGz",
                    "body": "Make the headline blue",
                    "selector": "h1",
                    "rect": {
                      "x": 24,
                      "y": 80,
                      "w": 640,
                      "h": 48
                    },
                    "elementText": "Pricing"
                  }
                },
                "reply": {
                  "summary": "A reply",
                  "value": {
                    "slug": "qkjgjQCQGz",
                    "parentId": "e7cb98b7-c5e3-4048-8590-e7c1b1dea787",
                    "body": "Done in v3."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedComment"
                },
                "example": {
                  "id": "e7cb98b7-c5e3-4048-8590-e7c1b1dea787",
                  "workspaceId": "0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90",
                  "documentId": "9908f969-9774-4374-a5bf-d66abcfbe70b",
                  "versionId": "f2d1c6e2-6960-488b-a198-6d4f33ab1841",
                  "parentId": null,
                  "selector": "h1",
                  "fallbackRect": {
                    "x": 24,
                    "y": 80,
                    "w": 640,
                    "h": 48
                  },
                  "elementText": "Pricing",
                  "authorId": "e970d2fd-66e4-4fa0-bac2-144f81995e32",
                  "body": "Make the headline blue",
                  "createdAt": "2026-10-27T13:22:38.297Z",
                  "resolvedAt": null,
                  "resolvedBy": null,
                  "reproduceNote": null,
                  "breadcrumbs": null,
                  "markEmoji": null,
                  "author": {
                    "email": "ada@example.com",
                    "name": "Ada"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://app.ollav.app/api/comments\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"slug\":\"qkjgjQCQGz\",\"body\":\"Make the headline blue\",\"selector\":\"h1\",\"rect\":{\"x\":24,\"y\":80,\"w\":640,\"h\":48},\"elementText\":\"Pricing\"}'"
          }
        ]
      }
    },
    "/api/comments/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CommentId"
        }
      ],
      "delete": {
        "operationId": "deleteComment",
        "x-mcp-tool": "delete_comment",
        "summary": "Delete a comment",
        "description": "Deletes a comment; deleting a top-level comment deletes its replies. Only the comment's author or the document's owner. Anyone else, and unknown ids, get the same 404. Needs the comment scope.",
        "tags": [
          "Comments"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deleted"
                  ],
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                },
                "example": {
                  "deleted": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X DELETE \"https://app.ollav.app/api/comments/e7cb98b7-c5e3-4048-8590-e7c1b1dea787\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\""
          }
        ]
      }
    },
    "/api/comments/{id}/resolve": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CommentId"
        }
      ],
      "post": {
        "operationId": "resolveComment",
        "x-mcp-tool": "resolve_comment",
        "summary": "Resolve a comment thread",
        "description": "Marks a top-level comment resolved. Do this after publishing a version that addresses it, and reply saying what changed. The comment's author or the document's owner; needs the comment scope.",
        "tags": [
          "Comments"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Resolved.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResolveResult"
                },
                "example": {
                  "ok": true,
                  "resolved": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://app.ollav.app/api/comments/e7cb98b7-c5e3-4048-8590-e7c1b1dea787/resolve\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\""
          }
        ]
      }
    },
    "/api/comments/{id}/unresolve": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CommentId"
        }
      ],
      "post": {
        "operationId": "unresolveComment",
        "x-mcp-tool": "resolve_comment",
        "summary": "Reopen a comment thread",
        "description": "Reopens a resolved top-level comment. Same rules as resolving (MCP: resolve_comment with resolved: false).",
        "tags": [
          "Comments"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Reopened.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResolveResult"
                },
                "example": {
                  "ok": true,
                  "resolved": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://app.ollav.app/api/comments/e7cb98b7-c5e3-4048-8590-e7c1b1dea787/unresolve\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\""
          }
        ]
      }
    },
    "/api/comments/{id}/attachments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CommentId"
        }
      ],
      "post": {
        "operationId": "attachImage",
        "summary": "Attach an image to a comment",
        "description": "Attaches ONE image (a screenshot) to a top-level comment you wrote, or on a document you own. multipart/form-data with a `file` field, or the raw image as the body. PNG, JPEG, WebP or GIF, checked by content (SVG is refused), up to 5 MB. Served from the content origin to those who can view the document. Needs the comment scope.",
        "tags": [
          "Comments"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "width": {
                    "type": "integer",
                    "description": "Display width in px (optional)."
                  },
                  "height": {
                    "type": "integer",
                    "description": "Display height in px (optional)."
                  }
                }
              },
              "example": {
                "file": "screenshot.png"
              }
            },
            "image/png": {
              "schema": {
                "type": "string",
                "format": "binary"
              },
              "example": "<binary PNG>"
            }
          }
        },
        "responses": {
          "201": {
            "description": "Attached.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttachmentResult"
                },
                "example": {
                  "id": "b8993718-40ff-49bc-aed5-cf0040a4105e",
                  "url": "https://content.ollav.app/a/b8993718-40ff-49bc-aed5-cf0040a4105e?vt=eyJ...",
                  "mime": "image/png",
                  "width": 1280,
                  "height": 720,
                  "size": 48213
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://app.ollav.app/api/comments/e7cb98b7-c5e3-4048-8590-e7c1b1dea787/attachments\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\" \\\n  -F file=@screenshot.png"
          }
        ]
      }
    },
    "/api/comments/{id}/reactions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CommentId"
        }
      ],
      "post": {
        "operationId": "reactToComment",
        "x-mcp-tool": "react_to_comment",
        "summary": "Add or remove a reaction",
        "description": "Adds (on: true, the default) or removes (on: false) your reaction on a comment or reply. Any single emoji; the legacy names +1, eyes, check and heart still work. A comment holds at most 20 different reactions. Anyone who may comment on the document; needs the comment scope.",
        "tags": [
          "Reactions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reaction"
                ],
                "properties": {
                  "reaction": {
                    "type": "string",
                    "description": "One emoji, e.g. 👍."
                  },
                  "on": {
                    "type": "boolean",
                    "default": true
                  }
                }
              },
              "example": {
                "reaction": "👍"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The comment's reactions after the change.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReactionResult"
                },
                "example": {
                  "ok": true,
                  "commentId": "e7cb98b7-c5e3-4048-8590-e7c1b1dea787",
                  "reactions": [
                    {
                      "reaction": "👍",
                      "count": 1,
                      "mine": true
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://app.ollav.app/api/comments/e7cb98b7-c5e3-4048-8590-e7c1b1dea787/reactions\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"reaction\":\"👍\"}'"
          }
        ]
      }
    },
    "/api/review": {
      "get": {
        "operationId": "getReview",
        "x-mcp-tool": "get_review",
        "summary": "Read feedback to act on",
        "description": "The feedback digest for an iterating agent: open threads by default, each with a source-findable anchor (quote = the element's text, reproduceNote, breadcrumbs). `selector` is the rendered DOM, not your source; search your source for `quote`. Pass the response's `cursor` back as `since` to get only threads with new activity. format=markdown returns the same digest as text/markdown. Loop: get_review → edit → publish_new_version → resolve_comment. Needs the read scope.",
        "tags": [
          "Review"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SlugQuery"
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "json (default) or markdown (text/markdown, the same threads as a readable summary).",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "markdown"
              ],
              "default": "json"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Which threads to return.",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "resolved",
                "all"
              ],
              "default": "open"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "ISO 8601 or epoch ms. Only threads with activity after this; pass the previous response's cursor.",
            "schema": {
              "type": "string"
            },
            "example": "2026-10-27T13:22:38.402Z"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "The digest.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReviewDigest"
                },
                "example": {
                  "slug": "qkjgjQCQGz",
                  "title": "Pricing page v2",
                  "url": "https://content.ollav.app/p/qkjgjQCQGz",
                  "currentVersionId": "f2d1c6e2-6960-488b-a198-6d4f33ab1841",
                  "filter": {
                    "status": "open",
                    "since": null
                  },
                  "totalThreads": 1,
                  "openThreads": 1,
                  "returned": 1,
                  "cursor": "2026-10-27T13:22:38.402Z",
                  "generatedAt": "2026-10-27T13:22:38.836Z",
                  "note": "anchor.selector targets the rendered DOM; to locate the element in your SOURCE (JSX/HTML) prefer anchor.quote and anchor.reproduceNote.",
                  "threads": [
                    {
                      "id": "e7cb98b7-c5e3-4048-8590-e7c1b1dea787",
                      "status": "open",
                      "onCurrentVersion": true,
                      "versionId": "f2d1c6e2-6960-488b-a198-6d4f33ab1841",
                      "author": {
                        "email": "ada@example.com",
                        "name": "Ada"
                      },
                      "createdAt": "2026-10-27T13:22:38.297Z",
                      "lastActivityAt": "2026-10-27T13:22:38.402Z",
                      "comment": "Make the headline blue",
                      "markEmoji": null,
                      "attachment": null,
                      "anchor": {
                        "quote": "Pricing",
                        "reproduceNote": null,
                        "breadcrumbs": null,
                        "selector": "h1"
                      },
                      "replies": []
                    }
                  ],
                  "markdown": "# Review: Pricing page v2\n\n..."
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                },
                "example": "# Review: Pricing page v2\n\n- Link: https://content.ollav.app/p/qkjgjQCQGz\n..."
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/review?slug=qkjgjQCQGz\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\""
          }
        ]
      }
    },
    "/api/exports": {
      "post": {
        "operationId": "exportData",
        "x-mcp-tool": "export_data",
        "summary": "Export data as a ZIP",
        "description": "Returns a signed download link, valid 15 minutes, to a ZIP streamed on demand. scope `account`: your profile, memberships, owned documents with every version, your comments and reactions (an API key: its own workspace only, partial: true). scope `workspace`: everything in the workspace (owner only). manifest.json in the ZIP lists every item. Needs the read scope; limited to a few exports per hour (429).",
        "tags": [
          "Exports"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scope": {
                    "type": "string",
                    "enum": [
                      "account",
                      "workspace"
                    ],
                    "default": "account"
                  },
                  "workspaceId": {
                    "type": "string",
                    "description": "Default: the key's workspace."
                  }
                }
              },
              "example": {
                "scope": "workspace"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The download link.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportLink"
                },
                "example": {
                  "scope": "workspace",
                  "workspaceId": "0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90",
                  "downloadUrl": "https://app.ollav.app/api/exports/download?token=eyJ...",
                  "expiresAt": 1793108258236,
                  "partial": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://app.ollav.app/api/exports\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"scope\":\"workspace\"}'"
          }
        ]
      }
    },
    "/api/exports/download": {
      "get": {
        "operationId": "downloadExport",
        "summary": "Download an export",
        "description": "The `downloadUrl` returned by POST /api/exports. The signed token is the credential (no API key needed); access is re-checked when the download starts. Streams application/zip. Expired or invalid tokens get 404: request a new export.",
        "tags": [
          "Exports"
        ],
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "description": "The signed token from downloadUrl.",
            "schema": {
              "type": "string"
            },
            "example": "eyJ1aWQiOiJlOTcw..."
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "The ZIP (Content-Disposition attachment).",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                },
                "example": "<binary ZIP>"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/exports/download?token=eyJ1aWQiOiJlOTcw...\""
          }
        ]
      }
    },
    "/api/workspaces": {
      "get": {
        "operationId": "listWorkspaces",
        "summary": "List workspaces",
        "description": "The workspaces the caller belongs to (an API key: its user's memberships) and which is current; for an API key the current one is always the key's own. `billingNotice` is set when the current workspace's payment failed or it is over its plan's limits.",
        "tags": [
          "Workspaces"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Workspaces.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceList"
                },
                "example": {
                  "currentId": "0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90",
                  "workspaces": [
                    {
                      "id": "0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90",
                      "name": "Acme",
                      "personal": false,
                      "role": "owner"
                    }
                  ],
                  "billingNotice": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/workspaces\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\""
          }
        ]
      }
    },
    "/api/workspaces/{id}/members": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WorkspaceId"
        }
      ],
      "get": {
        "operationId": "listWorkspaceMembers",
        "summary": "List a workspace's members",
        "description": "Members with their roles (owner > admin > member > guest) and the pending invites the caller may see. Guests and non-members get 404. Inviting and removing members needs the web UI.",
        "tags": [
          "Workspaces"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Members and pending invites.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberList"
                },
                "example": {
                  "workspaceId": "0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90",
                  "role": "owner",
                  "members": [
                    {
                      "userId": "e970d2fd-66e4-4fa0-bac2-144f81995e32",
                      "email": "ada@example.com",
                      "name": "Ada",
                      "role": "owner",
                      "joinedAt": 1793107356443
                    }
                  ],
                  "invites": []
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/workspaces/0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90/members\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\""
          }
        ]
      }
    },
    "/api/workspaces/{id}/billing": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WorkspaceId"
        }
      ],
      "get": {
        "operationId": "getWorkspaceBilling",
        "summary": "Get a workspace's plan, usage and limits",
        "description": "The workspace's plan, subscription, usage and limits (null = no limit), and which limits it is over. A workspace over a limit is read-only: reads work, writes that grow it answer 402. When billing isn't configured, `enabled` is false and there are no limits. Plan changes happen in the web UI (https://app.ollav.app/pricing). Members of the workspace; needs the read scope.",
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Billing overview.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceBilling"
                },
                "example": {
                  "workspaceId": "0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90",
                  "enabled": true,
                  "role": "owner",
                  "canManage": true,
                  "plan": "free",
                  "subscribedPlan": "free",
                  "inGrace": false,
                  "graceEndsAt": null,
                  "lapsed": false,
                  "subscription": null,
                  "limits": {
                    "documents": 3,
                    "members": 1,
                    "storageBytes": 52428800,
                    "versionRetentionDays": 30,
                    "apiCallsPerDay": 100
                  },
                  "usage": {
                    "documents": 1,
                    "members": 1,
                    "storageBytes": 257,
                    "apiCallsToday": 12
                  },
                  "overLimits": [],
                  "readOnly": false,
                  "pricingUrl": "https://app.ollav.app/pricing"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/workspaces/0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90/billing\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\""
          }
        ]
      }
    },
    "/api/me": {
      "get": {
        "operationId": "getMe",
        "summary": "Who am I",
        "description": "The user the API key belongs to and the workspace it acts in. A cheap way to check that a key works.",
        "tags": [
          "Account"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "The caller.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                },
                "example": {
                  "user": {
                    "id": "e970d2fd-66e4-4fa0-bac2-144f81995e32",
                    "email": "ada@example.com",
                    "name": "Ada"
                  },
                  "workspace": {
                    "id": "0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90",
                    "name": "Acme",
                    "role": "owner"
                  },
                  "livemode": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/me\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\""
          }
        ]
      }
    },
    "/api/keys": {
      "get": {
        "operationId": "listApiKeys",
        "summary": "List API keys",
        "description": "The caller's API keys in the current workspace, by prefix only (a key is shown in full once, when created). Create and revoke keys at https://app.ollav.app/keys.",
        "tags": [
          "API keys"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Keys, newest first.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyList"
                },
                "example": {
                  "keys": [
                    {
                      "id": "00d97554-4eb5-4d98-b0da-b88d78d6f88e",
                      "keyPrefix": "ol_live_mSos",
                      "name": "Claude Code",
                      "scopes": [
                        "upload",
                        "read",
                        "comment"
                      ],
                      "createdAt": 1793107356443,
                      "lastUsedAt": 1793107360365,
                      "revokedAt": null,
                      "apiVersion": "2026-11-01",
                      "mode": "live"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://app.ollav.app/api/keys\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\""
          }
        ]
      }
    },
    "/api/keys/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The key's id, from GET /api/keys.",
          "schema": {
            "type": "string"
          },
          "example": "00d97554-4eb5-4d98-b0da-b88d78d6f88e"
        }
      ],
      "delete": {
        "operationId": "revokeApiKey",
        "summary": "Revoke an API key",
        "description": "Revokes one of the caller's API keys in the current workspace, at once and for good; requests with it then answer 401. A key can revoke itself, e.g. when an agent is done with it. Unknown ids and other people's keys get the same 404. Creating keys needs the web UI (https://app.ollav.app/keys).",
        "tags": [
          "API keys"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/OllavVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Ollav-Version": {
                "$ref": "#/components/headers/OllavVersion"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X DELETE \"https://app.ollav.app/api/keys/00d97554-4eb5-4d98-b0da-b88d78d6f88e\" \\\n  -H \"Authorization: Bearer $OLLAV_API_KEY\" \\\n  -H \"X-Ollav-Client: agent\""
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ol_live_<random> or ol_test_<random>",
        "description": "An API key from https://app.ollav.app/keys, pinned to one workspace, with scopes upload, read and comment. ol_live_ keys work on live data; ol_test_ keys work in the key owner's test-mode sandbox of that workspace and can't reach live data (nor live keys test data). Keys made before test mode start sk_live_ and are live. MCP clients can instead get a live key through OAuth 2.1 (https://app.ollav.app/.well-known/oauth-authorization-server)."
      },
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "ollav_session",
        "description": "The web UI's signed-in session. Only for the few destructive operations an API key may not perform."
      }
    },
    "parameters": {
      "Slug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "description": "The document's slug, from the upload response or GET /api/dashboard.",
        "schema": {
          "type": "string"
        },
        "example": "qkjgjQCQGz"
      },
      "SlugQuery": {
        "name": "slug",
        "in": "query",
        "required": true,
        "description": "The document's slug, from the upload response or GET /api/dashboard.",
        "schema": {
          "type": "string"
        },
        "example": "qkjgjQCQGz"
      },
      "CommentId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "A comment id, from GET /api/review or GET /api/comments.",
        "schema": {
          "type": "string"
        },
        "example": "e7cb98b7-c5e3-4048-8590-e7c1b1dea787"
      },
      "WorkspaceId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "A workspace id, from GET /api/workspaces or GET /api/me.",
        "schema": {
          "type": "string"
        },
        "example": "0f3c2a8e-1b7d-4c1e-9a52-6d1f0e4b7a90"
      },
      "ClientHeader": {
        "name": "X-Ollav-Client",
        "in": "header",
        "required": true,
        "description": "CSRF guard on every state-changing request. Send any value, e.g. agent.",
        "schema": {
          "type": "string",
          "default": "agent"
        },
        "example": "agent"
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Any unique string (e.g. a UUID), up to 255 printable ASCII characters. Retrying with the same key and the same request returns the first response (Idempotent-Replayed: true) instead of doing it twice; the same key with a different request is a 409 IDEMPOTENCY_KEY_REUSED. Only successful responses are kept: after an error, fix the request and retry with the same key. Kept 24 hours, per API key. See https://ollav.app/docs/errors#retrying.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        },
        "example": "5f0c3b1e-8a2d-4c7e-9b1f-2d3e4a5b6c7d"
      },
      "OllavVersion": {
        "name": "Ollav-Version",
        "in": "header",
        "required": false,
        "description": "The API version to use for this request. Without it, the version the API key was pinned to when it was created. An unknown version is a 400 INVALID_API_VERSION. Versions: https://ollav.app/docs/changelog#versions.",
        "schema": {
          "$ref": "#/components/schemas/ApiVersion"
        },
        "example": "2026-11-01"
      }
    },
    "headers": {
      "RequestId": {
        "description": "Identifies this request; quote it when reporting a problem. Errors repeat it as error.request_id.",
        "schema": {
          "type": "string",
          "pattern": "^req_[0-9a-f]{24}$"
        },
        "example": "req_6f1c2a9b0d4e8f7a1b2c3d4e"
      },
      "OllavVersion": {
        "description": "The API version this response follows (the Ollav-Version request header, else the API key's pinned version). Absent only on the INVALID_API_VERSION error.",
        "schema": {
          "$ref": "#/components/schemas/ApiVersion"
        },
        "example": "2026-11-01"
      },
      "IdempotentReplayed": {
        "description": "true when this is the stored response of an earlier request with the same Idempotency-Key, not a new one.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        },
        "example": "true"
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request is invalid; error.param names the field and error.hint the fix. Codes: BAD_JSON, BAD_REQUEST, VALIDATION_ERROR, MISSING_HTML, MISSING_FILE, MISSING_SLUG, EMPTY_BODY, EMPTY, NOT_HTML, INVALID_VISIBILITY, UNSUPPORTED_TYPE, UNSUPPORTED_CONTENT_TYPE, TOO_MANY_REACTIONS, CONFIRMATION_REQUIRED, INVALID_API_VERSION (an unknown Ollav-Version) and IDEMPOTENCY_KEY_INVALID.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Ollav-Version": {
            "$ref": "#/components/headers/OllavVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "invalid_request_error",
                "code": "INVALID_VISIBILITY",
                "message": "visibility must be one of: workspace, link, public",
                "param": "visibility",
                "hint": "visibility must be 'workspace' (default), 'link' or 'public'. Only use 'public' if the user asked to share outside the company.",
                "doc_url": "https://ollav.app/docs/errors#INVALID_VISIBILITY",
                "request_id": "req_6f1c2a9b0d4e8f7a1b2c3d4e",
                "docs": "https://ollav.app/docs/agents#errors"
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "No valid API key (AUTH_REQUIRED). The key is missing, wrong or revoked, or its user left the key's workspace.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Ollav-Version": {
            "$ref": "#/components/headers/OllavVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "authentication_error",
                "code": "AUTH_REQUIRED",
                "message": "Sign in required",
                "param": "Authorization",
                "hint": "Send the API key as 'Authorization: Bearer ol_live_...' (or ol_test_...). If you did, the key is wrong, revoked, or its user left the key's workspace: ask the user for a new key from https://app.ollav.app/keys.",
                "doc_url": "https://ollav.app/docs/errors#AUTH_REQUIRED",
                "request_id": "req_6f1c2a9b0d4e8f7a1b2c3d4e",
                "docs": "https://ollav.app/docs/agents#errors"
              }
            }
          }
        }
      },
      "PlanLimit": {
        "description": "The workspace's plan doesn't allow this (PLAN_LIMIT), it is over its limits and read-only (PLAN_READ_ONLY), or the key's workspace used its daily API calls. Tell the user; don't retry.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Ollav-Version": {
            "$ref": "#/components/headers/OllavVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "permission_error",
                "code": "PLAN_LIMIT",
                "message": "The Free plan allows 3 documents. Upgrade for unlimited documents, or archive or delete one.",
                "limit": "documents",
                "plan": "free",
                "upgradeUrl": "https://app.ollav.app/pricing",
                "pricingUrl": "https://app.ollav.app/pricing",
                "hint": "This workspace has reached a limit of its plan; the message and 'limit' say which. Tell the user and don't retry.",
                "doc_url": "https://ollav.app/docs/errors#PLAN_LIMIT",
                "request_id": "req_6f1c2a9b0d4e8f7a1b2c3d4e",
                "docs": "https://ollav.app/docs/agents#errors"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "Not allowed: the key lacks a scope (SCOPE_REQUIRED), the X-Ollav-Client header is missing (CSRF_REQUIRED), a browser Origin is foreign (ORIGIN_FORBIDDEN), the caller's role is too low (ROLE_REQUIRED), or the action needs a browser session (FORBIDDEN).",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Ollav-Version": {
            "$ref": "#/components/headers/OllavVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "permission_error",
                "code": "SCOPE_REQUIRED",
                "message": "upload scope required",
                "hint": "This API key lacks the 'upload' scope. Ask the user to create a key with it at https://app.ollav.app/keys (scopes: upload, read, comment).",
                "doc_url": "https://ollav.app/docs/errors#SCOPE_REQUIRED",
                "request_id": "req_6f1c2a9b0d4e8f7a1b2c3d4e",
                "docs": "https://ollav.app/docs/agents#errors"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found (NOT_FOUND), or archived (ARCHIVED). Missing, forbidden and other-workspace resources all return the identical 404 (apart from request_id).",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Ollav-Version": {
            "$ref": "#/components/headers/OllavVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "not_found_error",
                "code": "NOT_FOUND",
                "message": "Document not found",
                "hint": "Check the slug or id. Slugs come from the upload response or list_documents (GET /api/dashboard). An API key only sees its own workspace, so documents elsewhere also return 404.",
                "doc_url": "https://ollav.app/docs/errors#NOT_FOUND",
                "request_id": "req_6f1c2a9b0d4e8f7a1b2c3d4e",
                "docs": "https://ollav.app/docs/agents#errors"
              }
            }
          }
        }
      },
      "Conflict": {
        "description": "Clashes with the current state (CONFLICT, e.g. the comment already has an image), or an Idempotency-Key problem: reused for a different request (IDEMPOTENCY_KEY_REUSED) or still in use by a running request (IDEMPOTENCY_KEY_IN_USE).",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Ollav-Version": {
            "$ref": "#/components/headers/OllavVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "idempotency_error",
                "code": "IDEMPOTENCY_KEY_REUSED",
                "message": "This Idempotency-Key was already used for a different request (method, path or body) in the last 24 hours. Use a new key.",
                "param": "Idempotency-Key",
                "hint": "Use a new Idempotency-Key for a new request. Reuse a key only to retry the identical request.",
                "doc_url": "https://ollav.app/docs/errors#IDEMPOTENCY_KEY_REUSED",
                "request_id": "req_6f1c2a9b0d4e8f7a1b2c3d4e",
                "docs": "https://ollav.app/docs/agents#errors"
              }
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "Over the size limit (TOO_LARGE): 15 MB per HTML document, 5 MB per image.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Ollav-Version": {
            "$ref": "#/components/headers/OllavVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "invalid_request_error",
                "code": "TOO_LARGE",
                "message": "File exceeds 15 MB limit",
                "hint": "Ollav hosts one HTML file up to 15 MB. Shrink it: convert inlined PNG screenshots to WebP, downscale images wider than about 2000px, or load large media from absolute https:// URLs. Then upload again.",
                "doc_url": "https://ollav.app/docs/errors#TOO_LARGE",
                "request_id": "req_6f1c2a9b0d4e8f7a1b2c3d4e",
                "docs": "https://ollav.app/docs/agents#errors"
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests (RATE_LIMITED): over 30 publishes or 120 comments an hour per account (looser per IP), too many exports, or a test key (ol_test_) over its 60 requests a minute. error.retry_after and the Retry-After header say how many seconds to wait; don't retry in a loop.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Ollav-Version": {
            "$ref": "#/components/headers/OllavVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "rate_limit_error",
                "code": "RATE_LIMITED",
                "message": "Too many publishes: 30 an hour per account. Try again in 12 minutes.",
                "retry_after": 702,
                "hint": "Too many requests. Wait error.retry_after seconds (also the Retry-After header; the message says it in words), then retry once with the same Idempotency-Key; don't retry in a loop. A test key allows 60 requests a minute.",
                "doc_url": "https://ollav.app/docs/errors#RATE_LIMITED",
                "request_id": "req_6f1c2a9b0d4e8f7a1b2c3d4e",
                "docs": "https://ollav.app/docs/agents#errors"
              }
            }
          }
        }
      },
      "Gone": {
        "description": "The document was taken down for breaking the Terms of Service (DOCUMENT_REMOVED). Don't retry.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Ollav-Version": {
            "$ref": "#/components/headers/OllavVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "not_found_error",
                "code": "DOCUMENT_REMOVED",
                "message": "This document was taken down for breaking the Terms; it can't be published to",
                "hint": "This document was taken down and can't be published to or restored through the API. Tell the user; don't retry.",
                "doc_url": "https://ollav.app/docs/errors#DOCUMENT_REMOVED",
                "request_id": "req_6f1c2a9b0d4e8f7a1b2c3d4e",
                "docs": "https://ollav.app/docs/agents#errors"
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Something failed on our side (INTERNAL_ERROR, type api_error). Retry once with the same Idempotency-Key; if it persists, report error.request_id.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Ollav-Version": {
            "$ref": "#/components/headers/OllavVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "api_error",
                "code": "INTERNAL_ERROR",
                "message": "Something went wrong on our side.",
                "hint": "Retry once after a few seconds with the same Idempotency-Key. If it keeps failing, tell the user and include error.request_id.",
                "doc_url": "https://ollav.app/docs/errors#INTERNAL_ERROR",
                "request_id": "req_6f1c2a9b0d4e8f7a1b2c3d4e",
                "docs": "https://ollav.app/docs/agents#errors"
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every /api error. Match on error.code (stable; the closed list at https://ollav.app/docs/errors). error.hint is the next step, written for an agent to act on.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "type",
              "code",
              "message",
              "doc_url",
              "request_id"
            ],
            "properties": {
              "type": {
                "$ref": "#/components/schemas/ErrorType"
              },
              "code": {
                "$ref": "#/components/schemas/ErrorCode"
              },
              "message": {
                "type": "string",
                "description": "What went wrong."
              },
              "param": {
                "type": "string",
                "description": "The request field or header at fault, when there is one."
              },
              "hint": {
                "type": "string",
                "description": "What to do next."
              },
              "doc_url": {
                "type": "string",
                "format": "uri",
                "description": "The code's anchor on /docs/errors."
              },
              "request_id": {
                "type": "string",
                "description": "Same as the Request-Id response header."
              },
              "docs": {
                "type": "string",
                "format": "uri",
                "description": "Older link to the agent quickstart's error section, kept for existing clients."
              },
              "limit": {
                "type": "string",
                "description": "PLAN_LIMIT only: which limit (documents, members, storageBytes, apiCallsPerDay)."
              },
              "plan": {
                "type": "string",
                "description": "PLAN_LIMIT / PLAN_READ_ONLY only: the workspace's plan."
              },
              "retry_after": {
                "type": "integer",
                "description": "RATE_LIMITED only: seconds to wait before retrying (same as the Retry-After header)."
              },
              "upgradeUrl": {
                "type": "string",
                "format": "uri"
              },
              "pricingUrl": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status, repeated (some routes)."
          }
        }
      },
      "ErrorType": {
        "type": "string",
        "description": "What kind of problem, for coarse handling. See https://ollav.app/docs/errors#types.",
        "enum": [
          "invalid_request_error",
          "authentication_error",
          "permission_error",
          "not_found_error",
          "rate_limit_error",
          "idempotency_error",
          "api_error"
        ]
      },
      "ErrorCode": {
        "type": "string",
        "description": "Every error code. Filled from src/errors.ts by npm run build:openapi.",
        "x-enum-from": "src/errors.ts",
        "enum": [
          "AUTH_REQUIRED",
          "ONBOARDING_EXPIRED",
          "SIGNATURE_INVALID",
          "SCOPE_REQUIRED",
          "ORIGIN_FORBIDDEN",
          "ROLE_REQUIRED",
          "FORBIDDEN",
          "NOT_ALLOWED",
          "NO_WORKSPACE",
          "TERMS_REQUIRED",
          "PLAN_LIMIT",
          "PLAN_READ_ONLY",
          "PLAN_REQUIRED",
          "ACCOUNT_SUSPENDED",
          "NOT_FOUND",
          "ARCHIVED",
          "VERSION_NOT_FOUND",
          "R2_MISSING",
          "DOCUMENT_REMOVED",
          "NO_VERSION",
          "CSRF_REQUIRED",
          "LOGIN_CODE_INVALID",
          "BAD_JSON",
          "BAD_REQUEST",
          "VALIDATION_ERROR",
          "MISSING_HTML",
          "MISSING_FILE",
          "MISSING_SLUG",
          "EMPTY_BODY",
          "EMPTY",
          "NOT_HTML",
          "INVALID_VISIBILITY",
          "INVALID_EMAIL",
          "UNSUPPORTED_TYPE",
          "UNSUPPORTED_CONTENT_TYPE",
          "TOO_LARGE",
          "TOO_MANY_FILES",
          "TOO_MANY_REACTIONS",
          "CONFIRMATION_REQUIRED",
          "CONFLICT",
          "SOLE_OWNER",
          "CONSUMER_DOMAIN",
          "TOO_MANY_MEMBERS",
          "ALREADY_SUBSCRIBED",
          "NO_SUBSCRIPTION",
          "METHOD_NOT_ALLOWED",
          "INVALID_API_VERSION",
          "IDEMPOTENCY_KEY_INVALID",
          "IDEMPOTENCY_KEY_REUSED",
          "IDEMPOTENCY_KEY_IN_USE",
          "RATE_LIMITED",
          "TOO_MANY_REQUESTS",
          "SIGNIN_RATE_LIMITED",
          "INTERNAL_ERROR",
          "PARSE_ERROR",
          "PROCESSING_FAILED",
          "STRIPE_ERROR",
          "BILLING_CANCEL_FAILED",
          "BILLING_NOT_CONFIGURED",
          "EMAIL_SEND_FAILED",
          "EMAIL_NOT_CONFIGURED",
          "GOOGLE_NOT_CONFIGURED"
        ]
      },
      "ApiVersion": {
        "type": "string",
        "description": "A dated API version. Filled from src/api-version.ts by npm run build:openapi.",
        "x-enum-from": "src/api-version.ts",
        "enum": [
          "2026-10-01",
          "2026-11-01"
        ]
      },
      "Visibility": {
        "type": "string",
        "enum": [
          "workspace",
          "link",
          "public"
        ],
        "default": "workspace",
        "description": "workspace (default): members of the workspace only. link: anyone signed in to Ollav with the URL. public: PUBLIC, anyone with the URL, no sign-in, including people outside the company."
      },
      "UnixMs": {
        "type": "integer",
        "description": "Unix epoch milliseconds.",
        "examples": [
          1793107356443
        ]
      },
      "DocumentSummary": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "title",
          "filename",
          "visibility",
          "createdAt",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "description": "Identifies the document in every other call and in its URL."
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "filename": {
            "type": [
              "string",
              "null"
            ],
            "description": "Uploads with the same filename add versions."
          },
          "visibility": {
            "$ref": "#/components/schemas/Visibility"
          },
          "createdAt": {
            "$ref": "#/components/schemas/UnixMs"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The share URL on the content origin. Give this to the user."
          }
        }
      },
      "StoredVersion": {
        "type": "object",
        "required": [
          "id",
          "documentId",
          "hash",
          "r2Key",
          "size",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "workspaceId": {
            "type": "string"
          },
          "documentId": {
            "type": "string"
          },
          "hash": {
            "type": "string",
            "description": "SHA-256 of the HTML."
          },
          "r2Key": {
            "type": "string"
          },
          "size": {
            "type": "integer",
            "description": "Bytes."
          },
          "createdAt": {
            "$ref": "#/components/schemas/UnixMs"
          }
        }
      },
      "DocumentDetail": {
        "type": "object",
        "required": [
          "id",
          "workspaceId",
          "slug",
          "title",
          "filename",
          "ownerId",
          "visibility",
          "currentVersionId",
          "createdAt",
          "updatedAt",
          "archivedAt",
          "expiresAt",
          "isPermanent",
          "url",
          "versions"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "workspaceId": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "filename": {
            "type": [
              "string",
              "null"
            ]
          },
          "ownerId": {
            "type": "string"
          },
          "ownerEmail": {
            "type": [
              "string",
              "null"
            ]
          },
          "visibility": {
            "$ref": "#/components/schemas/Visibility"
          },
          "currentVersionId": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "$ref": "#/components/schemas/UnixMs"
          },
          "updatedAt": {
            "$ref": "#/components/schemas/UnixMs"
          },
          "archivedAt": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Unix ms; set when archived."
          },
          "expiresAt": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Unix ms; null when permanent."
          },
          "isPermanent": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "expiryWarnedAt": {
            "type": [
              "integer",
              "null"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "versions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StoredVersion"
            }
          }
        }
      },
      "UploadRequest": {
        "type": "object",
        "required": [
          "html"
        ],
        "properties": {
          "html": {
            "type": "string",
            "description": "The whole self-contained HTML document, up to 15 MB."
          },
          "title": {
            "type": "string",
            "description": "Defaults to the <title> tag, then the filename."
          },
          "filename": {
            "type": "string",
            "description": "Re-uploading the same filename adds a version to your document."
          },
          "visibility": {
            "$ref": "#/components/schemas/Visibility"
          }
        }
      },
      "UploadResult": {
        "type": "object",
        "required": [
          "url",
          "slug",
          "documentId",
          "versionId",
          "hash",
          "deduped",
          "versioned",
          "warnings",
          "livemode"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The share URL. Give this to the user."
          },
          "slug": {
            "type": "string"
          },
          "documentId": {
            "type": "string"
          },
          "versionId": {
            "type": "string"
          },
          "hash": {
            "type": "string"
          },
          "deduped": {
            "type": "boolean",
            "description": "True when this exact content was already uploaded."
          },
          "versioned": {
            "type": "boolean",
            "description": "True when this added a version to an existing document (same filename)."
          },
          "previousVersionId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set when versioned."
          },
          "visibility": {
            "$ref": "#/components/schemas/Visibility"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Relative asset paths or <base href> that won't work here. Advisory; never blocks the upload."
          },
          "livemode": {
            "type": "boolean",
            "description": "False when a test key made it: a test-mode document only its owner sees, marked TEST and deleted 30 days after upload."
          }
        }
      },
      "VersionSummary": {
        "type": "object",
        "required": [
          "id",
          "hash",
          "size",
          "createdAt",
          "isCurrent",
          "versionNumber"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "hash": {
            "type": "string"
          },
          "size": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "isCurrent": {
            "type": "boolean"
          },
          "versionNumber": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "VersionList": {
        "type": "object",
        "required": [
          "document",
          "versions"
        ],
        "properties": {
          "document": {
            "type": "object",
            "required": [
              "slug",
              "title",
              "versionCount"
            ],
            "properties": {
              "slug": {
                "type": "string"
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "versionCount": {
                "type": "integer"
              }
            }
          },
          "versions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VersionSummary"
            }
          }
        }
      },
      "NewVersionResult": {
        "type": "object",
        "required": [
          "deduped",
          "slug",
          "url",
          "versionId",
          "hash",
          "warnings",
          "livemode"
        ],
        "properties": {
          "deduped": {
            "type": "boolean"
          },
          "slug": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "documentId": {
            "type": "string",
            "description": "Absent when deduped."
          },
          "versionId": {
            "type": "string"
          },
          "hash": {
            "type": "string"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "livemode": {
            "type": "boolean",
            "description": "False for a test-mode document."
          }
        }
      },
      "ArchiveResult": {
        "type": "object",
        "required": [
          "ok",
          "archived"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "archived": {
            "type": "boolean"
          }
        }
      },
      "DocumentExport": {
        "type": "object",
        "required": [
          "exportedAt",
          "document",
          "versions",
          "comments"
        ],
        "properties": {
          "exportedAt": {
            "type": "string",
            "format": "date-time"
          },
          "document": {
            "type": "object",
            "required": [
              "id",
              "slug",
              "title",
              "filename",
              "visibility",
              "createdAt",
              "updatedAt",
              "archivedAt",
              "expiresAt",
              "isPermanent",
              "url"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "slug": {
                "type": "string"
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "filename": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "visibility": {
                "$ref": "#/components/schemas/Visibility"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              },
              "archivedAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "expiresAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "isPermanent": {
                "type": "boolean"
              },
              "url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "versions": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "hash",
                "r2Key",
                "size",
                "createdAt",
                "isCurrent",
                "html"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "hash": {
                  "type": "string"
                },
                "r2Key": {
                  "type": "string"
                },
                "size": {
                  "type": "integer"
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "isCurrent": {
                  "type": "boolean"
                },
                "html": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Null if the stored file couldn't be read."
                }
              }
            }
          },
          "comments": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "parentId",
                "selector",
                "elementText",
                "authorId",
                "body",
                "createdAt",
                "resolvedAt",
                "resolvedBy",
                "versionId"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "parentId": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "selector": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "elementText": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "authorId": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "body": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "resolvedAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "resolvedBy": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "versionId": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "DashboardDocument": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "title",
          "filename",
          "visibility",
          "createdAt",
          "updatedAt",
          "archivedAt",
          "expiresAt",
          "isPermanent",
          "versionCount",
          "commentCount",
          "unresolvedCount",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "filename": {
            "type": [
              "string",
              "null"
            ]
          },
          "visibility": {
            "$ref": "#/components/schemas/Visibility"
          },
          "createdAt": {
            "$ref": "#/components/schemas/UnixMs"
          },
          "updatedAt": {
            "$ref": "#/components/schemas/UnixMs"
          },
          "archivedAt": {
            "type": [
              "integer",
              "null"
            ]
          },
          "expiresAt": {
            "type": [
              "integer",
              "null"
            ]
          },
          "isPermanent": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "versionCount": {
            "type": "integer"
          },
          "commentCount": {
            "type": "integer"
          },
          "unresolvedCount": {
            "type": "integer"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Dashboard": {
        "type": "object",
        "required": [
          "workspace",
          "documents",
          "sharedWithMe"
        ],
        "properties": {
          "workspace": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/WorkspaceRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "documents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DashboardDocument"
            }
          },
          "sharedWithMe": {
            "type": "array",
            "description": "Documents shared with you as a guest, in any workspace (browser sessions only; empty for API keys).",
            "items": {
              "type": "object",
              "required": [
                "slug",
                "title",
                "url"
              ],
              "properties": {
                "slug": {
                  "type": "string"
                },
                "title": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "filename": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "workspaceName": {
                  "type": "string"
                },
                "sharedAt": {
                  "$ref": "#/components/schemas/UnixMs"
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          }
        }
      },
      "Rect": {
        "type": "object",
        "required": [
          "x",
          "y",
          "w",
          "h"
        ],
        "description": "Bounding box of the element, in page pixels.",
        "properties": {
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          },
          "w": {
            "type": "number"
          },
          "h": {
            "type": "number"
          }
        }
      },
      "Breadcrumb": {
        "type": "object",
        "required": [
          "selector",
          "text"
        ],
        "description": "One click recorded before the comment was placed.",
        "properties": {
          "selector": {
            "type": "string",
            "maxLength": 300
          },
          "text": {
            "type": "string",
            "maxLength": 60
          }
        }
      },
      "Author": {
        "type": "object",
        "required": [
          "email",
          "name"
        ],
        "properties": {
          "email": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Attachment": {
        "type": "object",
        "required": [
          "url",
          "mime"
        ],
        "description": "An image attached to a top-level comment, served from the content origin.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "mime": {
            "type": "string"
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "ReactionCount": {
        "type": "object",
        "required": [
          "reaction",
          "count",
          "mine"
        ],
        "properties": {
          "reaction": {
            "type": "string",
            "description": "The emoji."
          },
          "count": {
            "type": "integer",
            "minimum": 1
          },
          "mine": {
            "type": "boolean",
            "description": "Whether the caller reacted with it."
          }
        }
      },
      "CommentRequest": {
        "type": "object",
        "required": [
          "slug"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "body": {
            "type": "string",
            "maxLength": 5000,
            "description": "Required, unless a top-level comment sets markEmoji."
          },
          "parentId": {
            "type": "string",
            "description": "Set for a reply (a top-level comment's id); omit for a top-level comment."
          },
          "selector": {
            "type": "string",
            "maxLength": 1000,
            "description": "CSS selector of the element. Required for top-level comments."
          },
          "rect": {
            "$ref": "#/components/schemas/Rect"
          },
          "elementText": {
            "type": "string",
            "description": "The element's visible text; helps re-anchor and find it in source."
          },
          "markEmoji": {
            "type": [
              "string",
              "null"
            ],
            "description": "One emoji to show as the pin (top-level only); null for a numbered pin."
          },
          "reproduceNote": {
            "type": "string",
            "maxLength": 300,
            "description": "How to reach the UI state, for reviewers (top-level only)."
          },
          "breadcrumbs": {
            "type": "array",
            "maxItems": 8,
            "description": "Clicks that lead to the UI state (top-level only).",
            "items": {
              "$ref": "#/components/schemas/Breadcrumb"
            }
          }
        }
      },
      "CreatedComment": {
        "type": "object",
        "description": "The stored comment as created. Since API version 2026-11-01 `breadcrumbs` is an array, as in GET /api/comments; before, it was the stored JSON string.",
        "required": [
          "id",
          "documentId",
          "versionId",
          "parentId",
          "selector",
          "fallbackRect",
          "elementText",
          "authorId",
          "body",
          "createdAt",
          "resolvedAt",
          "resolvedBy",
          "markEmoji",
          "author"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "workspaceId": {
            "type": "string"
          },
          "documentId": {
            "type": "string"
          },
          "versionId": {
            "type": "string"
          },
          "parentId": {
            "type": [
              "string",
              "null"
            ]
          },
          "selector": {
            "type": [
              "string",
              "null"
            ]
          },
          "fallbackRect": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Rect"
              },
              {
                "type": "null"
              }
            ]
          },
          "elementText": {
            "type": [
              "string",
              "null"
            ]
          },
          "authorId": {
            "type": "string"
          },
          "body": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "resolvedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "resolvedBy": {
            "type": [
              "string",
              "null"
            ]
          },
          "reproduceNote": {
            "type": [
              "string",
              "null"
            ]
          },
          "breadcrumbs": {
            "type": [
              "array",
              "null"
            ],
            "description": "Clicks that led to the UI state. Before API version 2026-11-01 this was the stored JSON string instead of the array.",
            "items": {
              "$ref": "#/components/schemas/Breadcrumb"
            }
          },
          "markEmoji": {
            "type": [
              "string",
              "null"
            ]
          },
          "author": {
            "$ref": "#/components/schemas/Author"
          }
        }
      },
      "Comment": {
        "type": "object",
        "required": [
          "id",
          "documentId",
          "versionId",
          "parentId",
          "selector",
          "fallbackRect",
          "elementText",
          "authorId",
          "body",
          "createdAt",
          "resolvedAt",
          "resolvedBy",
          "author",
          "isCurrentVersion",
          "markEmoji",
          "attachment",
          "reactions",
          "replies"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "documentId": {
            "type": "string"
          },
          "versionId": {
            "type": "string"
          },
          "parentId": {
            "type": [
              "string",
              "null"
            ]
          },
          "selector": {
            "type": [
              "string",
              "null"
            ],
            "description": "Rendered-DOM selector (top-level only)."
          },
          "fallbackRect": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Rect"
              },
              {
                "type": "null"
              }
            ]
          },
          "elementText": {
            "type": [
              "string",
              "null"
            ]
          },
          "authorId": {
            "type": [
              "string",
              "null"
            ]
          },
          "body": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "resolvedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "resolvedBy": {
            "type": [
              "string",
              "null"
            ]
          },
          "author": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Author"
              },
              {
                "type": "null"
              }
            ]
          },
          "isCurrentVersion": {
            "type": "boolean",
            "description": "False when the comment was made on an earlier version."
          },
          "reproduceNote": {
            "type": [
              "string",
              "null"
            ]
          },
          "breadcrumbs": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/Breadcrumb"
            }
          },
          "markEmoji": {
            "type": [
              "string",
              "null"
            ]
          },
          "attachment": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Attachment"
              },
              {
                "type": "null"
              }
            ]
          },
          "snapshotUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "The page as it looked when the comment was placed, if captured."
          },
          "reactions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReactionCount"
            }
          },
          "replies": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Comment"
            }
          }
        }
      },
      "CommentList": {
        "type": "object",
        "required": [
          "slug",
          "currentVersionId",
          "count",
          "unresolvedCount",
          "comments"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "currentVersionId": {
            "type": [
              "string",
              "null"
            ]
          },
          "docOwnerId": {
            "type": [
              "string",
              "null"
            ]
          },
          "viewerCanManage": {
            "type": "boolean"
          },
          "count": {
            "type": "integer",
            "description": "Top-level comments."
          },
          "unresolvedCount": {
            "type": "integer"
          },
          "comments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Comment"
            }
          }
        }
      },
      "ResolveResult": {
        "type": "object",
        "required": [
          "ok",
          "resolved"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "resolved": {
            "type": "boolean"
          }
        }
      },
      "AttachmentResult": {
        "type": "object",
        "required": [
          "id",
          "url",
          "mime",
          "width",
          "height",
          "size"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "mime": {
            "type": "string",
            "enum": [
              "image/png",
              "image/jpeg",
              "image/webp",
              "image/gif"
            ]
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "size": {
            "type": "integer",
            "description": "Bytes."
          }
        }
      },
      "ReactionResult": {
        "type": "object",
        "required": [
          "ok",
          "commentId",
          "reactions"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "commentId": {
            "type": "string"
          },
          "reactions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReactionCount"
            }
          }
        }
      },
      "ReviewThread": {
        "type": "object",
        "required": [
          "id",
          "status",
          "onCurrentVersion",
          "versionId",
          "author",
          "createdAt",
          "lastActivityAt",
          "comment",
          "anchor",
          "replies"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Pass to resolve_comment / POST /api/comments/{id}/resolve."
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "resolved"
            ]
          },
          "onCurrentVersion": {
            "type": "boolean"
          },
          "versionId": {
            "type": "string"
          },
          "author": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Author"
              },
              {
                "type": "null"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastActivityAt": {
            "type": "string",
            "format": "date-time"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          },
          "markEmoji": {
            "type": [
              "string",
              "null"
            ]
          },
          "attachment": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Attachment"
              },
              {
                "type": "null"
              }
            ]
          },
          "anchor": {
            "type": "object",
            "required": [
              "quote",
              "reproduceNote",
              "breadcrumbs",
              "selector"
            ],
            "properties": {
              "quote": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The element's text: search your source for THIS."
              },
              "reproduceNote": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "breadcrumbs": {
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "$ref": "#/components/schemas/Breadcrumb"
                }
              },
              "selector": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Rendered-DOM selector, not a source locator."
              }
            }
          },
          "replies": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "author",
                "body",
                "createdAt"
              ],
              "properties": {
                "author": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/Author"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "body": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      },
      "ReviewDigest": {
        "type": "object",
        "required": [
          "slug",
          "title",
          "url",
          "currentVersionId",
          "filter",
          "totalThreads",
          "openThreads",
          "returned",
          "cursor",
          "generatedAt",
          "threads"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "currentVersionId": {
            "type": [
              "string",
              "null"
            ]
          },
          "filter": {
            "type": "object",
            "required": [
              "status",
              "since"
            ],
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "open",
                  "resolved",
                  "all"
                ]
              },
              "since": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "totalThreads": {
            "type": "integer"
          },
          "openThreads": {
            "type": "integer"
          },
          "returned": {
            "type": "integer"
          },
          "cursor": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Pass back as ?since= on the next call."
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "note": {
            "type": "string"
          },
          "threads": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReviewThread"
            }
          },
          "markdown": {
            "type": "string",
            "description": "The same digest as Markdown."
          }
        }
      },
      "ExportLink": {
        "type": "object",
        "required": [
          "scope",
          "workspaceId",
          "downloadUrl",
          "expiresAt",
          "partial"
        ],
        "properties": {
          "scope": {
            "type": "string",
            "enum": [
              "account",
              "workspace"
            ]
          },
          "workspaceId": {
            "type": [
              "string",
              "null"
            ]
          },
          "downloadUrl": {
            "type": "string",
            "format": "uri",
            "description": "GET it within 15 minutes; no API key needed."
          },
          "expiresAt": {
            "$ref": "#/components/schemas/UnixMs"
          },
          "partial": {
            "type": "boolean",
            "description": "True for an API key's account export: its own workspace only."
          }
        }
      },
      "Role": {
        "type": "string",
        "enum": [
          "owner",
          "admin",
          "member",
          "guest"
        ]
      },
      "WorkspaceRef": {
        "type": "object",
        "required": [
          "id",
          "name",
          "role"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/Role"
          }
        }
      },
      "WorkspaceList": {
        "type": "object",
        "required": [
          "currentId",
          "workspaces",
          "billingNotice"
        ],
        "properties": {
          "currentId": {
            "type": [
              "string",
              "null"
            ]
          },
          "workspaces": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "personal",
                "role"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "personal": {
                  "type": "boolean",
                  "description": "The user's personal workspace (can't be deleted)."
                },
                "role": {
                  "$ref": "#/components/schemas/Role"
                }
              }
            }
          },
          "billingNotice": {
            "description": "Set when payment failed (grace) or the workspace is read-only over its limits.",
            "anyOf": [
              {
                "type": "object",
                "required": [
                  "kind",
                  "message"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "grace",
                      "read_only"
                    ]
                  },
                  "message": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "MemberList": {
        "type": "object",
        "required": [
          "workspaceId",
          "role",
          "members",
          "invites"
        ],
        "properties": {
          "workspaceId": {
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/Role"
          },
          "members": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "userId",
                "email",
                "name",
                "role",
                "joinedAt"
              ],
              "properties": {
                "userId": {
                  "type": "string"
                },
                "email": {
                  "type": "string"
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "role": {
                  "$ref": "#/components/schemas/Role"
                },
                "joinedAt": {
                  "$ref": "#/components/schemas/UnixMs"
                }
              }
            }
          },
          "invites": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "role",
                "createdAt",
                "expiresAt"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "email": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Null for a shareable link."
                },
                "role": {
                  "$ref": "#/components/schemas/Role"
                },
                "documentSlug": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Set for a guest invite to one document."
                },
                "documentTitle": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "maxUses": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "useCount": {
                  "type": "integer"
                },
                "createdAt": {
                  "$ref": "#/components/schemas/UnixMs"
                },
                "expiresAt": {
                  "$ref": "#/components/schemas/UnixMs"
                }
              }
            }
          }
        }
      },
      "PlanId": {
        "type": "string",
        "enum": [
          "free",
          "pro",
          "team"
        ]
      },
      "PlanLimits": {
        "type": "object",
        "description": "Null means no limit.",
        "required": [
          "documents",
          "members",
          "storageBytes",
          "versionRetentionDays",
          "apiCallsPerDay"
        ],
        "properties": {
          "documents": {
            "type": [
              "integer",
              "null"
            ]
          },
          "members": {
            "type": [
              "integer",
              "null"
            ]
          },
          "storageBytes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "versionRetentionDays": {
            "type": [
              "integer",
              "null"
            ]
          },
          "apiCallsPerDay": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "WorkspaceBilling": {
        "type": "object",
        "required": [
          "workspaceId",
          "enabled",
          "role",
          "canManage",
          "plan",
          "subscription",
          "usage",
          "limits",
          "readOnly",
          "overLimits"
        ],
        "properties": {
          "workspaceId": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean",
            "description": "False when billing isn't configured: no plans, no limits."
          },
          "role": {
            "$ref": "#/components/schemas/Role"
          },
          "canManage": {
            "type": "boolean",
            "description": "Whether the caller may change the plan (in the web UI)."
          },
          "plan": {
            "description": "The plan in force now (null when billing is off).",
            "anyOf": [
              {
                "$ref": "#/components/schemas/PlanId"
              },
              {
                "type": "null"
              }
            ]
          },
          "subscribedPlan": {
            "$ref": "#/components/schemas/PlanId"
          },
          "inGrace": {
            "type": "boolean",
            "description": "Payment failed; the paid plan stays until graceEndsAt."
          },
          "graceEndsAt": {
            "type": [
              "integer",
              "null"
            ]
          },
          "lapsed": {
            "type": "boolean"
          },
          "subscription": {
            "anyOf": [
              {
                "type": "object",
                "required": [
                  "status",
                  "plan",
                  "interval",
                  "seats",
                  "currentPeriodEnd",
                  "cancelAtPeriodEnd",
                  "live"
                ],
                "properties": {
                  "status": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "plan": {
                    "type": "string"
                  },
                  "interval": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "month",
                      "year",
                      null
                    ]
                  },
                  "seats": {
                    "type": "integer"
                  },
                  "currentPeriodEnd": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "cancelAtPeriodEnd": {
                    "type": "boolean"
                  },
                  "live": {
                    "type": "boolean"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "limits": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PlanLimits"
              },
              {
                "type": "null"
              }
            ]
          },
          "usage": {
            "type": "object",
            "required": [
              "documents",
              "members",
              "storageBytes"
            ],
            "properties": {
              "documents": {
                "type": "integer"
              },
              "members": {
                "type": "integer"
              },
              "storageBytes": {
                "type": "integer"
              },
              "apiCallsToday": {
                "type": "integer"
              }
            }
          },
          "overLimits": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "documents",
                "members",
                "storageBytes",
                "apiCallsPerDay"
              ]
            }
          },
          "readOnly": {
            "type": "boolean"
          },
          "pricingUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Me": {
        "type": "object",
        "required": [
          "user",
          "workspace",
          "livemode"
        ],
        "properties": {
          "user": {
            "type": "object",
            "required": [
              "id",
              "email",
              "name"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "workspace": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/WorkspaceRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "livemode": {
            "type": "boolean",
            "description": "False for an ol_test_ key: it works in the caller's test-mode sandbox of a workspace (`workspace` is the sandbox)."
          }
        }
      },
      "ApiKeyList": {
        "type": "object",
        "required": [
          "keys"
        ],
        "properties": {
          "keys": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "keyPrefix",
                "name",
                "scopes",
                "createdAt",
                "lastUsedAt",
                "revokedAt",
                "apiVersion",
                "mode"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "keyPrefix": {
                  "type": "string",
                  "description": "The first characters of the key, for recognising it."
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "scopes": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "upload",
                      "read",
                      "comment"
                    ]
                  }
                },
                "createdAt": {
                  "$ref": "#/components/schemas/UnixMs"
                },
                "lastUsedAt": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "revokedAt": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "apiVersion": {
                  "description": "The API version this key's requests use without an Ollav-Version header, pinned when the key was created.",
                  "$ref": "#/components/schemas/ApiVersion"
                },
                "mode": {
                  "type": "string",
                  "enum": [
                    "live",
                    "test"
                  ],
                  "description": "live (ol_live_ or older sk_live_) or test (ol_test_: works in the owner's test-mode sandbox)."
                }
              }
            }
          }
        }
      }
    }
  },
  "x-generated-from": "openapi/ollav.yaml"
}