{
  "openapi": "3.0.3",
  "info": {
    "title": "UK EPC Energy Performance API",
    "version": "0.1.0",
    "description": "Per-address EPC lookups with retrofit cost estimate, band trajectory, and\nsuitability flags for heat pumps and solar.\n",
    "contact": {
      "name": "Stratum APIs",
      "url": "https://stratumapis.com",
      "email": "support@stratumapis.com"
    },
    "termsOfService": "https://stratumapis.com/legal/terms",
    "x-logo": {
      "url": "https://stratumapis.com/icon.svg",
      "altText": "Stratum APIs"
    }
  },
  "servers": [
    {
      "url": "https://api.stratumapis.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "StratumKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "StratumKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Stratum-Key"
      }
    },
    "schemas": {
      "EpcRecord": {
        "type": "object",
        "properties": {
          "certificateHash": {
            "type": "string",
            "description": "MHCLG-issued LMK key for the most recent certificate."
          },
          "address": {
            "type": "object",
            "properties": {
              "line1": {
                "type": "string",
                "nullable": true
              },
              "line2": {
                "type": "string",
                "nullable": true
              },
              "town": {
                "type": "string",
                "nullable": true
              },
              "postcode": {
                "type": "string"
              },
              "uprn": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "currentBand": {
            "type": "string",
            "enum": [
              "A",
              "B",
              "C",
              "D",
              "E",
              "F",
              "G"
            ]
          },
          "currentEnergyEfficiency": {
            "type": "integer",
            "description": "SAP score 0 to 100."
          },
          "potentialBand": {
            "type": "string",
            "enum": [
              "A",
              "B",
              "C",
              "D",
              "E",
              "F",
              "G"
            ]
          },
          "potentialEnergyEfficiency": {
            "type": "integer"
          },
          "propertyType": {
            "type": "string"
          },
          "builtForm": {
            "type": "string"
          },
          "floorAreaSqm": {
            "type": "number",
            "nullable": true
          },
          "constructionAgeBand": {
            "type": "string",
            "nullable": true
          },
          "inspectionDate": {
            "type": "string",
            "format": "date"
          },
          "lodgementDate": {
            "type": "string",
            "format": "date"
          },
          "certificateExpiresAt": {
            "type": "string",
            "format": "date"
          },
          "recommendations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "improvement": {
                  "type": "string"
                },
                "indicativeCost": {
                  "type": "object",
                  "properties": {
                    "low": {
                      "type": "integer",
                      "description": "Lower bound in pence"
                    },
                    "high": {
                      "type": "integer",
                      "description": "Upper bound in pence"
                    }
                  }
                },
                "typicalSavingPerYearPence": {
                  "type": "integer"
                },
                "improvedBand": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          },
          "retrofitCostEstimate": {
            "type": "object",
            "properties": {
              "toBandC": {
                "type": "object",
                "properties": {
                  "low": {
                    "type": "integer"
                  },
                  "high": {
                    "type": "integer"
                  },
                  "feasible": {
                    "type": "boolean"
                  }
                }
              },
              "toBandB": {
                "type": "object",
                "properties": {
                  "low": {
                    "type": "integer"
                  },
                  "high": {
                    "type": "integer"
                  },
                  "feasible": {
                    "type": "boolean"
                  }
                }
              }
            }
          },
          "suitability": {
            "type": "object",
            "description": "Heat pump and solar suitability. Not worked out yet (coming soon), so both are always `unknown`.",
            "properties": {
              "heatPump": {
                "type": "string",
                "enum": [
                  "likely",
                  "possible",
                  "unlikely",
                  "unknown"
                ]
              },
              "solarPv": {
                "type": "string",
                "enum": [
                  "likely",
                  "possible",
                  "unlikely",
                  "unknown"
                ]
              }
            }
          },
          "bandTrajectory": {
            "type": "array",
            "description": "Earlier certificates for the property, oldest first. Not filled yet (coming soon), so always an empty list.",
            "items": {
              "type": "object",
              "properties": {
                "lodgementDate": {
                  "type": "string",
                  "format": "date"
                },
                "band": {
                  "type": "string"
                },
                "currentEnergyEfficiency": {
                  "type": "integer"
                }
              }
            }
          },
          "meesVerdict": {
            "type": "object",
            "nullable": true,
            "description": "MEES (Minimum Energy Efficiency Standards) compliance verdict for\nthis property. Computed from the current EPC band + the in-force\nMEES rules at evaluation time. Defaults assume a `new` tenancy\n(the strictest interpretation — surfaces any future grace period\na landlord-on-let needs to plan for).\n",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "meets",
                  "fails",
                  "exempt",
                  "not-applicable"
                ],
                "description": "Headline verdict, drives the colour-coded badge in UI surfaces. 'not-applicable' means the property is in Scotland or Northern Ireland, outside the England and Wales MEES regime (no penalty applies)."
              },
              "effectiveUntil": {
                "type": "string",
                "format": "date",
                "nullable": true,
                "description": "Date the current verdict ceases to hold (when the next stricter rule kicks in, or when an exemption expires). Null if no future rule will affect the verdict."
              },
              "requiredAction": {
                "type": "string",
                "nullable": true,
                "description": "One-line action the landlord/letting agent needs to take. Null if no action needed."
              },
              "gracePeriodMonths": {
                "type": "integer",
                "nullable": true,
                "description": "Months between today and the next rule that would change status. Useful for \"you've got 18 months to retrofit\" UI prompts."
              },
              "ruleId": {
                "type": "string",
                "description": "ID of the MEES rule that produced this verdict (for example `dom-e-all-2020`). Stable across releases; quote it if you contact support."
              },
              "message": {
                "type": "string",
                "description": "Plain-English summary suitable for direct display next to the status badge. Tailored for letting-agent buyers (uses \"rented home\"/\"commercial property\" rather than \"domestic\"/\"non-domestic\")."
              },
              "paybackEstimate": {
                "type": "object",
                "nullable": true,
                "description": "Optional MEES upgrade-cost + payback estimate. Populated when the property\nfails MEES (status `fails`); omitted/null when the property already meets the\nminimum or no measure data is available. Pass `?currentAnnualRent=<gbp>` on\nthe request to enable the `paybackMonths` figure; without it the cost is\nstill computed but `paybackMonths` will be null.\n",
                "properties": {
                  "totalUpgradeCost": {
                    "type": "number",
                    "description": "Total upgrade cost in GBP across the selected measures. Zero if no upgrade needed."
                  },
                  "selectedMeasures": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Selected measure identifiers in priority order. Empty if no upgrade needed."
                  },
                  "rentUpliftPerYear": {
                    "type": "number",
                    "description": "Estimated annual rent uplift in GBP at the upgraded band. Zero when current >= target or rent unknown."
                  },
                  "estimatedAnnualSaving": {
                    "type": "number",
                    "description": "EPC-reported energy saving across selected measures (GBP/yr). Informational."
                  },
                  "paybackMonths": {
                    "type": "integer",
                    "nullable": true,
                    "description": "Payback period in months. Null when no upgrade needed or `currentAnnualRent` was not provided."
                  },
                  "rentSource": {
                    "type": "string",
                    "enum": [
                      "provided",
                      "estimated",
                      "unknown"
                    ],
                    "description": "Where the rent number used for the calculation came from."
                  },
                  "notes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "One-line notes for the UI (already-compliant, high-cost-warning, payback-exceeds-10y)."
                  }
                }
              }
            }
          },
          "retrievedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PostcodeAllResponse": {
        "type": "object",
        "description": "Multi-property response returned by `GET /v1/epc/postcode/{postcode}/all` —\nall EPC certificates at a postcode (typical converted block has 5–25),\neach with its own meesVerdict, plus a postcode-level summary.\n",
        "properties": {
          "postcode": {
            "type": "string"
          },
          "propertyCount": {
            "type": "integer"
          },
          "properties": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EpcRecord"
            }
          },
          "meesSummary": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer"
              },
              "meets": {
                "type": "integer"
              },
              "fails": {
                "type": "integer"
              },
              "exempt": {
                "type": "integer"
              },
              "unknown": {
                "type": "integer"
              },
              "headline": {
                "type": "string",
                "description": "One-line aggregate phrasing for portfolio-overview UI (e.g. \"3 of 7 properties at this postcode fail MEES — review before listing\")."
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "requestId": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/v1/epc/address/{postcode}": {
      "get": {
        "summary": "Get the most recent EPC certificate for an address by postcode (latest match)",
        "operationId": "getEpcByPostcode",
        "parameters": [
          {
            "name": "postcode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "NW98SU"
          },
          {
            "name": "line1",
            "in": "query",
            "required": false,
            "description": "Filter by first address line (optional but recommended for multi-property postcodes)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currentAnnualRent",
            "in": "query",
            "required": false,
            "description": "Current annual rent in GBP. With it, `meesVerdict.paybackEstimate.paybackMonths`\nis worked out assuming the rent rises 5% for each EPC band the upgrade\nmoves. Without it, the upgrade cost is still given and `paybackMonths`\nis null.\n",
            "schema": {
              "type": "number",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "EPC record",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EpcRecord"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "No certificate found for that address"
          }
        }
      }
    },
    "/v1/epc/uprn/{uprn}": {
      "get": {
        "summary": "Get EPC by UPRN (unique property reference number)",
        "operationId": "getEpcByUprn",
        "parameters": [
          {
            "name": "uprn",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "EPC record",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EpcRecord"
                }
              }
            }
          },
          "404": {
            "description": "No certificate found for that UPRN"
          }
        }
      }
    },
    "/v1/epc/postcode/{postcode}/all": {
      "get": {
        "summary": "All EPC certificates at a postcode, each with its own MEES verdict",
        "operationId": "getEpcsByPostcode",
        "description": "Returns every EPC certificate registered at the given postcode (typical\nconverted block has 5–25), each with its own MEES verdict, plus a\npostcode-level `meesSummary` aggregate intended for portfolio-overview\nUIs.\n",
        "parameters": [
          {
            "name": "postcode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "All properties at the postcode with per-property MEES verdicts + aggregate summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostcodeAllResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/epc/postcode/{postcode}/aggregate": {
      "get": {
        "summary": "Postcode-level band distribution (coming soon; answers 501 until then)",
        "operationId": "getPostcodeAggregate",
        "parameters": [
          {
            "name": "postcode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Aggregated band counts and average improvement potential",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "postcode": {
                      "type": "string"
                    },
                    "certificatesCounted": {
                      "type": "integer"
                    },
                    "bandDistribution": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    },
                    "averageCurrentEfficiency": {
                      "type": "number"
                    },
                    "averagePotentialEfficiency": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Aggregate endpoint requires Pro tier"
          }
        }
      }
    }
  }
}