{
  "openapi": "3.1.0",
  "info": {
    "title": "Slidler carousel API",
    "version": "1",
    "description": "Exactly one of text or url; title is text-only. Unknown and duplicate parameters are rejected. Caps: text 1800 decoded UTF-8 bytes, url 2048 decoded UTF-8 bytes, title 100 decoded UTF-8 bytes; encoded request target 7000 bytes. Text requires 40 printable non-whitespace characters. Signed decimal integer n is clamped to 6..8; non-integer n defaults to 6. The response reports effective n. n counts point slides; cover and CTA are additional; short sources may yield fewer points. Branding is always on: \"made with slidler.com\" on every PDF page; no removal parameter. PDFs expire after 900 seconds; downloads do not extend expiry. Eviction or restart can remove them sooner; recreate missing PDFs. Cache caps: 256 files, 33554432 bytes total, 262144 bytes per PDF. Rendering caps: 10 pages, 40000 text characters, four concurrent processing slots. PDF uses Helvetica for WinAnsi text and embeds a Liberation Sans subset when text has other Latin, Greek or Cyrillic letters (e.g. Vučić, Łódź); smart quotes, dashes and ellipses normalize for PDF only; glyphs the font lacks are transliterated or dropped (e.g. emoji); control characters return unsupported_pdf_text (422). JSON preserves source characters and omits full source text. All API JSON and PDF responses use Cache-Control: no-store. Pasted text is sent to the server in the GET query and may appear in browser history or intermediary logs.\n\nAPI and MCP creation share a per-IP budget: 10 requests/minute, burst 20 (21 initial Python tokens), 2 concurrent API connections/IP. Extract retains a separate 1/minute budget, burst 3 (four Python tokens), two connections/IP. PDF connections have their own zone. API creation 429 responses include Retry-After: 6; global saturation returns 503. PDF downloads have a separate 60 requests/minute budget, burst 20. Proxy-generated limit/error responses may differ from the application JSON error schema and do not guarantee Retry-After. MCP transport has a separate nginx allowance of 10 requests/minute, burst 20, four concurrent connections/IP; only creation spends the shared Python creation budget. Sensitive nginx locations use query-free access logs and location error_log at crit; request/connection limit messages use warn and are suppressed. Ordinary oversized and upstream-failure request diagnostics are suppressed too.\n\nBrowser CORS on /mcp and /api/v1/carousel only: Access-Control-Allow-Origin *, without credentials. Methods: POST, OPTIONS for MCP; GET, HEAD, OPTIONS for carousel. Allowed headers: Content-Type, Accept, Mcp-Session-Id, MCP-Protocol-Version; exposed: Mcp-Session-Id. Preflight Max-Age 600 seconds; nginx answers 204 after a separate 60/minute, burst 20 limit. Host is validated; optional Origin accepts any well-formed HTTP(S) origin; malformed, multiple or null origins return 400. MCP knowingly relaxes its Origin MUST to syntax validation for this public, auth-less, cookie-less service."
  },
  "servers": [
    {
      "url": "https://slidler.com"
    }
  ],
  "paths": {
    "/mcp": {
      "post": {
        "operationId": "mcp",
        "summary": "Remote MCP Streamable HTTP (JSON only)",
        "description": "initialize, notifications/initialized, ping, tools/list and tools/call (create_carousel). Clients must send Accept: application/json, text/event-stream. Missing Accept is allowed; Accept excluding application/json returns 406. No sessions or SSE. Protocol 2025-06-18; also accepts 2025-03-26. Host is validated; optional Origin accepts any well-formed HTTP(S) origin (malformed/multiple/null: 400). tools/call returns structuredContent matching Carousel and text with the absolute PDF link; processing errors use isError. GET/DELETE return 405.",
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "description": "Clients must send Accept: application/json, text/event-stream. Missing Accept is allowed; Accept excluding application/json returns 406.",
            "schema": {
              "type": "string"
            },
            "example": "application/json, text/event-stream"
          },
          {
            "name": "MCP-Protocol-Version",
            "in": "header",
            "schema": {
              "enum": [
                "2025-06-18",
                "2025-03-26"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jsonrpc",
                  "method"
                ],
                "properties": {
                  "jsonrpc": {
                    "const": "2.0"
                  },
                  "id": {
                    "type": [
                      "string",
                      "integer"
                    ]
                  },
                  "method": {
                    "type": "string"
                  },
                  "params": {
                    "type": "object"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response; tool input schema in x-mcp."
          },
          "202": {
            "description": "Initialized notification accepted; empty body."
          },
          "400": {
            "description": "Invalid JSON, envelope, parameters or protocol version."
          },
          "403": {
            "description": "Untrusted Host."
          },
          "406": {
            "description": "Accept excludes application/json."
          },
          "413": {
            "description": "Body exceeds 16 KiB."
          },
          "415": {
            "description": "Content-Type must be application/json."
          },
          "429": {
            "description": "MCP transport proxy budget exhausted."
          }
        }
      }
    },
    "/api/v1/carousel": {
      "get": {
        "operationId": "createCarousel",
        "summary": "Create a branded carousel and short-lived PDF",
        "description": "Exactly one of text or url is required; title is text-only. Input is validated before fetching.",
        "parameters": [
          {
            "name": "text",
            "in": "query",
            "required": false,
            "description": "Source text; at least 40 printable non-whitespace characters. Maximum 1800 decoded UTF-8 bytes (not character count).",
            "schema": {
              "type": "string",
              "x-max-utf8-bytes": 1800
            },
            "example": "Good carousels share one idea per slide. Keep each sentence short and concrete. End with a useful action for your reader."
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "Public HTTP(S) source URL; guarded fetching and redirect validation. Maximum 2048 decoded UTF-8 bytes (not character count).",
            "schema": {
              "type": "string",
              "x-max-utf8-bytes": 2048
            },
            "example": "https://example.com/article"
          },
          {
            "name": "title",
            "in": "query",
            "required": false,
            "description": "Optional title, allowed only with text. Maximum 100 decoded UTF-8 bytes (not character count).",
            "schema": {
              "type": "string",
              "x-max-utf8-bytes": 100
            },
            "example": "Better carousels"
          },
          {
            "name": "n",
            "in": "query",
            "required": false,
            "description": "Requested point slides, plus cover and CTA; actual point_count may be smaller. Signed decimal integer n is clamped to 6..8; non-integer n defaults to 6. The response reports effective n.",
            "schema": {
              "type": "string",
              "default": "6"
            },
            "example": "6"
          }
        ],
        "x-example-request": "https://slidler.com/api/v1/carousel?text=Good+carousels+share+one+idea+per+slide.+Keep+each+sentence+short+and+concrete.+End+with+a+useful+action+for+your+reader.&title=Better+carousels&n=6",
        "responses": {
          "200": {
            "description": "Completed carousel and matching PDF; n is the effective requested point count.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Carousel"
                }
              }
            }
          },
          "400": {
            "description": "invalid_input: Invalid, conflicting, unknown, duplicate or oversized input; unreadable source. ",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_input": {
                    "value": {
                      "code": "invalid_input",
                      "message": "Invalid, conflicting, unknown, duplicate or oversized input; unreadable source."
                    }
                  }
                }
              }
            }
          },
          "414": {
            "description": "uri_too_long: Encoded request target exceeds the byte cap. ",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "uri_too_long": {
                    "value": {
                      "code": "uri_too_long",
                      "message": "Encoded request target exceeds the byte cap."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "unsupported_pdf_text: Text contains unsupported control characters. layout_error: Text cannot fit, or PDF rendering cap exceeded. ",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "unsupported_pdf_text": {
                    "value": {
                      "code": "unsupported_pdf_text",
                      "message": "Text contains unsupported control characters."
                    }
                  },
                  "layout_error": {
                    "value": {
                      "code": "layout_error",
                      "message": "Text cannot fit, or PDF rendering cap exceeded."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Shared per-IP creation budget exhausted. ",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              },
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "const": 6
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "code": "rate_limited",
                      "message": "Shared per-IP creation budget exhausted."
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "upstream_failure: Source or storage could not be processed. ",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "upstream_failure": {
                    "value": {
                      "code": "upstream_failure",
                      "message": "Source or storage could not be processed."
                    }
                  }
                }
              }
            }
          },
          "504": {
            "description": "timeout: Request deadline exceeded. ",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "timeout": {
                    "value": {
                      "code": "timeout",
                      "message": "Request deadline exceeded."
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "busy: Global processing capacity exhausted. unavailable: PDF storage or export accounting unavailable. ",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "busy": {
                    "value": {
                      "code": "busy",
                      "message": "Global processing capacity exhausted."
                    }
                  },
                  "unavailable": {
                    "value": {
                      "code": "unavailable",
                      "message": "PDF storage or export accounting unavailable."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/pdf/{token}.pdf": {
      "get": {
        "operationId": "downloadPDF",
        "summary": "Download a completed PDF before expiry",
        "description": "No query parameters. TTL 900 seconds; missing, evicted or expired PDFs return 404.",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{48}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Branded vector PDF.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "not_found: PDF missing or expired. ",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "not_found": {
                    "value": {
                      "code": "not_found",
                      "message": "PDF missing or expired."
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "unavailable: PDF storage or export accounting unavailable. ",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "unavailable": {
                    "value": {
                      "code": "unavailable",
                      "message": "PDF storage or export accounting unavailable."
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "x-mcp": {
    "endpoint": "https://slidler.com/mcp",
    "transport": "Streamable HTTP",
    "protocolVersion": "2025-06-18",
    "acceptedVersions": [
      "2025-06-18",
      "2025-03-26"
    ],
    "inputSchema": {
      "type": "object",
      "required": [
        "source"
      ],
      "additionalProperties": false,
      "properties": {
        "source": {
          "type": "string",
          "description": "Source text; at least 40 printable non-whitespace characters. Or a public HTTP(S) URL. Text cap: 1800 UTF-8 bytes; URL cap: 2048 UTF-8 bytes."
        },
        "title": {
          "type": "string",
          "description": "Optional title, allowed only with text.",
          "x-max-utf8-bytes": 100
        },
        "n": {
          "type": "integer",
          "default": 6,
          "description": "Signed decimal integer n is clamped to 6..8; non-integer n defaults to 6. The response reports effective n."
        }
      }
    },
    "tool": "create_carousel",
    "maxBodyBytes": 16384,
    "responses": "JSON only; structuredContent is Carousel, text contains absolute PDF URL; no sessions or SSE"
  },
  "components": {
    "schemas": {
      "Carousel": {
        "type": "object",
        "properties": {
          "api_version": {
            "const": "1"
          },
          "renderer_version": {
            "const": "helvetica-3"
          },
          "title": {
            "type": "string"
          },
          "kind": {
            "enum": [
              "text",
              "url",
              "youtube-transcript",
              "youtube-description"
            ]
          },
          "slides": {
            "type": "array",
            "minItems": 2,
            "maxItems": 10,
            "items": {
              "type": "object",
              "properties": {
                "k": {
                  "enum": [
                    "cover",
                    "point",
                    "cta"
                  ]
                },
                "h": {
                  "type": "string"
                },
                "b": {
                  "type": "string"
                }
              },
              "required": [
                "k",
                "h",
                "b"
              ],
              "additionalProperties": false
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "caption_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "n": {
            "type": "integer",
            "minimum": 6,
            "maximum": 8
          },
          "point_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8
          },
          "page_count": {
            "type": "integer",
            "minimum": 2,
            "maximum": 10
          },
          "pdf": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              },
              "expires_at": {
                "type": "string",
                "format": "date-time"
              },
              "mime_type": {
                "const": "application/pdf"
              },
              "byte_length": {
                "type": "integer",
                "minimum": 1,
                "maximum": 262144
              }
            },
            "required": [
              "url",
              "expires_at",
              "mime_type",
              "byte_length"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "api_version",
          "renderer_version",
          "title",
          "kind",
          "slides",
          "warnings",
          "caption_status",
          "n",
          "point_count",
          "page_count",
          "pdf"
        ],
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "invalid_input",
              "not_found",
              "uri_too_long",
              "unsupported_pdf_text",
              "layout_error",
              "rate_limited",
              "upstream_failure",
              "timeout",
              "busy",
              "unavailable"
            ]
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "code",
          "message"
        ],
        "additionalProperties": false
      }
    }
  }
}
