{
  "openapi": "3.0.3",
  "info": {
    "title": "PowerGridIQ Rating API",
    "version": "1.0.0",
    "description": "Proprietary ratings and decisions for siting or scheduling a large electricity load across global power markets. Agent-friendly: JSON, CORS-open, self-describing. Free tier: 100 req/day per IP, no key. Higher monthly quotas with an API key sent as Authorization: Bearer pk_live_... Responses carry X-PGIQ-RateLimit-* headers; over quota returns 429.",
    "contact": {
      "url": "https://powergridiq.com"
    }
  },
  "servers": [
    {
      "url": "https://powergridiq.com"
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "pk_live",
        "description": "API key issued by PowerGridIQ; raises the rate limit above the free tier."
      }
    },
    "schemas": {
      "FieldProvenance": {
        "type": "object",
        "description": "Provenance for a single metric. A live demand does not make the price live.",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "live",
              "recent",
              "derived_current",
              "structural",
              "modelled",
              "temporarily_unavailable",
              "unavailable"
            ]
          },
          "observation_time": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the value was observed. Null unless state is live, recent or derived_current."
          },
          "data_vintage": {
            "type": [
              "string",
              "null"
            ],
            "description": "Vintage of the structural baseline. Null when the value is an observation."
          },
          "note": {
            "type": "string"
          }
        },
        "required": [
          "state"
        ]
      },
      "GridSnapshot": {
        "type": "object",
        "properties": {
          "market_id": {
            "type": "string",
            "description": "Canonical public market slug. Prefer this over any house code."
          },
          "as_of": {
            "type": "string",
            "description": "Retained for compatibility. Read as_of_type to know what it means."
          },
          "as_of_type": {
            "type": "string",
            "enum": [
              "observation_time",
              "data_vintage"
            ]
          },
          "observation_time": {
            "type": [
              "string",
              "null"
            ]
          },
          "data_vintage": {
            "type": [
              "string",
              "null"
            ]
          },
          "retrieved_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "rating_vintage": {
            "type": [
              "string",
              "null"
            ]
          },
          "record_state": {
            "type": "string",
            "enum": [
              "mixed",
              "structural"
            ],
            "description": "Conservatively derived. Never live: no market has every field live."
          },
          "price_usd_mwh": {
            "type": [
              "number",
              "null"
            ],
            "description": "ALWAYS modelled. There is no live price feed for any market."
          },
          "field_provenance": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/FieldProvenance"
            }
          }
        }
      },
      "PriceIndexWindow": {
        "type": "object",
        "description": "An n-day window. The LEVEL and the COMPARISON are separate availability questions: a gap in the preceding period does not make this period's average unknown.",
        "properties": {
          "days": {
            "type": "integer",
            "description": "Window length, 7 or 28"
          },
          "status": {
            "type": "string",
            "enum": [
              "current",
              "fallback",
              "building"
            ],
            "description": "current: the window ending on the latest complete day. fallback: an earlier complete window, shown with its own dates. building: no complete window exists yet."
          },
          "is_current": {
            "type": "boolean",
            "description": "False when a dated fallback is shown. Never present a fallback as the current period."
          },
          "level_available": {
            "type": "boolean"
          },
          "comparison_available": {
            "type": "boolean"
          },
          "available": {
            "type": "boolean",
            "description": "Legacy compatibility: true only when the window is current AND has a valid comparison."
          },
          "period": {
            "type": "object",
            "nullable": true,
            "properties": {
              "start": {
                "type": "string",
                "format": "date"
              },
              "end": {
                "type": "string",
                "format": "date"
              },
              "mean_level": {
                "type": "number"
              },
              "mean_basket_eur_mwh": {
                "type": "number"
              }
            }
          },
          "comparison": {
            "type": "object",
            "nullable": true,
            "properties": {
              "start": {
                "type": "string",
                "format": "date"
              },
              "end": {
                "type": "string",
                "format": "date"
              },
              "mean_level": {
                "type": "number"
              },
              "mean_basket_eur_mwh": {
                "type": "number"
              }
            }
          },
          "change_pct": {
            "type": "number",
            "nullable": true
          },
          "basket_change_pct": {
            "type": "number",
            "nullable": true,
            "description": "Identical to change_pct by construction; published so it can be checked."
          },
          "blocking_gap": {
            "type": "object",
            "nullable": true,
            "properties": {
              "day": {
                "type": "string",
                "format": "date"
              },
              "missing": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "building": {
            "type": "object",
            "nullable": true,
            "properties": {
              "consecutive_complete": {
                "type": "integer"
              },
              "required": {
                "type": "integer"
              },
              "since_gap": {
                "type": "string",
                "nullable": true
              },
              "text": {
                "type": "string"
              }
            }
          }
        }
      },
      "RealizedCostComponents": {
        "type": "object",
        "description": "Delivered-cost breakdown for the declared 100 MW / 95% load-factor case. Read component_status.evidence_reconciliation first. Where it is not_independently_reconciled the BAND is the compiled result and the lines are an estimated allocation of its midpoint, not separately observed figures. Where it is independently_reconciled each line was read from its own named source and the total is their sum. A sourced line may still be bundled: category_coverage reports which categories share a line and whether that line is sourced.",
        "properties": {
          "cost_components": {
            "type": "array",
            "description": "Component lines. Read provenance.provenance_state (provenance-state-1.0) before describing any line: directly_observed, tariff_derived, estimated_allocation or unavailable. A tariff_derived line is CALCULATED from filed rates by the published formula it carries; it is not a figure the utility states for this case. evidence and provenance.confidence are derived from the state and cannot contradict it. Where evidence is independently_corroborated the value is still an allocation and provenance.corroborated_by names the figure that agrees with it.",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "usd_per_mwh": {
                  "type": "number"
                },
                "evidence": {
                  "type": "string",
                  "enum": [
                    "estimated_allocation",
                    "independently_sourced"
                  ],
                  "description": "estimated_allocation means this line apportions the compiled midpoint. It is NOT a measurement and must not be reported as one."
                },
                "source": {
                  "type": "string",
                  "nullable": true,
                  "description": "Present only when the line is independently sourced."
                }
              }
            }
          },
          "component_status": {
            "type": "object",
            "description": "Two separate questions. Never merge them.",
            "properties": {
              "arithmetic_integrity": {
                "type": "boolean",
                "description": "The printed lines sum to the compiled midpoint. A hard build gate. This is NOT evidence: an allocation always sums to its own anchor."
              },
              "evidence_reconciliation": {
                "type": "string",
                "enum": [
                  "independently_reconciled",
                  "partially_reconciled",
                  "not_independently_reconciled"
                ],
                "description": "Whether the lines were separately observed and then agree with the compiled band. Cannot be satisfied by arithmetic. not_independently_reconciled in every market today."
              },
              "independently_sourced_lines": {
                "type": "integer"
              },
              "total_lines": {
                "type": "integer"
              },
              "bundled": {
                "type": "array",
                "description": "Lines combining more than one category, with what they combine.",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "combines": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              },
              "text": {
                "type": "string"
              },
              "method_version": {
                "type": "string",
                "description": "e.g. cost-components-1.0"
              }
            }
          },
          "component_source": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Source of the compiled band the allocation is drawn from."
          }
        }
      },
      "PriceIndexDay": {
        "type": "object",
        "properties": {
          "day": {
            "type": "string",
            "format": "date"
          },
          "level": {
            "type": "number"
          },
          "basket_eur_mwh": {
            "type": "number"
          }
        }
      },
      "PriceIndexSignal": {
        "type": "object",
        "description": "Plain-language reading of the numbers in this payload. Generated from the same window logic; adds no new measurement and can only describe what is already here.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "current",
              "latest_complete_fallback",
              "level_only",
              "building",
              "unavailable"
            ],
            "description": "current = a complete current week with a complete week before it. latest_complete_fallback = an OLDER complete week, shown with its own dates; never describe it as current. level_only = this week's average is known but the comparison is not. building = no complete week exists yet. unavailable = no level at all."
          },
          "direction": {
            "type": "string",
            "nullable": true,
            "enum": [
              "up",
              "down",
              "flat",
              null
            ],
            "description": "flat when the rounded change is under 0.05 per cent, so the word can never contradict the printed figure."
          },
          "headline": {
            "type": "string",
            "description": "Complete sentence, e.g. 'In the latest comparable week, European wholesale prices eased 9.4%.'"
          },
          "headline_display": {
            "type": "string",
            "description": "Same sentence without the terminal full stop, for use as a display heading."
          },
          "period": {
            "type": "object",
            "nullable": true,
            "description": "The week the headline describes. NOT necessarily the most recent seven days.",
            "properties": {
              "start": {
                "type": "string",
                "format": "date"
              },
              "end": {
                "type": "string",
                "format": "date"
              }
            }
          },
          "comparison_period": {
            "type": "object",
            "nullable": true,
            "properties": {
              "start": {
                "type": "string",
                "format": "date"
              },
              "end": {
                "type": "string",
                "format": "date"
              }
            }
          },
          "mean_basket_eur_mwh": {
            "type": "number",
            "nullable": true,
            "description": "Mean of the 23-zone basket over `period`, EUR/MWh."
          },
          "mean_index": {
            "type": "number",
            "nullable": true
          },
          "comparison_change_pct": {
            "type": "number",
            "nullable": true,
            "description": "Percentage change against the preceding seven days. Identical for index and basket by construction."
          },
          "relative_to_base_pct": {
            "type": "number",
            "nullable": true,
            "description": "How far the period sat above or below the July 2026 baseline, in per cent. Equals mean_index - 100."
          },
          "coverage": {
            "type": "string",
            "description": "e.g. '23 of 23 zones'"
          },
          "is_current": {
            "type": "boolean",
            "description": "False means the figure describes an older week and newer daily data exists. Do not report it as the latest week."
          },
          "basis_text": {
            "type": "string",
            "nullable": true,
            "description": "The euros behind the headline, in a sentence."
          },
          "index_text": {
            "type": "string",
            "nullable": true,
            "description": "What the index number means against July 2026, in a sentence."
          },
          "latest_daily_context": {
            "type": "object",
            "nullable": true,
            "description": "Present when complete daily observations exist that are NEWER than `period`. This is how an agent avoids reporting an older weekly move as current conditions.",
            "properties": {
              "date": {
                "type": "string",
                "format": "date"
              },
              "basket_eur_mwh": {
                "type": "number"
              },
              "index": {
                "type": "number"
              },
              "vs_period_direction": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "higher",
                  "lower",
                  "similar",
                  null
                ]
              },
              "vs_period_pct": {
                "type": "number"
              },
              "text": {
                "type": "string"
              }
            }
          },
          "blocking_gap": {
            "type": "object",
            "nullable": true,
            "description": "Why a current comparison is unavailable.",
            "properties": {
              "date": {
                "type": "string",
                "format": "date"
              },
              "constituents": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "what_it_means": {
            "type": "string",
            "description": "Measured interpretation only. Never asserts a cause; a cause requires a published commentary note with sources."
          },
          "use": {
            "type": "string"
          },
          "limitation": {
            "type": "string",
            "description": "What cannot be inferred. Always states that this is not a delivered cost."
          }
        }
      },
      "PriceIndex": {
        "type": "object",
        "properties": {
          "signal": {
            "$ref": "#/components/schemas/PriceIndexSignal"
          },
          "name": {
            "type": "string"
          },
          "schema_version": {
            "type": "string"
          },
          "index_method_version": {
            "type": "string",
            "description": "e.g. index-basket-normalised-1.1"
          },
          "method_version": {
            "type": "string",
            "description": "Delivery-day capture method, e.g. market-delivery-day-2.0"
          },
          "formula": {
            "type": "string"
          },
          "superseded_methods": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "recording": {
            "type": "boolean",
            "description": "True while the base period is incomplete; no level is published."
          },
          "as_of": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "level": {
            "type": "number",
            "nullable": true
          },
          "basket_eur_mwh": {
            "type": "number",
            "nullable": true
          },
          "base": {
            "type": "object",
            "properties": {
              "period_version": {
                "type": "string"
              },
              "month": {
                "type": "string"
              },
              "value": {
                "type": "number"
              },
              "basket_eur_mwh": {
                "type": "number",
                "nullable": true
              },
              "dates": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "date"
                }
              },
              "days_required": {
                "type": "integer"
              },
              "days_complete": {
                "type": "integer"
              },
              "is_complete": {
                "type": "boolean"
              },
              "excluded_dates": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "date": {
                      "type": "string"
                    },
                    "constituent": {
                      "type": "string"
                    },
                    "reason": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "constituents": {
            "type": "object",
            "properties": {
              "universe": {
                "type": "integer"
              },
              "required_for_publication": {
                "type": "integer"
              },
              "zones": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "weights": {
            "type": "object",
            "properties": {
              "scheme": {
                "type": "string"
              },
              "per_constituent": {
                "type": "number"
              },
              "sums_to": {
                "type": "number"
              },
              "basis": {
                "type": "string"
              }
            }
          },
          "coverage": {
            "type": "object",
            "properties": {
              "days_observed": {
                "type": "integer"
              },
              "days_complete": {
                "type": "integer"
              },
              "first_day": {
                "type": "string",
                "nullable": true
              },
              "last_day": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "series": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/PriceIndexDay"
            }
          },
          "constituent_reliability": {
            "type": "object",
            "nullable": true,
            "description": "Which constituents cost the index usable days. days_solely_blocking counts delivery days a single missing zone alone prevented from completing, which is the number a coverage decision turns on; a zone missing alongside nine others on an outage day is a different problem. Not a judgement about any operator or exchange.",
            "properties": {
              "incomplete_days": {
                "type": "integer"
              },
              "days_blocked_by_a_single_zone": {
                "type": "integer"
              },
              "by_zone": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "zone": {
                      "type": "string"
                    },
                    "days_missing": {
                      "type": "integer"
                    },
                    "days_solely_blocking": {
                      "type": "integer"
                    }
                  }
                }
              },
              "rule": {
                "type": "string"
              },
              "why_the_rule_stands": {
                "type": "string"
              },
              "what_this_is_not": {
                "type": "string"
              }
            }
          },
          "change_1d": {
            "type": "object",
            "nullable": true,
            "description": "Step between the last two COMPLETE delivery days. Not a market-direction signal: day-ahead power has a weekly shape, so a weekday-to-weekend step is the calendar rather than the market. Read comparability before quoting level_pct, and note that days_apart may exceed 1 when a delivery day was skipped for incomplete coverage.",
            "properties": {
              "from": {
                "type": "string"
              },
              "to": {
                "type": "string"
              },
              "level_pct": {
                "type": "number"
              },
              "basket_pct": {
                "type": "number"
              },
              "identical_by_construction": {
                "type": "boolean"
              },
              "days_apart": {
                "type": "integer"
              },
              "is_adjacent": {
                "type": "boolean"
              },
              "from_day_type": {
                "type": "string",
                "enum": [
                  "weekday",
                  "weekend"
                ]
              },
              "to_day_type": {
                "type": "string",
                "enum": [
                  "weekday",
                  "weekend"
                ]
              },
              "same_day_type": {
                "type": "boolean"
              },
              "comparability": {
                "type": "string",
                "enum": [
                  "like_for_like",
                  "same_day_type_across_a_gap",
                  "weekday_weekend_step"
                ]
              },
              "do_not_report_as": {
                "type": "string"
              }
            }
          },
          "window_7d": {
            "$ref": "#/components/schemas/PriceIndexWindow"
          },
          "window_28d": {
            "$ref": "#/components/schemas/PriceIndexWindow"
          },
          "source_gaps": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "day": {
                  "type": "string",
                  "format": "date"
                },
                "have": {
                  "type": "integer"
                },
                "missing": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "latest_note": {
            "type": "object",
            "nullable": true,
            "description": "Null until commentary is published.",
            "properties": {
              "id": {
                "type": "string"
              },
              "published_at": {
                "type": "string",
                "format": "date-time"
              },
              "headline": {
                "type": "string"
              },
              "url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "methodology_url": {
            "type": "string",
            "format": "uri"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "PriceIndexNote": {
        "type": "object",
        "description": "Measured observations, quantified drivers and PowerGridIQ interpretation are deliberately separate fields.",
        "properties": {
          "id": {
            "type": "string"
          },
          "published_at": {
            "type": "string",
            "format": "date-time"
          },
          "data_as_of": {
            "type": "string",
            "format": "date"
          },
          "trigger": {
            "type": "string",
            "enum": [
              "monthly",
              "event"
            ]
          },
          "period": {
            "type": "object",
            "properties": {
              "start": {
                "type": "string",
                "format": "date"
              },
              "end": {
                "type": "string",
                "format": "date"
              }
            }
          },
          "headline": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "observations": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Facts calculated directly from the index."
          },
          "assessment": {
            "type": "string",
            "description": "PowerGridIQ interpretation. Not a measured fact."
          },
          "breadth": {
            "type": "object",
            "properties": {
              "zones_up": {
                "type": "integer"
              },
              "zones_down": {
                "type": "integer"
              },
              "zones_flat": {
                "type": "integer"
              }
            }
          },
          "drivers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "zone": {
                  "type": "string"
                },
                "direction": {
                  "type": "string",
                  "enum": [
                    "up",
                    "down"
                  ]
                },
                "contribution_index_points": {
                  "type": "number"
                },
                "contribution_eur_mwh": {
                  "type": "number"
                },
                "related_url": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          },
          "context": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Documented explanatory events, with sources."
          },
          "limitations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "internal_links": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                }
              }
            }
          },
          "index_method_version": {
            "type": "string"
          }
        }
      },
      "PriceIndexNotes": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "notes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PriceIndexNote"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/v1/meta": {
      "get": {
        "summary": "Methodology, tiers, pillar weights, lenses, provenance",
        "operationId": "getMeta",
        "responses": {
          "200": {
            "description": "Meta"
          }
        }
      }
    },
    "/api/v1/markets": {
      "get": {
        "summary": "Compact list of all rated markets",
        "operationId": "listMarkets",
        "responses": {
          "200": {
            "description": "Markets"
          }
        }
      }
    },
    "/api/v1/ratings": {
      "get": {
        "summary": "All full ratings",
        "operationId": "listRatings",
        "responses": {
          "200": {
            "description": "Ratings"
          }
        }
      }
    },
    "/api/v1/ratings/{market}": {
      "get": {
        "summary": "Full rating + evidence for one market",
        "operationId": "getRating",
        "parameters": [
          {
            "name": "market",
            "in": "path",
            "required": true,
            "description": "Market id, e.g. quebec",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rating. Read `evidence_layers` first: it carries THREE INDEPENDENT judgments -- band, component and cost pillar -- and a market may pass one while failing another. If `evidence_layers.component.illustrative` is true, the component lines apportion a compiled midpoint and are NOT observed tariff charges; display `evidence_layers.component.notice` beside them or omit the breakdown. A component-evidence limitation is not a statement about the band. When the market publishes a delivered-cost breakdown, realized_cost carries cost_components and component_status; read component_status.evidence_reconciliation before describing any line as sourced. For an OVERVIEW row, realized_cost is a constituent envelope: `aggregation` is `constituent_envelope`, `mid_usd_per_mwh` and `cost_components` are null by design, the bounds come from the records in `derived_from`, and `not_market_wide` states that a site elsewhere in the region may fall outside the range.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "realized_cost": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/RealizedCostComponents"
                        }
                      ],
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown market"
          }
        }
      }
    },
    "/api/v1/cost-history/{market}": {
      "get": {
        "summary": "What changed in one market's recurring cost record, and whether the market moved or PowerGridIQ did",
        "operationId": "getCostHistory",
        "description": "Every change to the canonical cost state is classified at build time, and the class says WHO moved. Only `economic_change` is a market signal; `source_revision`, `input_replacement`, `input_regression`, `methodology_change`, `evidence_state_change` and `correction` are all PowerGridIQ changing what it reads or repairing what it published. `first_observation` carries `is_change: false` and must never be reported as a movement. WHAT changed, WHEN, and WHO moved are FREE at every tier, because a reader who relied on a band we later withdrew has to be able to learn that. The historical FIGURES are the compiled time series and require a Desk key; without one, each event carries a `values` object naming the entitlement rather than omitting the keys. `recorded: false` means PowerGridIQ does not snapshot this market at all, which is an absence in coverage and NOT a finding that its costs have been stable. Vocabulary: cost-vintage-1.0. Methodology: https://powergridiq.com/cost-methodology",
        "parameters": [
          {
            "name": "market",
            "in": "path",
            "required": true,
            "description": "Market id, e.g. miso",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Classified history. `change_count` excludes first observations; `correction_count` is how many times PowerGridIQ has corrected itself here; `market_signal_count` is how many changes were the market moving."
          },
          "404": {
            "description": "Unknown market"
          }
        }
      }
    },
    "/api/v1/best": {
      "get": {
        "summary": "Ranked best-market decision under a weighting lens and filters",
        "operationId": "getBest",
        "parameters": [
          {
            "name": "lens",
            "in": "query",
            "required": false,
            "description": "Weighting lens",
            "schema": {
              "type": "string",
              "enum": [
                "default",
                "cost",
                "carbon",
                "momentum"
              ],
              "default": "default"
            }
          },
          {
            "name": "group",
            "in": "query",
            "required": false,
            "description": "Region group filter",
            "schema": {
              "type": "string",
              "enum": [
                "us",
                "canada",
                "europe",
                "middle_east",
                "latam",
                "asia",
                "oceania",
                "africa"
              ]
            }
          },
          {
            "name": "min_tier",
            "in": "query",
            "required": false,
            "description": "Only tiers at or better than this (1=best)",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max results",
            "schema": {
              "type": "integer",
              "default": 5
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked markets"
          }
        }
      }
    },
    "/api/v1/screen": {
      "get": {
        "summary": "Screen every market against hard constraints (max cost, max months to connect, min tier, min carbon). Returns a verdict for EVERY market in scope, not only the ones that pass",
        "operationId": "screenMarkets",
        "description": "Every market matching the region and tier scope is evaluated and carries a `verdict`: `qualifies`, `does_not_qualify` or `insufficient_evidence`. A rejected market is REPORTED with its decisive reason, never silently omitted, and 'no record exists' is a different answer from 'outside your limit'. `results` is the qualifying set and `count` its length, both unchanged; `rejected` and `insufficient_evidence` are additive. `decisive_reason.code` comes from a closed vocabulary (`cost_above_ceiling`, `cod_not_achievable`, `carbon_above_ceiling`, `cost_evidence_not_collected`, `time_evidence_not_collected`, `carbon_evidence_not_collected`, `jurisdiction_required`). `jurisdiction_required` means the REQUEST lacks a jurisdiction for a region whose jurisdictions hold opposite cost-evidence states; `decisive_reason.available_jurisdictions` lists them and the caller should re-screen with `jurisdiction=`. `constraint_trace` lists every dimension with `pass`, `fail`, `unknown` or `not_tested`. Constraints are evaluated server-side against the true figure, so the VERDICT is free at every tier; only the precise observed values are withheld without a Desk key. Ranking by tier and score applies only to markets that already qualify, so a strong rating can never rescue a failed hard constraint. Contract: power-feasibility-contract-0.2 (0.2 adds the `jurisdiction_required` reason code and the `jurisdiction` dimension; 0.1 remains published and unchanged).",
        "parameters": [
          {
            "name": "max_cost",
            "in": "query",
            "required": false,
            "description": "Only markets whose recurring cost midpoint for the standard 100 MW case is at or below this, in US dollars per MWh",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "max_months",
            "in": "query",
            "required": false,
            "description": "Only markets whose typical connection wait is at or below this many months (applies to markets with compiled queue data). Takes precedence over target_cod if both are supplied",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "target_cod",
            "in": "query",
            "required": false,
            "description": "Target commercial-operation date, as a year (2030) or an ISO date (2030-06-01). Converted to a month count from today and applied as max_months; the derived figure is echoed as request.target_cod_months so the caller can see what was screened on. Most callers have a date, not a month count",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "required_mw",
            "in": "query",
            "required": false,
            "description": "Contracted demand in MW. RECORDED AND ECHOED BUT NOT TESTED: PowerGridIQ holds no local connectable-capacity evidence for any market, so no verdict here confirms a market can serve this load. Every market's constraint_trace carries an advisory mw_capability entry with outcome not_assessed, and the response carries a top-level caveat. It does not affect the verdict, because failing every market on a question that was never asked would mislead as much as passing them",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "min_tier",
            "in": "query",
            "required": false,
            "description": "Only tiers at or better than this (1=best)",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5
            }
          },
          {
            "name": "min_carbon",
            "in": "query",
            "required": false,
            "description": "Only markets whose CARBON PILLAR is at or above this (0-100, higher is cleaner). The carbon pillar is an assessed score forming part of the PGIQ Rating. It is NOT grid.carbon_intensity_g, the measured gCO2eq/kWh, and is not derived from it -- no part of the rating reads the intensity. The two rank markets similarly but not identically: mean rank gap 3.4 places across the 77 markets carrying both, six in opposite halves, widest divergence Estonia at 23 places. To filter on measured intensity, read grid.carbon_intensity_g and apply your own threshold. Separately, nine Canadian markets carry carbon_official_reference on /api/v1/ratings/{market}: the ECCC National Inventory Report Annex 7 generation intensity, measured, on this product's declared operational-combustion boundary. It is published beside grid.carbon_intensity_g and has NOT been adopted -- the field states which value this API serves. Read carbon_official_reference.generation_intensity for the official figure; never consumption_intensity_not_adopted, which is a wider boundary.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100
            }
          },
          {
            "name": "group",
            "in": "query",
            "required": false,
            "description": "Region group filter",
            "schema": {
              "type": "string",
              "enum": [
                "us",
                "canada",
                "europe",
                "middle_east",
                "latam",
                "asia",
                "oceania",
                "africa"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Every market in scope, each with a verdict, a decisive reason and a cost_breakdown. cost_breakdown.available is false for a withdrawn band, a withheld breakdown or an unresearched market, and `reason` says which. Component shares are free; per-line dollars follow the midpoint entitlement. capacity_cross_check, where present, is a measured auction observation with a PowerGridIQ conversion that applies no gross-up and is a floor, not a tariff charge."
          }
        }
      }
    },
    "/api/v1/developments": {
      "get": {
        "summary": "Cited grid developments feed, ONE ROW PER MARKET",
        "operationId": "getDevelopments",
        "parameters": [
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Filter to one market id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One row per market. A development affecting several markets appears as several rows sharing one event_id, each flagged is_projection_of_shared_event with shared_event_market_count. DO NOT COUNT THOSE ROWS AS SEPARATE PROJECTS AND DO NOT SUM market_capacity_mw ACROSS THEM -- that double-counts one physical plant. Aggregate by event_id, or use /api/v1/developments/events. Legacy rows may carry null stage and delivers; see the canonical endpoint's schema_cohort for whether that means 'not assessed' rather than 'no impact'."
          }
        }
      }
    },
    "/api/v1/developments/events": {
      "get": {
        "summary": "Canonical development events, ONE ROW PER EVENT",
        "operationId": "getDevelopmentEvents",
        "parameters": [
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Filter to events affecting one market id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "Return a single event",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One record per real-world event, carrying its market_impacts inline. Does NOT replace /api/v1/developments; both are supported and their row semantics differ. Capacity is split three ways and must not be conflated: project.nameplate_capacity_mw is the PHYSICAL PLANT; project.physical_location_market_id is the single market hosting it; market_impacts[].market_capacity_mw is that market's ALLOCATION. market_capacity_qualifier is exact | up_to | at_least, where up_to is a CEILING and not a quantity. capacity_basis is tolling_allocation | contracted_future | currently_operating. currently_operating_mw is what is generating today. catalog_complete reports whether every legacy record has been canonicalized. Events with is_legacy_wrapper carry schema_cohort; legacy_pre_schema records predate the stage and delivers fields, so stage_assessment 'not_assessed_pre_schema' means UNEXAMINED, not 'no stage' and not 'no product effect'. verification_status 'verified_for_context' means the event may be described to a reader but is NOT analytically eligible and must not feed a number, pillar or rating."
          }
        }
      }
    },
    "/api/v1/actions": {
      "get": {
        "summary": "Rating-action history",
        "operationId": "getActions",
        "parameters": [
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Filter to one market id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Actions"
          }
        }
      }
    },
    "/api/v1/state": {
      "get": {
        "summary": "One compact snapshot of the whole product (Tier 1 list, top markets, recent changes, live extremes, developments, queue, links)",
        "operationId": "getState",
        "responses": {
          "200": {
            "description": "State snapshot"
          }
        }
      }
    },
    "/api/v1/grid": {
      "get": {
        "summary": "Compact grid metrics for all markets (price, average operational carbon, system-level firm margin)",
        "operationId": "listGrid",
        "responses": {
          "200": {
            "description": "Grid metrics"
          }
        }
      }
    },
    "/api/v1/grid/{market}": {
      "get": {
        "summary": "Grid snapshot for one market. Fields carry independent provenance: some are live operator observations, others structural baselines. Read field_provenance, not the record-level as_of, before quoting a number.",
        "description": "TEMPORAL SEMANTICS. as_of is retained unchanged for backward compatibility, but it means different things for different markets: an observation timestamp where a live feed exists, a rating vintage where one does not. as_of_type declares which of the two you received (observation_time or data_vintage), and both are also returned separately, exactly one non-null. retrieved_at is when PowerGridIQ fetched the reading. FIELD PROVENANCE: field_provenance maps every returned metric to its own state, observation_time and data_vintage. States are live, recent, derived_current, structural, modelled, temporarily_unavailable, unavailable. A live demand does NOT make the price live. NO PRICE FEED EXISTS: price_usd_mwh is always state modelled in every market, an undated typical value and never an observation; imports_mw and exports_mw are likewise modelled everywhere. record_state is derived conservatively and is mixed whenever field states differ; it is never live, because no market has every field live.",
        "operationId": "getGrid",
        "parameters": [
          {
            "name": "market",
            "in": "path",
            "required": true,
            "description": "Market id, e.g. quebec",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Grid snapshot"
          },
          "404": {
            "description": "Unknown market"
          }
        }
      }
    },
    "/api/v1/cheapest-window": {
      "get": {
        "summary": "Cheapest hours to run a flexible load: typical daily price shape per market",
        "operationId": "getCheapestWindow",
        "parameters": [
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Market id. If omitted, returns each market's cheapest-hour price ranked ascending.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max results when no market is given",
            "schema": {
              "type": "integer",
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cheapest windows"
          }
        }
      }
    },
    "/api/v1/price-index": {
      "get": {
        "summary": "European Day-Ahead Power Price Index: daily EUR/MWh basket across 23 European bidding zones, indexed to July 2026 = 100, with seven-day and 28-day windows",
        "description": "Wholesale day-ahead prices only. Not a delivered cost of electricity. A day publishes only when all 23 constituents are present; nothing is imputed, substituted or carried forward. Separate product from the Global Power Conditions Monitor at /api/v1/index.",
        "operationId": "getPriceIndex",
        "responses": {
          "200": {
            "description": "Index payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PriceIndex"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/price-index/notes": {
      "get": {
        "summary": "Published commentary on the European price index",
        "description": "Monthly once a valid 28-day comparison exists, plus event-driven notes after a material move. Human-reviewed before publication. Returns an empty collection when nothing has been published.",
        "operationId": "listPriceIndexNotes",
        "responses": {
          "200": {
            "description": "Notes collection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PriceIndexNotes"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/price-index/notes/{id}": {
      "get": {
        "summary": "One commentary note",
        "operationId": "getPriceIndexNote",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Note id, e.g. eppi-2026-08",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Note",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PriceIndexNote"
                }
              }
            }
          },
          "404": {
            "description": "Unknown note"
          }
        }
      }
    },
    "/api/v1/usage": {
      "get": {
        "summary": "Your API key's current-month usage and remaining quota",
        "operationId": "getUsage",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Usage"
          },
          "401": {
            "description": "Missing or invalid key"
          }
        }
      }
    },
    "/api/v1/subscribe": {
      "post": {
        "summary": "Subscribe an https endpoint to rating-change webhooks",
        "operationId": "subscribeWebhook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public https endpoint to receive signed POSTs"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "rating.action",
                        "market.under_review",
                        "market.review_cleared"
                      ]
                    },
                    "description": "Defaults to all events"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subscription created; returns id and one-time secret"
          },
          "400": {
            "description": "Invalid url or events"
          }
        }
      }
    },
    "/api/v1/subscribe/{id}": {
      "get": {
        "summary": "Webhook subscription status",
        "operationId": "getSubscription",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Subscription"
          },
          "404": {
            "description": "Unknown subscription"
          }
        }
      },
      "delete": {
        "summary": "Unsubscribe (requires the secret)",
        "operationId": "deleteSubscription",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PGIQ-Secret",
            "in": "header",
            "required": true,
            "description": "The secret returned at subscribe",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unsubscribed"
          },
          "403": {
            "description": "Secret required"
          },
          "404": {
            "description": "Unknown subscription"
          }
        }
      }
    }
  }
}