{
  "openapi": "3.1.1",
  "info": {
    "title": "Movie Night Consensus Picker API",
    "version": "1.0.0",
    "summary": "Everyone ranks the films, vetoes count: Borda, approval, runoff and Condorcet decide.",
    "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://movienightconsensuspicker.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Everyone ranks the films, vetoes count: Borda, approval, runoff and Condorcet decide.",
        "description": "List the candidate films (or restaurants, games, holiday spots), then let each person rank them and veto the ones they cannot stand. The picker runs four voting methods — Borda count, approval, instant-runoff and Condorcet/Schulze — shows each winner and why, and breaks ties fairly with a seed.\n\nMethodology:\n- Each ballot is a ranking, best first; films a person leaves out count as tied below all films they ranked. Names are matched case-insensitively; unknown names are reported and skipped.\n- Vetoes: in “remove” mode every film vetoed by at least one person is struck before any counting (if that would remove all films, vetoes are ignored and a warning is shown).\n- Borda: with n films, 1st place earns n−1 points, 2nd n−2, … ; unranked films share the remaining points equally. Approval: each voter approves their top N films; most approvals wins. Instant-runoff (IRV): count first preferences, eliminate the film with the fewest, transfer those ballots to their next choice, repeat until one film has a majority of the ballots still in play.\n- Condorcet/Schulze: for every pair, count how many voters prefer each. A Condorcet winner beats every other film head-to-head. The Schulze method (strongest paths) always finds a winner and agrees with the Condorcet winner when one exists.\n- Ties are broken with a random order of the films drawn from the seed (sfc32 seeded with SHA-256), shown in the result, so the outcome is reproducible and nobody can claim favouritism. The recommendation is the Schulze winner.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "movie_night_consensus_picker",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MovieNightConsensusPickerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Five friends, four films, one veto",
                  "value": {
                    "candidates": [
                      "Dune: Part Two",
                      "Paddington 2",
                      "Alien",
                      "Amélie"
                    ],
                    "ballots": [
                      {
                        "voter": "Ana",
                        "ranking": "Dune: Part Two > Alien > Amélie > Paddington 2"
                      },
                      {
                        "voter": "Ben",
                        "ranking": "Paddington 2 > Amélie > Dune: Part Two",
                        "veto": "Alien"
                      },
                      {
                        "voter": "Cleo",
                        "ranking": "Amélie > Paddington 2 > Dune: Part Two > Alien"
                      },
                      {
                        "voter": "Dev",
                        "ranking": "Dune: Part Two > Paddington 2 > Amélie > Alien"
                      },
                      {
                        "voter": "Eli",
                        "ranking": "Paddington 2 > Dune: Part Two > Amélie > Alien"
                      }
                    ],
                    "vetoMode": "remove"
                  }
                },
                "example2": {
                  "summary": "Methods disagree (classic three-way split)",
                  "value": {
                    "candidates": [
                      "Action",
                      "Comedy",
                      "Drama"
                    ],
                    "ballots": [
                      {
                        "voter": "A1",
                        "ranking": "Action > Drama > Comedy"
                      },
                      {
                        "voter": "A2",
                        "ranking": "Action > Drama > Comedy"
                      },
                      {
                        "voter": "A3",
                        "ranking": "Action > Drama > Comedy"
                      },
                      {
                        "voter": "A4",
                        "ranking": "Action > Drama > Comedy"
                      },
                      {
                        "voter": "B1",
                        "ranking": "Comedy > Drama > Action"
                      },
                      {
                        "voter": "B2",
                        "ranking": "Comedy > Drama > Action"
                      },
                      {
                        "voter": "B3",
                        "ranking": "Comedy > Drama > Action"
                      },
                      {
                        "voter": "C1",
                        "ranking": "Drama > Comedy > Action"
                      },
                      {
                        "voter": "C2",
                        "ranking": "Drama > Comedy > Action"
                      }
                    ],
                    "vetoMode": "ignore",
                    "approvalCount": 1,
                    "seed": "popcorn"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MovieNightConsensusPickerResult"
                }
              }
            }
          },
          "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"
                }
              }
            }
          }
        }
      },
      "MovieNightConsensusPickerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "candidates": {
            "title": "Candidates (one per line)",
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 20
          },
          "ballots": {
            "title": "Ballots",
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "voter": {
                  "title": "Name",
                  "type": "string",
                  "maxLength": 40
                },
                "ranking": {
                  "title": "Ranking, best first",
                  "description": "Separate with “>” or commas. Unlisted films count as tied last.",
                  "type": "string",
                  "maxLength": 1000
                },
                "veto": {
                  "title": "Veto (optional)",
                  "description": "Films this person refuses to watch, separated by commas.",
                  "type": "string",
                  "maxLength": 400
                }
              },
              "required": [
                "ranking"
              ]
            }
          },
          "vetoMode": {
            "title": "Vetoes",
            "type": "string",
            "enum": [
              "remove",
              "ignore"
            ],
            "default": "remove"
          },
          "approvalCount": {
            "title": "Approval: top N of each ranking count as approved",
            "description": "Empty = half of the candidates, rounded up.",
            "type": "integer",
            "minimum": 1,
            "maximum": 20
          },
          "seed": {
            "title": "Tie-break seed (optional)",
            "description": "Ties are broken by a seeded random order of the candidates. Empty = derived from the inputs.",
            "type": "string",
            "maxLength": 60
          }
        },
        "required": [
          "candidates",
          "ballots"
        ]
      },
      "MovieNightConsensusPickerResult": {
        "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": [
              "seed",
              "candidates",
              "vetoed",
              "eligible",
              "voters",
              "methods",
              "condorcetWinner",
              "consensus",
              "recommendation",
              "borda",
              "approval",
              "irvRounds",
              "pairwise",
              "tieBreakOrder"
            ],
            "properties": {
              "seed": {
                "type": "string"
              },
              "candidates": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "vetoed": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "candidate",
                    "by"
                  ],
                  "properties": {
                    "candidate": {
                      "type": "string"
                    },
                    "by": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "eligible": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "voters": {
                "type": "integer"
              },
              "methods": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "method",
                    "winner",
                    "tieBroken",
                    "explanation"
                  ],
                  "properties": {
                    "method": {
                      "type": "string"
                    },
                    "winner": {
                      "type": "string"
                    },
                    "tieBroken": {
                      "type": "boolean"
                    },
                    "explanation": {
                      "type": "string"
                    }
                  }
                }
              },
              "condorcetWinner": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "consensus": {
                "type": "boolean",
                "description": "All four methods pick the same film."
              },
              "recommendation": {
                "type": "string"
              },
              "borda": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "candidate",
                    "points"
                  ],
                  "properties": {
                    "candidate": {
                      "type": "string"
                    },
                    "points": {
                      "type": "number"
                    }
                  }
                }
              },
              "approval": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "candidate",
                    "approvals"
                  ],
                  "properties": {
                    "candidate": {
                      "type": "string"
                    },
                    "approvals": {
                      "type": "integer"
                    }
                  }
                }
              },
              "irvRounds": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "round",
                    "counts",
                    "exhausted",
                    "eliminated",
                    "tieBreak"
                  ],
                  "properties": {
                    "round": {
                      "type": "integer"
                    },
                    "counts": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    },
                    "exhausted": {
                      "type": "integer"
                    },
                    "eliminated": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "tieBreak": {
                      "type": "boolean"
                    }
                  }
                }
              },
              "pairwise": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "a",
                    "b",
                    "aOverB",
                    "bOverA"
                  ],
                  "properties": {
                    "a": {
                      "type": "string"
                    },
                    "b": {
                      "type": "string"
                    },
                    "aOverB": {
                      "type": "integer"
                    },
                    "bOverA": {
                      "type": "integer"
                    }
                  }
                }
              },
              "tieBreakOrder": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "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"
              }
            }
          }
        }
      }
    }
  }
}