{
  "openapi": "3.1.0",
  "info": {
    "title": "Psephology.AI election data API",
    "version": "1.0.0",
    "description": "UK election results, candidates, areas and insights. Data: Psephology.ai (psephology.ai). Includes data from Democracy Club (CC BY 4.0) and other public sources."
  },
  "servers": [
    {
      "url": "https://psephology.ai/api/public/v1"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key"
      }
    }
  },
  "paths": {
    "/elections": {
      "get": {
        "summary": "List elections",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "year",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/elections/{slug}": {
      "get": {
        "summary": "Election detail with party totals",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/elections/{slug}/results": {
      "get": {
        "summary": "Result rows",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "council",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ward",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/areas": {
      "get": {
        "summary": "Search areas",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/areas/{slug}": {
      "get": {
        "summary": "Area with parent and children",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/areas/{slug}/history": {
      "get": {
        "summary": "Every contest in an area",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/candidates": {
      "get": {
        "summary": "Search candidates",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/candidates/{slug}": {
      "get": {
        "summary": "Candidate record (person-level on Starter+)",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/parties": {
      "get": {
        "summary": "List parties",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/parties/{slug}": {
      "get": {
        "summary": "Party record",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/insights/local-election-proxy/{constituency}": {
      "get": {
        "summary": "Local election proxy",
        "parameters": [
          {
            "name": "constituency",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/insights/bucking-the-trend/{council}": {
      "get": {
        "summary": "Bucking the trend",
        "parameters": [
          {
            "name": "council",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/insights/byelections": {
      "get": {
        "summary": "By-election tracker",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    },
    "/boundaries/{area}": {
      "get": {
        "summary": "Current boundary GeoJSON (Starter+)",
        "parameters": [
          {
            "name": "area",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Body has `data`, `credit` and, for lists, `next_cursor`."
          },
          "401": {
            "description": "Missing or revoked key"
          },
          "429": {
            "description": "Over a rate limit"
          }
        }
      }
    }
  }
}