{
  "openapi": "3.1.1",
  "info": {
    "title": "Movie & Series Marathon Planner API",
    "version": "1.0.0",
    "summary": "Plan a binge: when each movie or episode starts and ends, total days, and a calendar.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://runtimemarathonplanner.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Plan a binge: when each movie or episode starts and ends, total days, and a calendar.",
        "description": "Add movies (runtime) and series (episodes × minutes × seasons), your breaks, when you start and how many hours a day you want to watch. The planner schedules every movie and episode, tells you how many days the marathon takes and whether you finish by a date, and exports the viewing sessions as an .ics calendar.\n\nMethodology:\n- Each movie is one item; each series becomes episodes × seasons items of the given length.\n- Items are watched in the order entered. Within a day, a break follows each item (the episode break inside a title, the title break between titles); breaks do not count towards the daily maximum.\n- If the next item would push the day over the maximum viewing time, it moves to the next day at the daily start time. With pausing allowed, an item is split instead when at least 15 minutes fit into the day.\n- An item longer than the daily maximum is scheduled in one piece (with a warning) unless pausing is allowed.\n- A viewing session belongs to the day it starts, even if it runs past midnight. Times are local wall-clock times without a time zone; the .ics file uses floating times (RFC 5545), so calendars show them at the same clock time wherever you are.\n- Finish-by check: the last item must end before midnight at the end of that date. Required hours per day = total watching time ÷ number of days from the start date to the finish-by date.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "runtime_marathon_planner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RuntimeMarathonPlannerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Lord-of-the-Rings-length trilogy over a weekend",
                  "value": {
                    "titles": [
                      {
                        "title": "Part 1",
                        "minutes": 178
                      },
                      {
                        "title": "Part 2",
                        "minutes": 179
                      },
                      {
                        "title": "Part 3",
                        "minutes": 201
                      }
                    ],
                    "start": "2026-10-23T19:00",
                    "dailyStart": "14:00",
                    "maxHoursPerDay": 6,
                    "breakMinutes": 10,
                    "titleBreakMinutes": 30,
                    "allowSplit": false,
                    "finishBy": "2026-10-25"
                  }
                },
                "example2": {
                  "summary": "A 5-season drama at 2 hours a night",
                  "value": {
                    "titles": [
                      {
                        "title": "Drama",
                        "minutes": 47,
                        "episodes": 10,
                        "seasons": 5
                      }
                    ],
                    "start": "2026-11-01T20:00",
                    "dailyStart": "20:00",
                    "maxHoursPerDay": 2,
                    "breakMinutes": 5,
                    "titleBreakMinutes": 20,
                    "allowSplit": false,
                    "finishBy": "2026-11-20"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RuntimeMarathonPlannerResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "RuntimeMarathonPlannerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "titles": {
            "title": "Movies and series",
            "description": "Movie: runtime in minutes. Series: minutes per episode, episodes per season and seasons.",
            "type": "array",
            "minItems": 1,
            "maxItems": 30,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "title": {
                  "title": "Title",
                  "type": "string",
                  "maxLength": 80
                },
                "minutes": {
                  "title": "Minutes (per episode)",
                  "type": "number",
                  "minimum": 1,
                  "maximum": 600
                },
                "episodes": {
                  "title": "Episodes per season",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 500,
                  "default": 1
                },
                "seasons": {
                  "title": "Seasons",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60,
                  "default": 1
                }
              },
              "required": [
                "title",
                "minutes"
              ]
            }
          },
          "start": {
            "title": "Start",
            "description": "When the marathon begins (local time). Local date-time YYYY-MM-DDTHH:mm",
            "type": "string"
          },
          "dailyStart": {
            "title": "On the following days, start at",
            "type": "string",
            "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
            "default": "18:00"
          },
          "maxHoursPerDay": {
            "title": "Maximum viewing per day",
            "description": "Watching time only; breaks come on top.",
            "type": "number",
            "minimum": 0.5,
            "maximum": 20,
            "default": 4,
            "x-unit": "h"
          },
          "breakMinutes": {
            "title": "Break between episodes",
            "type": "number",
            "minimum": 0,
            "maximum": 240,
            "default": 10,
            "x-unit": "min"
          },
          "titleBreakMinutes": {
            "title": "Break between titles",
            "type": "number",
            "minimum": 0,
            "maximum": 480,
            "default": 20,
            "x-unit": "min"
          },
          "allowSplit": {
            "title": "Allow pausing a movie or episode and continuing the next day",
            "type": "boolean",
            "default": false
          },
          "finishBy": {
            "title": "Finish by",
            "description": "Optional: checks whether you make it by the end of this day.",
            "type": "string",
            "format": "date"
          }
        },
        "required": [
          "titles",
          "start"
        ]
      },
      "RuntimeMarathonPlannerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "totalWatchMinutes",
              "totalBreakMinutes",
              "items",
              "days",
              "firstStart",
              "finish",
              "finishBy",
              "onTime",
              "requiredHoursPerDay",
              "titles",
              "perDay",
              "schedule",
              "sessions"
            ],
            "properties": {
              "totalWatchMinutes": {
                "type": "number"
              },
              "totalBreakMinutes": {
                "type": "number"
              },
              "items": {
                "type": "integer",
                "description": "Movies + episodes."
              },
              "days": {
                "type": "integer",
                "description": "Viewing days, counted from the start date to the last session."
              },
              "firstStart": {
                "type": "string"
              },
              "finish": {
                "type": "string",
                "description": "Local date-time the last item ends."
              },
              "finishBy": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "onTime": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "requiredHoursPerDay": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Viewing hours per day needed to finish by the date (watching time only)."
              },
              "titles": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "title",
                    "items",
                    "minutes",
                    "start",
                    "end"
                  ],
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "items": {
                      "type": "integer"
                    },
                    "minutes": {
                      "type": "number"
                    },
                    "start": {
                      "type": "string"
                    },
                    "end": {
                      "type": "string"
                    }
                  }
                }
              },
              "perDay": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "day",
                    "date",
                    "watchMinutes",
                    "from",
                    "to"
                  ],
                  "properties": {
                    "day": {
                      "type": "integer"
                    },
                    "date": {
                      "type": "string"
                    },
                    "watchMinutes": {
                      "type": "number"
                    },
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string"
                    }
                  }
                }
              },
              "schedule": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "title",
                    "label",
                    "part",
                    "day",
                    "date",
                    "start",
                    "end",
                    "minutes"
                  ],
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "part": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "day": {
                      "type": "integer"
                    },
                    "date": {
                      "type": "string"
                    },
                    "start": {
                      "type": "string"
                    },
                    "end": {
                      "type": "string"
                    },
                    "minutes": {
                      "type": "number"
                    }
                  }
                }
              },
              "sessions": {
                "type": "integer"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}