{"openapi":"3.0.3","info":{"title":"WattGap Energy Intelligence API","version":"v1","description":"Read-only, provenance-stamped energy diagnostic truth for partners and household agents. The diagnostic is never for sale and no payer or access key changes eligibility truth, ranking, or recommendations. Every authenticated caller receives the same answer for the same facts. Access tiers change throughput only: defaults are 100 requests per hour for a free key and 1,000 per hour for a metered key; a deployment may publish a different quota in RateLimit-Policy. X-WattGap-Attribution is optional self-declared analytics metadata, not authorization, verified identity, or an entitlement; adding or omitting it cannot change throughput. X-WattGap-Key remains the authorization control. Errors use RFC 9457 application/problem+json with a machine-readable code and a human-readable detail (also copied to error for compatibility). Rate-limit conventions: IETF RateLimit and RateLimit-Policy structured fields, plus Retry-After on HTTP 429. Versioning is in the URL path (/api/v1). Breaking changes require a new major path. Deprecation is signaled with RFC 9745 Deprecation and Sunset headers and documented at /api-versioning. No current version is deprecated. Every answer carries source, verify date, and confidence. OpenAPI expresses structural and scalar bounds; runtime-only invariants additionally require confidence_tier to equal provenance.confidence, data_sources to have exact ordered equality with provenance.source, each rebate total to equal the nearest-cent sum of program amounts, data.zip to equal meta.zip whenever the endpoint data includes a ZIP, and each energy response's serialized data and meta sections to stay within 524272 encoded bytes apiece.","contact":{"name":"WattGap developer support","url":"https://wattgap.com/contact"}},"externalDocs":{"description":"WattGap developer docs, versioning and deprecation policy, and error model","url":"https://wattgap.com/developers#versioning-and-deprecation"},"servers":[{"url":"/","description":"WattGap API host"}],"components":{"securitySchemes":{"WattGapKey":{"type":"apiKey","in":"header","name":"X-WattGap-Key"}},"schemas":{"RatePlan":{"type":"object","properties":{"label":{"type":"string","maxLength":4096},"utility":{"type":"string","maxLength":4096},"rate_id":{"type":"string","maxLength":4096},"sector":{"type":"string","maxLength":4096},"description":{"type":"string","maxLength":4096},"source_url":{"anyOf":[{"type":"string","enum":[""]},{"type":"string","maxLength":2048}]},"rate_type":{"type":"string","enum":["flat","tiered","tou"]},"fixed_monthly_charge":{"type":"number","minimum":0},"avg_rate_per_kwh":{"type":"number","minimum":0},"peak_rate":{"type":"number","minimum":0},"off_peak_rate":{"type":"number","minimum":0},"peak_hours":{"type":"string","maxLength":4096},"is_default":{"type":"boolean"}},"required":["label","utility","rate_id","sector","description","source_url","rate_type","fixed_monthly_charge","avg_rate_per_kwh","is_default"],"additionalProperties":false},"UsageData":{"type":"object","properties":{"tier":{"type":"string","enum":["free","metered"]},"total_calls":{"type":"integer","minimum":0},"per_endpoint":{"type":"object","maxProperties":500,"additionalProperties":{"type":"integer","minimum":0}},"unique_zips":{"type":"integer","minimum":0},"unique_apps":{"type":"integer","minimum":0},"confidence_tier_distribution":{"type":"object","properties":{"high":{"type":"integer","minimum":0},"medium":{"type":"integer","minimum":0},"low":{"type":"integer","minimum":0}},"required":["high","medium","low"],"additionalProperties":false},"first_seen":{"type":"string","nullable":true,"maxLength":128},"last_seen":{"type":"string","nullable":true,"maxLength":128}},"required":["tier","total_calls","per_endpoint","unique_zips","unique_apps","confidence_tier_distribution","first_seen","last_seen"],"additionalProperties":false},"UsageResponse":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/UsageData"},"meta":{"type":"object","properties":{"generated_at":{"type":"string","maxLength":128},"api_version":{"type":"string","enum":["v1"]}},"required":["generated_at","api_version"],"additionalProperties":false}},"required":["data","meta"],"additionalProperties":false},"ProvenanceEnvelope":{"type":"object","properties":{"source":{"type":"array","items":{"type":"string","maxLength":4096},"maxItems":64},"verified_at":{"type":"string","nullable":true,"maxLength":128},"confidence":{"type":"string","enum":["high","medium","low"]},"confidence_base":{"type":"string","enum":["high","medium","low"]},"age_days":{"type":"number","nullable":true,"minimum":0},"stale":{"type":"boolean"},"reason":{"type":"string","enum":["fresh","aging","stale","unverifiable","check_failed"]},"as_of":{"type":"string","maxLength":128}},"required":["source","verified_at","confidence","confidence_base","age_days","stale","reason","as_of"],"additionalProperties":false},"EnergyApiMeta":{"type":"object","properties":{"zip":{"type":"string","pattern":"^\\d{5}$"},"generated_at":{"type":"string","maxLength":128},"api_version":{"type":"string","enum":["v1"]},"confidence_tier":{"type":"string","enum":["high","medium","low"]},"data_sources":{"type":"array","items":{"type":"string","maxLength":4096},"maxItems":64},"provenance":{"$ref":"#/components/schemas/ProvenanceEnvelope"}},"required":["zip","generated_at","api_version","confidence_tier","data_sources","provenance"],"additionalProperties":false},"ProfileResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"zip":{"type":"string","pattern":"^\\d{5}$"},"state":{"type":"string","maxLength":4096},"utility":{"type":"string","maxLength":4096},"rebate_program_count":{"type":"integer","minimum":0},"total_potential_rebates":{"type":"number","minimum":0,"maximum":90071992547409.9},"ground_truth_available":{"type":"boolean"},"rate_plan_count":{"type":"integer","minimum":0}},"required":["zip","state","utility","rebate_program_count","total_potential_rebates","ground_truth_available","rate_plan_count"],"additionalProperties":false},"meta":{"$ref":"#/components/schemas/EnergyApiMeta"}},"required":["data","meta"],"additionalProperties":false},"RebatesResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"programs":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","maxLength":4096},"type":{"type":"string","maxLength":4096},"amount":{"type":"number","minimum":0,"maximum":90071992547409.9},"income_qualified":{"type":"boolean"},"source_urls":{"type":"array","items":{"type":"string","maxLength":2048},"maxItems":64},"last_verified_at":{"type":"string","nullable":true,"maxLength":128}},"required":["name","type","amount"],"additionalProperties":false},"maxItems":500},"total":{"type":"number","minimum":0,"maximum":90071992547409.9}},"required":["programs","total"],"additionalProperties":false},"meta":{"$ref":"#/components/schemas/EnergyApiMeta"}},"required":["data","meta"],"additionalProperties":false},"ScoreResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"wattgap_score":{"type":"number","minimum":0,"maximum":100},"potential_savings":{"type":"number","minimum":0,"maximum":90071992547409.9},"zip":{"type":"string","pattern":"^\\d{5}$"},"state":{"type":"string","maxLength":4096}},"required":["wattgap_score","potential_savings","zip","state"],"additionalProperties":false},"meta":{"$ref":"#/components/schemas/EnergyApiMeta"}},"required":["data","meta"],"additionalProperties":false},"RatePlansResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"rate_plans":{"type":"array","items":{"$ref":"#/components/schemas/RatePlan"},"maxItems":500}},"required":["rate_plans"],"additionalProperties":false},"meta":{"$ref":"#/components/schemas/EnergyApiMeta"}},"required":["data","meta"],"additionalProperties":false},"UtilityResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"utility_name":{"type":"string","maxLength":4096},"eia_id":{"type":"string","nullable":true,"maxLength":4096},"state":{"type":"string","maxLength":4096}},"required":["utility_name","eia_id","state"],"additionalProperties":false},"meta":{"$ref":"#/components/schemas/EnergyApiMeta"}},"required":["data","meta"],"additionalProperties":false},"ProgramStatusResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"territory":{"type":"string","maxLength":4096},"utility_name":{"type":"string","nullable":true,"maxLength":4096},"programs":{"type":"array","items":{"type":"object","properties":{"program_id":{"type":"string","maxLength":4096},"name":{"type":"string","maxLength":4096},"status":{"type":"string","enum":["active","paused","exhausted","coming_soon"]},"notes":{"type":"string","nullable":true,"maxLength":4096},"funding_remaining_usd":{"type":"number","nullable":true,"minimum":0,"maximum":90071992547409.9},"last_verified_at":{"type":"string","nullable":true,"maxLength":128},"source":{"type":"string","enum":["certified","live"]},"status_age_days":{"type":"number","nullable":true,"minimum":0},"status_freshness":{"type":"string","enum":["fresh","aging","stale","unknown"]}},"required":["program_id","name","status","notes","funding_remaining_usd","last_verified_at","source"],"additionalProperties":false},"maxItems":500}},"required":["territory","utility_name","programs"],"additionalProperties":false},"meta":{"$ref":"#/components/schemas/EnergyApiMeta"}},"required":["data","meta"],"additionalProperties":false},"ProblemDetails":{"type":"object","properties":{"type":{"type":"string","minLength":1,"maxLength":2048},"title":{"type":"string","minLength":1,"maxLength":128},"status":{"type":"integer","minimum":400,"maximum":599},"detail":{"type":"string","maxLength":4096},"instance":{"type":"string","minLength":1,"maxLength":2048},"code":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z][a-z0-9_]*$"},"error":{"type":"string","maxLength":4096}},"required":["type","title","status","detail","code","error"],"additionalProperties":false},"OpenApiDocument":{"type":"object","properties":{"openapi":{"type":"string","minLength":1},"info":{"type":"object","properties":{"title":{"type":"string","minLength":1},"version":{"type":"string","minLength":1}},"required":["title","version"]},"paths":{"type":"object","additionalProperties":{"type":"object","properties":{}}}},"required":["openapi","info","paths"]},"CitationScorecardMarkdown":{"type":"string","minLength":1},"GetEnergyProfileMcpInput":{"type":"object","properties":{"zip":{"type":"string","pattern":"^\\d{5}$"}},"required":["zip"],"additionalProperties":false},"CheckRebatesMcpInput":{"type":"object","properties":{"zip":{"type":"string","pattern":"^\\d{5}$"}},"required":["zip"],"additionalProperties":false},"GetProgramStatusMcpInput":{"type":"object","properties":{"zip":{"type":"string","pattern":"^\\d{5}$"}},"required":["zip"],"additionalProperties":false},"ResolveUtilityMcpInput":{"type":"object","properties":{"zip":{"type":"string","pattern":"^\\d{5}$"}},"required":["zip"],"additionalProperties":false},"CitationScorecardResponse":{"type":"object","properties":{"month":{"type":"string","pattern":"^\\d{4}-(0[1-9]|1[0-2])$","description":"Must equal the year and month (YYYY-MM prefix) of the sibling generated_at field exactly. This cross-field equality is not enforced by this OpenAPI 3.0 schema (no `if`/`then` or equivalent keyword is available); a consumer must implement the check independently to build a validator as strict as the API."},"generated_at":{"type":"string","pattern":"^\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d{1,9})?Z$","description":"Must be a real calendar UTC timestamp, not merely pattern-shaped: for example 2026-02-30T00:00:00Z matches this field's pattern but is rejected by the API because February never has a 30th day. Full calendar validity (days-per-month, leap-year February 29) is not enforced by this OpenAPI 3.0 schema; a consumer must implement the check independently. Its year and month (the first 7 characters, YYYY-MM) must also equal the sibling month field exactly; see that field's description for the same cross-field rule."},"markdown":{"type":"string"}},"required":["month","generated_at","markdown"],"additionalProperties":false,"description":"month and generated_at satisfy two invariants that this OpenAPI 3.0 document cannot express as schema keywords, so they are documented here instead of enforced: (1) generated_at must be a real calendar-valid UTC timestamp, not just pattern-shaped -- 2026-02-30T00:00:00Z matches the property pattern but the API never returns it; (2) month must equal the YYYY-MM prefix of generated_at exactly. Both are enforced at runtime by CitationScorecardResponseSchema in lib/energy-api-contract/src/schemas.ts. A validator generated from this document alone will accept responses the API can never legitimately return unless both checks are implemented independently."}},"parameters":{}},"paths":{"/api/v1/energy/profile":{"get":{"operationId":"getEnergyProfile","summary":"Home energy profile for a ZIP","description":"Return the provenance-stamped home energy profile for a 5-digit US ZIP: serving utility, climate, rebate potential, and confidence. Requires X-WattGap-Key. The diagnostic answer does not change by key or payer.","tags":["energy"],"security":[{"WattGapKey":[]}],"parameters":[{"schema":{"type":"string","pattern":"^\\d{5}$"},"required":true,"name":"zip","in":"query"}],"responses":{"200":{"description":"Home energy profile. OpenAPI expresses structural and scalar bounds; runtime-only invariants additionally require confidence_tier to equal provenance.confidence, data_sources to have exact ordered equality with provenance.source, each rebate total to equal the nearest-cent sum of program amounts, data.zip to equal meta.zip whenever the endpoint data includes a ZIP, and each energy response's serialized data and meta sections to stay within 524272 encoded bytes apiece.","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileResponse"}}}},"400":{"description":"Invalid request (for example, malformed ZIP)","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"Missing or invalid X-WattGap-Key. Rate-limit headers are omitted because no key was accepted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying. Present on HTTP 429.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Provider or response-schema failure","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/energy/rebates":{"get":{"operationId":"getEnergyRebates","summary":"Potentially claimable programs based on ZIP-level facts","description":"Screening result only, not a final eligibility determination. Additional household facts may be requested only when needed for a specific program. Requires X-WattGap-Key.","tags":["energy"],"security":[{"WattGapKey":[]}],"parameters":[{"schema":{"type":"string","pattern":"^\\d{5}$"},"required":true,"name":"zip","in":"query"},{"schema":{"type":"number","minimum":0,"maximum":1000000000},"required":false,"name":"income","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":20},"required":false,"name":"household_size","in":"query"}],"responses":{"200":{"description":"Potentially claimable programs. OpenAPI expresses structural and scalar bounds; runtime-only invariants additionally require confidence_tier to equal provenance.confidence, data_sources to have exact ordered equality with provenance.source, each rebate total to equal the nearest-cent sum of program amounts, data.zip to equal meta.zip whenever the endpoint data includes a ZIP, and each energy response's serialized data and meta sections to stay within 524272 encoded bytes apiece.","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RebatesResponse"}}}},"400":{"description":"Invalid request (for example, malformed ZIP)","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"Missing or invalid X-WattGap-Key. Rate-limit headers are omitted because no key was accepted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying. Present on HTTP 429.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Provider or response-schema failure","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/energy/score":{"get":{"operationId":"getEnergyScore","summary":"WattGap Score and potential savings for a ZIP","description":"Return the WattGap Score and potential savings for a 5-digit US ZIP, with provenance and confidence. Requires X-WattGap-Key.","tags":["energy"],"security":[{"WattGapKey":[]}],"parameters":[{"schema":{"type":"string","pattern":"^\\d{5}$"},"required":true,"name":"zip","in":"query"}],"responses":{"200":{"description":"WattGap Score. OpenAPI expresses structural and scalar bounds; runtime-only invariants additionally require confidence_tier to equal provenance.confidence, data_sources to have exact ordered equality with provenance.source, each rebate total to equal the nearest-cent sum of program amounts, data.zip to equal meta.zip whenever the endpoint data includes a ZIP, and each energy response's serialized data and meta sections to stay within 524272 encoded bytes apiece.","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScoreResponse"}}}},"400":{"description":"Invalid request (for example, malformed ZIP)","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"Missing or invalid X-WattGap-Key. Rate-limit headers are omitted because no key was accepted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying. Present on HTTP 429.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Provider or response-schema failure","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/energy/rate-plans":{"get":{"operationId":"getEnergyRatePlans","summary":"Utility rate plans for a ZIP","description":"List electric rate plans for the utility that serves a 5-digit US ZIP, including average rate and time-of-use fields when known. Requires X-WattGap-Key.","tags":["energy"],"security":[{"WattGapKey":[]}],"parameters":[{"schema":{"type":"string","pattern":"^\\d{5}$"},"required":true,"name":"zip","in":"query"}],"responses":{"200":{"description":"Utility rate plans. OpenAPI expresses structural and scalar bounds; runtime-only invariants additionally require confidence_tier to equal provenance.confidence, data_sources to have exact ordered equality with provenance.source, each rebate total to equal the nearest-cent sum of program amounts, data.zip to equal meta.zip whenever the endpoint data includes a ZIP, and each energy response's serialized data and meta sections to stay within 524272 encoded bytes apiece.","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RatePlansResponse"}}}},"400":{"description":"Invalid request (for example, malformed ZIP)","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"Missing or invalid X-WattGap-Key. Rate-limit headers are omitted because no key was accepted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying. Present on HTTP 429.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Provider or response-schema failure","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/energy/utility":{"get":{"operationId":"getEnergyUtility","summary":"Serving utility for a ZIP","description":"Resolve the serving electric utility for a 5-digit US ZIP with provenance and honest confidence. Requires X-WattGap-Key.","tags":["energy"],"security":[{"WattGapKey":[]}],"parameters":[{"schema":{"type":"string","pattern":"^\\d{5}$"},"required":true,"name":"zip","in":"query"}],"responses":{"200":{"description":"Serving utility. OpenAPI expresses structural and scalar bounds; runtime-only invariants additionally require confidence_tier to equal provenance.confidence, data_sources to have exact ordered equality with provenance.source, each rebate total to equal the nearest-cent sum of program amounts, data.zip to equal meta.zip whenever the endpoint data includes a ZIP, and each energy response's serialized data and meta sections to stay within 524272 encoded bytes apiece.","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UtilityResponse"}}}},"400":{"description":"Invalid request (for example, malformed ZIP)","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"Missing or invalid X-WattGap-Key. Rate-limit headers are omitted because no key was accepted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying. Present on HTTP 429.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Provider or response-schema failure","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/energy/program-status":{"get":{"operationId":"getEnergyProgramStatus","summary":"Program status and verify date for a ZIP","description":"Return program availability, freshness, and provenance for a 5-digit US ZIP without guessing when coverage is unknown. Requires X-WattGap-Key.","tags":["energy"],"security":[{"WattGapKey":[]}],"parameters":[{"schema":{"type":"string","pattern":"^\\d{5}$"},"required":true,"name":"zip","in":"query"}],"responses":{"200":{"description":"Program status with provenance. OpenAPI expresses structural and scalar bounds; runtime-only invariants additionally require confidence_tier to equal provenance.confidence, data_sources to have exact ordered equality with provenance.source, each rebate total to equal the nearest-cent sum of program amounts, data.zip to equal meta.zip whenever the endpoint data includes a ZIP, and each energy response's serialized data and meta sections to stay within 524272 encoded bytes apiece.","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramStatusResponse"}}}},"400":{"description":"Invalid request (for example, malformed ZIP)","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"Missing or invalid X-WattGap-Key. Rate-limit headers are omitted because no key was accepted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying. Present on HTTP 429.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Provider or response-schema failure","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/usage":{"get":{"operationId":"getEnergyApiUsage","summary":"The calling key's own aggregate usage telemetry","description":"Return aggregate call counts for the calling X-WattGap-Key only. Never includes household records or another key's usage.","tags":["meta"],"security":[{"WattGapKey":[]}],"responses":{"200":{"description":"Usage summary for the calling key","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"401":{"description":"Missing or invalid X-WattGap-Key. Rate-limit headers are omitted because no key was accepted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Compatibility form of the request quota.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Compatibility form of the remaining request quota.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current quota window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying. Present on HTTP 429.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/openapi.json":{"get":{"operationId":"getEnergyApiOpenApi","summary":"This OpenAPI document","description":"Unauthenticated machine-readable OpenAPI 3.0 contract for the WattGap Energy Intelligence API. Also published at /openapi.json and /api/openapi.yaml. Errors use RFC 9457 application/problem+json. Versioning and deprecation: /api-versioning.","tags":["meta"],"security":[],"responses":{"200":{"description":"The machine-readable OpenAPI contract for this API","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenApiDocument"}}}},"429":{"description":"Rate limit exceeded","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying. Present on HTTP 429.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/energy/citation-scorecard":{"get":{"operationId":"getCitationScorecardMarkdown","summary":"Citation scorecard markdown for the latest monthly assistant-accuracy report","description":"Returns the latest citation scorecard report as plain-text markdown. Gated behind CITATION_SCORECARD_PUBLISH env flag; returns 404 when the flag is not set to 'true'.","tags":["energy"],"security":[],"responses":{"200":{"description":"Citation scorecard report in markdown","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}}},"content":{"text/plain":{"schema":{"$ref":"#/components/schemas/CitationScorecardMarkdown"}}}},"404":{"description":"Not published or no report available","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Failed to read report","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v1/energy/citation-scorecard.json":{"get":{"operationId":"getCitationScorecardJson","summary":"Citation scorecard for the latest monthly assistant-accuracy report","description":"Returns the latest citation scorecard report as a JSON envelope (month, generated_at, markdown). Gated behind CITATION_SCORECARD_PUBLISH env flag; returns 404 when the flag is not set to 'true'. The unsuffixed /citation-scorecard variant returns the same report as plain-text markdown.","tags":["energy"],"security":[],"responses":{"200":{"description":"Citation scorecard report","headers":{"RateLimit":{"description":"Remaining quota in the current IETF RateLimit structured-field format.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota policy in the current IETF RateLimit-Policy structured-field format.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CitationScorecardResponse"}}}},"404":{"description":"Not published or no report available","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"Failed to read report","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}},"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}}}}