{
  "openapi": "3.1.0",
  "info": {
    "title": "Lolly",
    "version": "1.184.0",
    "summary": "Render on-brand files from URL parameters.",
    "description": "Lolly: constraint-first, template-driven platform for generating production-ready creative and content assets at scale.\n\nEvery key that is not reserved is a tool input, named by its manifest id. Read the manifest at /tools/{id}/tool.json (or call lolly_describe_tool over MCP) before building a request; the whole reserved set is documented at https://lolly.tools/info/build/url-mode.md. Short guide: https://lolly.tools/agents.md",
    "license": {
      "name": "MPL-2.0",
      "identifier": "MPL-2.0"
    },
    "contact": {
      "url": "https://github.com/lolly-tools/lolly"
    }
  },
  "externalDocs": {
    "description": "The docs, as markdown twins",
    "url": "https://lolly.tools/llms.txt"
  },
  "servers": [
    {
      "url": "https://lolly.tools",
      "description": "The reference instance. The hot-link render route is switched off here (404)."
    },
    {
      "url": "https://lolly.art",
      "description": "The public demo instance. The hot-link render route is live."
    }
  ],
  "tags": [
    {
      "name": "render",
      "description": "Files from URL parameters"
    },
    {
      "name": "catalog",
      "description": "What this deployment offers"
    },
    {
      "name": "mcp",
      "description": "The Model Context Protocol endpoint and its OAuth discovery"
    }
  ],
  "paths": {
    "/tool/{id}.{ext}": {
      "get": {
        "tags": [
          "render"
        ],
        "operationId": "renderTool",
        "summary": "Render a tool to a file (browser-free formats, no auth)",
        "description": "The canonical embed URL served for real: the query is the tool's inputs plus the reserved output keys. Public tool and catalog data only, nothing stored per request. Content Credentials are always off here, which is what makes the response deterministic for its URL and therefore cacheable; a credentialed render is one lolly_render call away. Self-hosters disable the route with LOLLY_DISABLE_RENDER_GET=1, after which every such URL returns 404.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The tool id, as listed in /catalog/tools/index.json.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9-]*[a-z0-9]$"
            }
          },
          {
            "name": "ext",
            "in": "path",
            "required": true,
            "description": "The output format. png is served for SVG-native tools only.",
            "schema": {
              "type": "string",
              "enum": [
                "svg",
                "emf",
                "eps",
                "eps-cmyk",
                "dxf",
                "exr",
                "hdr",
                "penpot",
                "html",
                "md",
                "txt",
                "json",
                "csv",
                "ics",
                "vcf",
                "png"
              ]
            }
          },
          {
            "name": "width",
            "in": "query",
            "required": false,
            "description": "Output width in `unit` (alias `w`). Physical units convert at `dpi`; the result may not exceed 10000 px.",
            "schema": {
              "type": "number",
              "exclusiveMinimum": 0
            }
          },
          {
            "name": "height",
            "in": "query",
            "required": false,
            "description": "Output height in `unit` (alias `h`).",
            "schema": {
              "type": "number",
              "exclusiveMinimum": 0
            }
          },
          {
            "name": "unit",
            "in": "query",
            "required": false,
            "description": "The unit width and height are given in.",
            "schema": {
              "type": "string",
              "enum": [
                "px",
                "mm",
                "cm",
                "in",
                "pt"
              ],
              "default": "px"
            }
          },
          {
            "name": "dpi",
            "in": "query",
            "required": false,
            "description": "Raster resolution for physical units.",
            "schema": {
              "type": "number",
              "minimum": 1,
              "maximum": 1200,
              "default": 300
            }
          },
          {
            "name": "profile",
            "in": "query",
            "required": false,
            "description": "Colour profile: `srgb`, `none` or a CMYK press condition such as `fogra39`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "_v",
            "in": "query",
            "required": false,
            "description": "Pin the tool version so a tool update cannot change the output.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Locale for a tool that localises its output.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "z",
            "in": "query",
            "required": false,
            "description": "A packed query (the compressed form the app mints for long links). Expanded server-side before the other keys are read.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "x-lolly-inputs": "Any other query key is a tool input by manifest id (or urlKey); see /tools/{id}/tool.json.",
        "responses": {
          "200": {
            "description": "The rendered file. Text formats carry `; charset=utf-8`.",
            "headers": {
              "ETag": {
                "description": "Strong: engine version, catalog build and the canonical URL.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "public, s-maxage=86400, stale-while-revalidate=604800",
                "schema": {
                  "type": "string"
                }
              },
              "Content-Security-Policy": {
                "description": "Always `sandbox`, so tool-authored markup never executes in the origin when navigated to directly.",
                "schema": {
                  "type": "string"
                }
              },
              "Content-Disposition": {
                "description": "inline; filename=\"{id}.{ext}\"",
                "schema": {
                  "type": "string"
                }
              },
              "X-Robots-Tag": {
                "description": "noindex",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string"
                }
              },
              "application/x-msmetafile": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/postscript": {
                "schema": {
                  "type": "string"
                }
              },
              "image/vnd.dxf": {
                "schema": {
                  "type": "string"
                }
              },
              "image/x-exr": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/vnd.radiance": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/x-penpot": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/html": {
                "schema": {
                  "type": "string"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/json": {
                "schema": {
                  "type": "string"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "text/calendar": {
                "schema": {
                  "type": "string"
                }
              },
              "text/vcard": {
                "schema": {
                  "type": "string"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "If-None-Match matched the ETag."
          },
          "400": {
            "description": "A format that needs the browser tier, png for a tool that is not SVG-native, an output bound exceeded (10000 px edge, dpi 1..1200) or a query over 4096 characters. The body names the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown tool, a tool whose status is not official or community, or a deployment with the route switched off. The same answer for all three.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many renders from this address.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The render failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "head": {
        "tags": [
          "render"
        ],
        "operationId": "renderToolHead",
        "summary": "Headers only, same status codes as GET",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The tool id, as listed in /catalog/tools/index.json.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9-]*[a-z0-9]$"
            }
          },
          {
            "name": "ext",
            "in": "path",
            "required": true,
            "description": "The output format. png is served for SVG-native tools only.",
            "schema": {
              "type": "string",
              "enum": [
                "svg",
                "emf",
                "eps",
                "eps-cmyk",
                "dxf",
                "exr",
                "hdr",
                "penpot",
                "html",
                "md",
                "txt",
                "json",
                "csv",
                "ics",
                "vcf",
                "png"
              ]
            }
          },
          {
            "name": "width",
            "in": "query",
            "required": false,
            "description": "Output width in `unit` (alias `w`). Physical units convert at `dpi`; the result may not exceed 10000 px.",
            "schema": {
              "type": "number",
              "exclusiveMinimum": 0
            }
          },
          {
            "name": "height",
            "in": "query",
            "required": false,
            "description": "Output height in `unit` (alias `h`).",
            "schema": {
              "type": "number",
              "exclusiveMinimum": 0
            }
          },
          {
            "name": "unit",
            "in": "query",
            "required": false,
            "description": "The unit width and height are given in.",
            "schema": {
              "type": "string",
              "enum": [
                "px",
                "mm",
                "cm",
                "in",
                "pt"
              ],
              "default": "px"
            }
          },
          {
            "name": "dpi",
            "in": "query",
            "required": false,
            "description": "Raster resolution for physical units.",
            "schema": {
              "type": "number",
              "minimum": 1,
              "maximum": 1200,
              "default": 300
            }
          },
          {
            "name": "profile",
            "in": "query",
            "required": false,
            "description": "Colour profile: `srgb`, `none` or a CMYK press condition such as `fogra39`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "_v",
            "in": "query",
            "required": false,
            "description": "Pin the tool version so a tool update cannot change the output.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Locale for a tool that localises its output.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "z",
            "in": "query",
            "required": false,
            "description": "A packed query (the compressed form the app mints for long links). Expanded server-side before the other keys are read.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Headers as for GET, no body."
          },
          "304": {
            "description": "Not modified."
          },
          "400": {
            "description": "See GET."
          },
          "404": {
            "description": "See GET."
          },
          "429": {
            "description": "See GET."
          }
        }
      }
    },
    "/catalog/tools/index.json": {
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "listTools",
        "summary": "The generated tool index for this deployment",
        "responses": {
          "200": {
            "description": "Every tool with id, name, status, formats and version.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/tools/{id}/tool.json": {
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "getToolManifest",
        "summary": "One tool's manifest: inputs, defaults, formats, canvas size",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The tool id, as listed in /catalog/tools/index.json.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9-]*[a-z0-9]$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The manifest, validated against schemas/tool.schema.json.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "No such tool."
          }
        }
      }
    },
    "/info/capabilities.json": {
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "getCapabilities",
        "summary": "Format claims: reads, writes, round trips and metadata per format",
        "responses": {
          "200": {
            "description": "The claims file.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/lolly.json": {
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "getDiscovery",
        "summary": "This deployment's discovery record",
        "responses": {
          "200": {
            "description": "Where the catalog, manifests, render route, MCP endpoints and agent files live.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/mcp": {
      "post": {
        "tags": [
          "mcp"
        ],
        "operationId": "mcp",
        "summary": "MCP over Streamable HTTP (JSON-RPC 2.0, POST)",
        "description": "The browser-free tier. The full tier, with a headless browser behind it, is https://mcp.lolly.tools/mcp; same tools, same token. Tools: lolly_compile, lolly_inspect, lolly_measure, lolly_validate, lolly_diff, lolly_package, lolly_list_tools, lolly_describe_tool, lolly_build_url, lolly_render, lolly_transform, lolly_redact, lolly_verify. Resources: lolly://catalog, lolly://assets, lolly://tokens, lolly://tool/{id}, lolly://tool/{id}/preview, lolly://asset/{id}. Notifications are answered 202 with no body. Without a valid token the answer is 401 with a WWW-Authenticate header pointing at the protected-resource metadata.",
        "security": [
          {
            "bearer": []
          },
          {
            "oauth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC response.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "202": {
            "description": "A notification was accepted."
          },
          "401": {
            "description": "Missing or invalid token.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "MCP is not configured on this deployment."
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "tags": [
          "mcp"
        ],
        "operationId": "oauthServerMetadata",
        "summary": "OAuth 2.1 authorization server metadata (RFC 8414)",
        "responses": {
          "200": {
            "description": "Issuer, endpoints, PKCE S256, dynamic client registration.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "tags": [
          "mcp"
        ],
        "operationId": "oauthResourceMetadata",
        "summary": "OAuth protected resource metadata (RFC 9728)",
        "responses": {
          "200": {
            "description": "The resource and its authorization servers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "The instance access token, held by the operator."
      },
      "oauth": {
        "type": "oauth2",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://lolly.tools/api/mcp/authorize",
            "tokenUrl": "https://lolly.tools/api/mcp/token",
            "scopes": {}
          }
        },
        "description": "Stateless OAuth 2.1 with PKCE and dynamic client registration at /api/mcp/register."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "method": {
            "type": "string"
          },
          "params": {
            "type": "object"
          }
        }
      }
    }
  }
}
