{"openapi":"3.1.0","info":{"title":"WattChop Partner API","version":"1.1.0","description":"📖 **New here? Start with the [Partner API guided tour](https://support.wattchop.com/kb/partner-sizing-api)** — a step-by-step walkthrough of every endpoint below, runnable right here in this playground.\n\n---\n\n**WattChop Partner API v1** — programmatic access to tariff-smart sizing and savings results. Three algorithms, one API.\n\nEvery response carries dual savings values — `savings_static` (sizing-alone) and `savings_optimized` (under WattChop Dispatch) — plus a diminishing-returns curve. The gap between the two savings figures is the value WattChop Dispatch delivers to the optimized number.\n\n**Tariffs:** `GET /v1/tariffs` is the catalog — every tariff you can size against, with a `tariff_id` to pass straight to `POST /v1/sizing`. Each entry carries a `certification` of `certified` (validated by WattChop against the published rate sheet) or `provisional` (in the WattChop Library, not yet certified); both are runnable. To find tariffs for a customer, filter by `utility_id`; ZIP-level lookup is available on request.\n\n**Two dates, and they are not the same thing.** `certified_as_of` is the date WattChop verified the tariff's *mechanics* against the published rate sheet. `effective_from` is the date the *utility's* rates took effect. **WattChop certifies mechanics; the utility owns the effective date.** A tariff can be certified long after it took effect, and a tariff certified last year can be pricing rates that changed last month — which is why every run also reports the version it was priced against.\n\n**Versions.** A `tariff_id` is a stable handle: store it in your config and it keeps meaning the same tariff. A *version* is the specific rate run that priced a given study, reported on every sizing response under `tariff.version`, and listed by `GET /v1/tariffs/{tariff_id}/versions`. `content_hash` is the identity of the economics — two versions with the same hash price identically, and a hash that changes means the dollars changed. Runs always use the current version; historical rate runs are not selectable in v1.\n\n**Spotted a rate change before we did?** `POST /v1/tariffs/{tariff_id}/report-update` — free on public tariffs, always. While a report is open, every run against that tariff reports `tariff.freshness = \"update_pending\"`, so anyone pricing a deal can see a change has been reported and is being checked.\n\nThe three legacy `tariff_code` values (`sce_tou_d_prime`, `sce_tou_gs3e`, `pge_b20p`) still work and map to tariff ids 109, 50 and 57 respectively. Supply exactly one of `tariff_id` or `tariff_code`.\n\n**Authentication:** Every endpoint requires a credential — free, but never anonymous. A free API key (`X-Api-Key`) covers `GET /v1/tariffs`, `GET /v1/tariffs/{tariff_id}/versions`, `GET /v1/building-types`, `POST /v1/synthesize` and `GET /v1/template`; `GET /v1/tariffs` without a key returns a coverage summary only. Bearer token required: `POST /v1/sizing`, `POST /v1/sizing/sweep`, `GET /v1/sizing/sweep/{run_id}`, `GET /v1/samples` — supplied as `Authorization: Bearer <token>`. One endpoint is on its own lane: `POST /v1/tariffs/{tariff_id}/report-update` does not yet accept an API key — it authenticates against your WattChop account sign-in. API-key access is coming; ask support if you need it sooner. Your organization's key is issued by WattChop, scoped to your account, and delivered separately from this documentation. Questions: support@wattchop.com.\n\n**Attribution:** Every response carries a `Powered by WattChop` attribution field. Display of this attribution is required wherever the values are surfaced to end customers.\n\n© 2026 EnergyBoom LLC","contact":{"name":"WattChop Partner Support","email":"support@wattchop.com"}},"paths":{"/api/v1/tariffs":{"get":{"tags":["Tariff Lookup","Partner API v1"],"summary":"List Tariffs","description":"The WattChop tariff catalog — every tariff you can size or dispatch against, by `tariff_id`.\n\nPass a `tariff_id` from this list to `POST /api/v1/sizing` or `POST /api/v1/sizing/sweep`.\n\n**Certification** is `certified` when WattChop has validated the tariff against the published rate sheet, `provisional` otherwise (in the WattChop Library, not yet certified). Both are runnable.\n\n**Coverage:** to find tariffs for a customer, filter by `utility_id`. ZIP-level lookup is available on request.\n\n**Access:** without a key this returns a per-utility coverage summary (counts only). With a free API key (`X-Api-Key`) it returns per-tariff IDs, certification, `certified_as_of` and `freshness`. Rate-limited per key.","operationId":"list_partner_tariffs_api_v1_tariffs_get","parameters":[{"name":"utility_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by utility. Accepts a utility id from the catalog.","title":"Utility Id"},"description":"Filter by utility. Accepts a utility id from the catalog."},{"name":"sector","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"'residential' matches only residential; any other value matches all non-residential tariffs.","title":"Sector"},"description":"'residential' matches only residential; any other value matches all non-residential tariffs."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text match against tariff code, name, or description.","title":"Q"},"description":"Free-text match against tariff code, name, or description."},{"name":"demand_kw","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Only tariffs whose demand band admits this peak demand (kW).","title":"Demand Kw"},"description":"Only tariffs whose demand band admits this peak demand (kW)."},{"name":"include_unrunnable","in":"query","required":false,"schema":{"type":"boolean","description":"Include tariffs that carry no rate rows (has_rates=false). These cannot be sized against; off by default.","default":false,"title":"Include Unrunnable"},"description":"Include tariffs that carry no rate rows (has_rates=false). These cannot be sized against; off by default."},{"name":"X-Api-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tariffs/{tariff_id}/versions":{"get":{"tags":["Tariff Lookup","Partner API v1"],"summary":"List Tariff Versions","description":"The rate runs behind a tariff code.\n\nA `tariff_id` is a stable **handle** — store it in your config and it keeps meaning the same tariff. A **version** is the specific rate run that priced a given study. `content_hash` is the identity of the economics: two versions with the same hash price identically, and a hash that changes means the dollars changed.\n\n`certified_as_of` is WattChop's mechanics-verification date. `effective_from` is the utility's own effective date. **WattChop certifies mechanics, the utility owns the effective date.** The two are never the same field and never interchangeable.\n\nRequires a free API key (`X-Api-Key`). Rate-limited per key.","operationId":"list_tariff_versions_api_v1_tariffs__tariff_id__versions_get","parameters":[{"name":"tariff_id","in":"path","required":true,"schema":{"type":"integer","title":"Tariff Id"}},{"name":"X-Api-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tariffs/{tariff_id}/report-update":{"post":{"tags":["Tariff Lookup","Partner API v1"],"summary":"Report a Tariff Update","description":"Tell us a utility has changed its rates.\n\nIf you are pricing deals against a tariff, you will often see a rate change before we do. This is how you tell us. Reporting against a public tariff is **free, always** — we would rather know.\n\nSend the date you believe the new rates took effect and a pointer to the source: `kind='url'` for a published rate sheet, or `kind='shared_link'` for a large source book that will not fit in a single document link. (Direct file upload is not accepted yet.)\n\nWhile a report is open, every sizing run against that tariff carries `tariff.freshness = \"update_pending\"` — so anyone pricing a deal can see that a change has been reported and is being checked. You will be notified when the new version lands.\n\nReports against a **private** tariff your organization owns carry a $25 verification fee and return a checkout link.","operationId":"report_tariff_update_api_v1_tariffs__tariff_id__report_update_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tariff_id","in":"path","required":true,"schema":{"type":"integer","title":"Tariff Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/sizing":{"post":{"tags":["Partner API v1","Partner API v1"],"summary":"Score one system, synchronously (fixed PV + battery)","description":"The sizing primitive, synchronous single-config mode. Supply BOTH `pv_kw` and `battery_kwh`; the engine scores that exact system in one pass and returns the full envelope directly (HTTP 200, ~30-90s). No polling.\n\n`diminishing_returns` is omitted: this response is one point. To build a curve, call this endpoint N times at different sizes and compose the results yourself.\n\nIf you want the optimizer to choose sizes for you, or a full multi-scenario dimensioning study, use POST /v1/sizing/sweep instead.\n\n**Request encoding:** send as `multipart/form-data` with a form field named `request` containing the JSON-encoded SizingRequest body. (A bundled sample needs no file; a CSV upload adds an `intervals_csv` file part.)","operationId":"submit_partner_sizing_sync_api_v1_sizing_post","security":[{"PartnerBearer":[]}],"parameters":[{"name":"X-Api-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_submit_partner_sizing_sync_api_v1_sizing_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SizingEnvelope"}}}},"400":{"description":"pv_kw and battery_kwh are both required; omit-either is not supported on this endpoint."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/sizing/sweep":{"post":{"tags":["Partner API v1","Partner API v1"],"summary":"Submit a partner sizing SWEEP run (multi-scenario, async, premium tier)","description":"Submits an asynchronous sizing run against any catalog tariff (see `GET /v1/tariffs`) and either a bundled sample load profile or a partner-uploaded CSV. Returns a 202 with a `run_id`; poll `GET /api/v1/sizing/sweep/{run_id}` until you receive HTTP 200 with the full envelope. Run completion typically takes 30–60 seconds.\n\n**Request encoding:** send as `multipart/form-data` with a form field named `request` containing the JSON-encoded SizingRequest body; add an `intervals_csv` file part when `intervals.kind=='upload'`.","operationId":"submit_partner_sizing_api_v1_sizing_sweep_post","security":[{"PartnerBearer":[]}],"parameters":[{"name":"X-Api-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_submit_partner_sizing_api_v1_sizing_sweep_post"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SizingRunQueued"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/sizing/sweep/{run_id}":{"get":{"tags":["Partner API v1","Partner API v1"],"summary":"Poll a partner sizing SWEEP run","description":"Polls the status of a partner sizing run. While queued or running, returns HTTP 202 with `status`/`progress_pct`/`progress_message`. When the run completes, returns HTTP 200 with the full sizing envelope (same shape as the sync `/v1/sizing` response, with `run_id` populated). On engine failure, returns HTTP 500 with the error message. There is no list endpoint; partners should persist the envelope on receipt of the 200.","operationId":"get_partner_sizing_api_v1_sizing_sweep__run_id__get","security":[{"PartnerBearer":[]}],"parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}},{"name":"X-Api-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SizingEnvelope"}}}},"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SizingRunPending"}}},"description":"Accepted"},"404":{"description":"Unknown run_id."},"500":{"description":"Engine failed; see `detail`."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/template":{"get":{"tags":["Partner API v1","Partner API v1"],"summary":"Download the partner CSV template","description":"Returns a 1-day interval CSV showing the exact format required for `intervals_csv` uploads on POST /api/v1/sizing. Two columns: `timestamp_utc` (ISO 8601, ending in Z) and `kwh` (energy per interval, non-negative). Full uploads must contain exactly 35,040 rows (1 year of 15-minute intervals, non-leap year). Requires a free API key (`X-Api-Key`, or the same key as `Authorization: Bearer`).","operationId":"download_partner_template_api_v1_template_get","parameters":[{"name":"X-Api-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"CSV file (text/csv) — the 1-day format example.","content":{"text/csv":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/samples":{"get":{"tags":["Partner API v1","Partner API v1"],"summary":"List bundled sample load profiles","description":"Returns the manifest of bundled sample load profiles. Each sample is paired with the test tariff whose rate structure best exercises the engine's economic logic for that load shape.\n\n**The KEY of each `samples` entry is its `sample_site_id`** — pass it as `intervals.sample_site_id` with `intervals.kind='sample'` to POST /api/v1/sizing or POST /api/v1/sizing/sweep. There is no `sample_site_id` field inside the entries; the entry body carries the filename, label, annual kWh and paired tariff.","operationId":"list_partner_samples_api_v1_samples_get","security":[{"PartnerBearer":[]}],"parameters":[{"name":"X-Api-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/building-types":{"get":{"tags":["Synthesizer","Partner API v1"],"summary":"The canonical building-type catalog","description":"Every building type the synthesizer accepts, with its display label, sector and load archetype, plus the legacy shorthand aliases that are still honoured.\n\n**The canonical strings use an EN DASH (–, U+2013), not a hyphen.** A plain hyphen is accepted and canonicalized; any other spelling is rejected with 422 and the allowed list. Requires a free API key.","operationId":"list_building_types_api_v1_building_types_get","parameters":[{"name":"X-Api-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BuildingTypesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/synthesize":{"post":{"tags":["Synthesizer","Partner API v1"],"summary":"Synthesize a year of 15-minute intervals from 12 monthly bills","description":"Stateless. Returns a full non-leap year of 15-minute interval data shaped to the building type and reconciled to the monthly energy you supply. Nothing is stored — no project is created and no data is written.\n\n**CSV (default)** is byte-compatible with the WattChop interval upload template (`timestamp_utc,kwh`), so the response can be uploaded straight back into a project. **JSON** (`format: \"json\"`) returns the same series plus full metadata.\n\n`peak_kw` is the ANNUAL maximum demand: it caps every month and shapes the one month that naturally carries the peak. It is not applied as a target to all twelve months.\n\n**Your billed peak is a measurement, and it is always honoured.** Real buildings are peakier than archetype curves — an annual peak often lives in a few 15-minute needles that a smooth curve cannot express. When shaping cannot reach the number you supplied, the peak is injected as a short demand event on the month's naturally busiest interval, and the rest of that month is renormalized so its energy stays exact. `metadata.peak_injected`, `metadata.injected_months` and the `X-WattChop-Peak-Injected` header say when this happened. You are never asked to lower a number you read off a bill.\n\nTimestamps carry SITE-LOCAL WALL-CLOCK digits — see `metadata.clock_convention`.\n\n**Errors:** 422 unknown building type (with the allowed list), \"Other\" without detail, physically infeasible peak (a month's energy cannot fit under the ceiling), bad timezone, malformed monthly_kwh; 429 rate limited. There is no longer an \"unreachable peak\" error — see the injection note above.\n\nRate limited per IP. Requires a free API key.","operationId":"synthesize_api_v1_synthesize_post","parameters":[{"name":"X-Api-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/routes__synth_api__SynthesizeRequest"}}}},"responses":{"200":{"description":"CSV (default) or JSON, per `format`.","content":{"application/json":{"schema":{}},"text/csv":{}}},"422":{"description":"Validation failure. `detail.code` identifies the class."},"429":{"description":"Rate limited. See `Retry-After`."}}}}},"components":{"schemas":{"Attribution":{"properties":{"powered_by":{"type":"string","title":"Powered By"},"required_display":{"type":"string","title":"Required Display"},"methodology_url":{"type":"string","title":"Methodology Url"}},"type":"object","required":["powered_by","required_display","methodology_url"],"title":"Attribution"},"Body_submit_partner_sizing_api_v1_sizing_sweep_post":{"properties":{"request":{"type":"string","title":"Request","description":"JSON-encoded SizingRequest body."},"intervals_csv":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Intervals Csv","description":"CSV required if intervals.kind=='upload'."}},"type":"object","required":["request"],"title":"Body_submit_partner_sizing_api_v1_sizing_sweep_post"},"Body_submit_partner_sizing_sync_api_v1_sizing_post":{"properties":{"request":{"type":"string","title":"Request","description":"JSON-encoded SizingRequest body."},"intervals_csv":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Intervals Csv","description":"CSV required if intervals.kind=='upload'."}},"type":"object","required":["request"],"title":"Body_submit_partner_sizing_sync_api_v1_sizing_post"},"BuildingTypeEntry":{"properties":{"building_type":{"type":"string","title":"Building Type","description":"Canonical value. Uses an EN DASH (U+2013)."},"display_name":{"type":"string","title":"Display Name","description":"Short human label, e.g. \"Office\"."},"description":{"type":"string","title":"Description","description":"One-line description of the load pattern."},"sector":{"type":"string","title":"Sector","description":"commercial | residential | industrial | agricultural | municipal"},"archetype":{"type":"string","title":"Archetype","description":"Thermal/operational archetype driving the shape."}},"type":"object","required":["building_type","display_name","description","sector","archetype"],"title":"BuildingTypeEntry"},"BuildingTypesResponse":{"properties":{"count":{"type":"integer","title":"Count","description":"Number of canonical building types."},"building_types":{"items":{"$ref":"#/components/schemas/BuildingTypeEntry"},"type":"array","title":"Building Types","description":"The published canon."},"legacy_aliases":{"additionalProperties":{"type":"string"},"type":"object","title":"Legacy Aliases","description":"Legacy shorthand -> canonical value. Accepted by /v1/synthesize."},"note":{"type":"string","title":"Note","description":"Encoding guidance for the canonical strings."}},"type":"object","required":["count","building_types","legacy_aliases","note"],"title":"BuildingTypesResponse"},"DiminishingReturnsPoint":{"properties":{"battery_kwh":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Battery Kwh","description":"Battery size at this sweep point (kWh)."},"annual_bill":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Annual Bill","description":"Optimized annual bill at this size."},"savings_optimized":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Savings Optimized","description":"Absolute savings vs true baseline (true_baseline minus annual_bill)."},"is_recommended":{"type":"boolean","title":"Is Recommended","description":"True on the single row matching recommended_battery_kwh. May NOT be the curve max."}},"type":"object","required":["is_recommended"],"title":"DiminishingReturnsPoint"},"DispatchBlock":{"properties":{"recommended_cadence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommended Cadence","description":"Always 'autonomous' on projects sized on or after 2026-07-28, or null. The former seasonal | monthly | daily rubric was retired 2026-07-28: dispatch now runs autonomously on-site and is re-minted whenever the tariff changes, so cadence is no longer a recommendation. Historical projects may still carry a legacy value until re-optimized."},"cadence_reasoning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cadence Reasoning","description":"Why this cadence applies. Constant under the autonomous regime; retained for backward compatibility."},"annual_uplift":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Annual Uplift","description":"Annual $ uplift WattChop dispatch unlocks over naive."},"requires":{"type":"string","title":"Requires","description":"Subscription required to realize the uplift."},"schedule_available":{"type":"boolean","title":"Schedule Available","description":"Always false here — the schedule is paywalled and never surfaced by this endpoint."}},"type":"object","required":["requires","schedule_available"],"title":"DispatchBlock"},"EngineProvenance":{"properties":{"engine_revision":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Engine Revision","description":"Engine build that produced this run. Null on pre-rev-01392 SRJs."},"generated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated At","description":"When the engine run was produced. Null on legacy SRJs."}},"type":"object","title":"EngineProvenance"},"ExportModeling":{"properties":{"requested":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested","description":"Export program requested for the run."},"applied":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applied","description":"Export program the engine actually priced under."},"coerced":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Coerced","description":"True when applied != requested — read `note` for why."},"acc_utility":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Acc Utility","description":"ACC utility key used for export pricing. Null when no ACC mapping applied."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Plain-language explanation when coerced is true."}},"type":"object","title":"ExportModeling","description":"What export program the engine actually priced under.\n\nThe requested program is not always the applied one. Export credits for\nNEM-3 are priced off California ACC tables; when the tariff's utility has no\nACC mapping (any non-CA-IOU), the engine coerces to 'none' rather than\nsilently substituting a generic fallback rate for a program it does not\nmodel (e.g. MA SMART, Eversource net metering). This block reports that."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"Scenario":{"properties":{"id":{"type":"string","title":"Id","description":"\"solar_battery\" or \"solar_only\"."},"label":{"type":"string","title":"Label","description":"Human label, e.g. \"Solar + Battery\"."},"is_recommended":{"type":"boolean","title":"Is Recommended","description":"Always true for the recommended scenario. The only flag — there is no `winner`."},"system":{"$ref":"#/components/schemas/ScenarioSystem"},"savings_static":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Savings Static","description":"Annual savings under naive (non-WattChop) dispatch."},"savings_optimized":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Savings Optimized","description":"Annual savings under WattChop dispatch."},"optimization_uplift":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Optimization Uplift","description":"savings_optimized minus savings_static. The WattChop delta."},"demand_savings":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Demand Savings","description":"Annual demand-charge savings component."},"energy_savings":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Energy Savings","description":"Annual energy-charge savings component."}},"type":"object","required":["id","label","is_recommended","system"],"title":"Scenario"},"ScenarioSystem":{"properties":{"pv_kw":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pv Kw","description":"Recommended PV size (kW)."},"battery_kwh":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Battery Kwh","description":"Recommended battery energy (kWh). 0 when no battery is recommended."},"battery_kw":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Battery Kw","description":"Recommended battery power (kW). 0 when no battery is recommended."}},"type":"object","title":"ScenarioSystem"},"SizingEnvelope":{"properties":{"site_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Ref","description":"Echo of the project id; set by the preview endpoint as a partner reference. Not emitted by the projection."},"run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Run Id","description":"Echo of the partner run id (uuid). Set by POST /v1/sizing and GET /v1/sizing/{run_id}; not emitted by the preview endpoint. CRITICAL: any handler-decorated key MUST be declared here, or response_model silently strips it (line-112 lesson, Slice D)."},"engine":{"$ref":"#/components/schemas/EngineProvenance"},"scenarios":{"items":{"$ref":"#/components/schemas/Scenario"},"type":"array","title":"Scenarios","description":"0 or 1 element: the recommended scenario, or empty if no viable PV recommendation."},"diminishing_returns":{"anyOf":[{"items":{"$ref":"#/components/schemas/DiminishingReturnsPoint"},"type":"array"},{"type":"null"}],"title":"Diminishing Returns","description":"Multi-point sizing curve. Present on the async/sweep envelope; OMITTED on the sync single-config envelope (the primitive returns one point; the partner composes the curve by calling N times)."},"selection_rationale":{"additionalProperties":true,"type":"object","title":"Selection Rationale","description":"Engine's gate explanation for the recommended config. May be {} on some engine paths."},"dispatch":{"$ref":"#/components/schemas/DispatchBlock"},"attribution":{"$ref":"#/components/schemas/Attribution"},"tariff":{"anyOf":[{"$ref":"#/components/schemas/TariffRef"},{"type":"null"}],"description":"PD/T2 2026-08-18. The tariff this run was priced against. Omitted on runs produced before the tariff catalog shipped."},"export_modeling":{"anyOf":[{"$ref":"#/components/schemas/ExportModeling"},{"type":"null"}],"description":"PD/T2 2026-08-18. Export-program coverage disclosure. Omitted on runs produced before the disclosure shipped."}},"type":"object","required":["engine","scenarios","dispatch","attribution"],"title":"SizingEnvelope","example":{"attribution":{"methodology_url":"https://app.wattchop.com/methodology","powered_by":"WattChop","required_display":"Battery sizing by WattChop"},"diminishing_returns":[{"annual_bill":612300.0,"battery_kwh":540.0,"is_recommended":false,"savings_optimized":188100.0},{"annual_bill":543100.0,"battery_kwh":1080.0,"is_recommended":true,"savings_optimized":257200.0},{"annual_bill":541900.0,"battery_kwh":1620.0,"is_recommended":false,"savings_optimized":258400.0}],"dispatch":{"annual_uplift":58800.0,"cadence_reasoning":"Dispatch runs autonomously on-site and is re-minted whenever the tariff changes. Cadence is not customer-selectable under WattChop Battery Management.","recommended_cadence":"autonomous","requires":"wattchop_dispatch_subscription","schedule_available":false},"engine":{"engine_revision":"01392","generated_at":"2026-05-20T18:42:11Z"},"scenarios":[{"demand_savings":121500.0,"energy_savings":135700.0,"id":"solar_battery","is_recommended":true,"label":"Solar + Battery","optimization_uplift":58800.0,"savings_optimized":257200.0,"savings_static":198400.0,"system":{"battery_kw":540.0,"battery_kwh":1080.0,"pv_kw":412.5}}]}},"SizingRunPending":{"properties":{"run_id":{"type":"string","title":"Run Id"},"status":{"type":"string","enum":["queued","running"],"title":"Status"},"progress_pct":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Progress Pct"},"progress_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Progress Message"},"partner_run_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partner Run Ref"}},"type":"object","required":["run_id","status","progress_pct"],"title":"SizingRunPending","description":"Shape returned by GET /api/v1/sizing/{run_id} while the run is still queued\nor running. Returned with HTTP 202 via JSONResponse — NOT through\nresponse_model (which can only carry SizingEnvelope, the 200 shape)."},"SizingRunQueued":{"properties":{"run_id":{"type":"string","title":"Run Id"},"status":{"type":"string","const":"queued","title":"Status","default":"queued"},"poll_url":{"type":"string","title":"Poll Url","description":"Relative URL to poll for completion. GET this until you receive HTTP 200."},"partner_run_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partner Run Ref"}},"type":"object","required":["run_id","poll_url"],"title":"SizingRunQueued","description":"Shape returned by POST /api/v1/sizing on successful submission."},"TariffRef":{"properties":{"id":{"type":"integer","title":"Id","description":"Catalog tariff id. Pass this to POST /v1/sizing."},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code","description":"Tariff code, e.g. 'TOU-GS-3-E'."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Human-readable tariff name."},"utility_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utility Name","description":"Utility that publishes the tariff."},"certification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Certification","description":"'certified' (WattChop validated against the published rate sheet) or 'provisional' (in the WattChop Library, not yet certified). Both are runnable."},"certified_as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Certified As Of","description":"The date WattChop verified this tariff's MECHANICS against the published rate sheet. WattChop certifies mechanics; the utility owns the effective date (see version.effective_from). The two are never interchangeable."},"version":{"anyOf":[{"$ref":"#/components/schemas/TariffVersionRef"},{"type":"null"}],"description":"Which rate run priced this study. Omitted on runs produced before version identity shipped."},"freshness":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Freshness","description":"PL/T-V7. `current`, or `update_pending` when someone has reported that the utility changed its rates and we are checking. `update_pending` is community-reported diligence, not a defect: it means this tariff is being actively watched. It appears on certified and provisional tariffs alike."}},"type":"object","required":["id"],"title":"TariffRef","description":"Which tariff this run was actually priced against."},"TariffVersionRef":{"properties":{"rate_version_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Version Id","description":"The rate run the engine actually priced against. Null when the engine did not report one — an honest null, never a guess."},"catalog_version_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Catalog Version Id","description":"The effective-date record for this tariff, if one exists."},"effective_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effective From","description":"The date the UTILITY's rates took effect. The utility's fact — not to be confused with certified_as_of, which is ours."},"effective_through":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effective Through","description":"When a newer version supersedes this one. Null while current."},"is_current":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Current","description":"True when this is the version a run submitted today would use."},"content_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Hash","description":"Identity of the economics — a canonical hash over every rate-bearing field. Two versions with the same hash price identically; a changed hash means the dollars changed. Computed at read time, so it always describes the rates as they are right now."}},"type":"object","title":"TariffVersionRef","description":"PJ/T-V1 2026-08-18 — which rate run actually priced this study.\n\nTWO NAMESPACES, reported separately and never merged (banked landmine):\n  rate_version_id     custom_tariff_rate_details.version_id — the rows that\n                      hold rate content and what the engine priced against.\n  catalog_version_id  tariff_versions.id — the effective-date record.\nThey are different numbers for the same tariff. Do not join them."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"routes__synth_api__SynthesizeRequest":{"properties":{"building_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Building Type","description":"Canonical value from /v1/building-types, or a legacy alias."},"building_type_detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Building Type Detail","description":"Required when building_type is \"Other\" (2-120 chars)."},"monthly_kwh":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Monthly Kwh","description":"Exactly 12 values, January first, each > 0."},"peak_kw":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Peak Kw","description":"Annual maximum demand (kW), as read off a utility bill. Acts as a CEILING on every month and as a shaping target for the single month that naturally carries the annual peak. Your billed peak is honoured: if the archetype curve cannot reach it by shaping, it is injected as a short demand event and flagged in `metadata.peak_injected`. Omit for a purely profile-shaped peak."},"monthly_peak_kw":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Monthly Peak Kw","description":"Advanced: 12 per-month demand readings, each a target for its own month. Same injection guarantee as `peak_kw`, applied per month."},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat","description":"Latitude — enables weather modulation only."},"lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lng","description":"Longitude — enables weather modulation only."},"tz_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tz Name","description":"IANA timezone. Declarative in v1. Default America/Los_Angeles."},"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year","description":"Calendar year. Default: last full calendar year."},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format","description":"\"csv\" (default) or \"json\".","default":"csv"}},"type":"object","title":"SynthesizeRequest"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"},"PartnerBearer":{"type":"http","description":"Partner-issued bearer token","scheme":"bearer"}}}}