{"openapi": "3.0.3", "info": {"title": "GridView Reporting API", "version": "1.0.0", "description": "Standalone reporting API served from the reporting server. It reads hourly rollups and tariff overlays from its own SQLite store."}, "servers": [{"url": "http://gridview.reverse-proxy.co.za"}], "security": [{"ApiKeyHeader": []}, {"BearerAuth": []}], "paths": {"/": {"get": {"summary": "HTML reporting UI", "description": "Browser UI for selecting a meter and viewing hourly usage bars.", "security": [], "responses": {"200": {"description": "HTML UI", "content": {"text/html": {"schema": {"type": "string"}}}}}}}, "/favicon.png": {"get": {"summary": "GridView favicon", "description": "Serves the curated GridBrain favicon source asset as a PNG.", "security": [], "responses": {"200": {"description": "PNG favicon", "content": {"image/png": {"schema": {"type": "string", "format": "binary"}}}}}}}, "/docs": {"get": {"summary": "HTML API documentation", "description": "Human-readable API reference and usage examples.", "security": [], "responses": {"200": {"description": "HTML documentation page", "content": {"text/html": {"schema": {"type": "string"}}}}}}}, "/health": {"get": {"summary": "Service health", "description": "Basic health and runtime metadata for the reporting service.", "security": [], "responses": {"200": {"description": "Healthy service response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HealthResponse"}}}}}}}, "/openapi.json": {"get": {"summary": "OpenAPI contract", "description": "Machine-readable API contract for client generation and agent discovery.", "security": [], "responses": {"200": {"description": "OpenAPI 3.0 document", "content": {"application/json": {"schema": {"type": "object"}}}}}}}, "/api-info": {"get": {"summary": "Compact API discovery summary", "description": "Small JSON overview intended for quick LLM or client discovery.", "security": [], "responses": {"200": {"description": "API summary response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiInfoResponse"}}}}}}}, "/meters": {"get": {"summary": "List meters", "description": "Returns the known meter identifiers in the local rollup store.", "security": [{"ApiKeyHeader": []}, {"BearerAuth": []}], "responses": {"200": {"description": "Meter list", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MetersResponse"}}}}, "401": {"description": "Unauthorized", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}}}}}}}, "/tariffs": {"get": {"summary": "List tariff rules", "description": "Returns tariff rules for a specific meter plus matching shared defaults.", "security": [{"ApiKeyHeader": []}, {"BearerAuth": []}], "parameters": [{"name": "meter", "in": "query", "required": false, "description": "Meter identifier. When omitted, all tariff rules are returned.", "schema": {"type": "string"}, "example": "meter-subtropico-fridge"}], "responses": {"200": {"description": "Tariff rules", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TariffsResponse"}}}}, "401": {"description": "Unauthorized", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}}}}}}}, "/usage/hourly": {"get": {"summary": "Fetch hourly usage", "description": "Returns hourly usage rows and summary totals for one meter over a requested range. Naive datetimes are interpreted in Africa/Johannesburg.", "security": [{"ApiKeyHeader": []}, {"BearerAuth": []}], "parameters": [{"name": "meter", "in": "query", "required": true, "description": "Meter identifier to query.", "schema": {"type": "string"}, "example": "meter-subtropico-fridge"}, {"name": "start", "in": "query", "required": true, "description": "Inclusive range start in ISO 8601 format.", "schema": {"type": "string", "format": "date-time"}, "example": "2026-03-01T00:00:00+02:00"}, {"name": "end", "in": "query", "required": true, "description": "Exclusive range end in ISO 8601 format.", "schema": {"type": "string", "format": "date-time"}, "example": "2026-04-01T00:00:00+02:00"}], "responses": {"200": {"description": "Hourly usage payload", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HourlyUsageResponse"}}}}, "400": {"description": "Validation error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}}}}, "401": {"description": "Unauthorized", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}}}}}}}}, "components": {"securitySchemes": {"ApiKeyHeader": {"type": "apiKey", "in": "header", "name": "X-API-Key", "description": "Primary API key header for protected JSON routes."}, "BearerAuth": {"type": "http", "scheme": "bearer", "bearerFormat": "API key", "description": "Alternative bearer-token style API key."}}, "schemas": {"ErrorResponse": {"type": "object", "required": ["error"], "properties": {"error": {"type": "string"}}}, "HealthResponse": {"type": "object", "required": ["status", "rollup_db", "meter_count", "requires_api_key"], "properties": {"status": {"type": "string", "example": "ok"}, "rollup_db": {"type": "string"}, "meter_count": {"type": "integer", "example": 4}, "requires_api_key": {"type": "boolean", "example": true}}}, "MetersResponse": {"type": "object", "required": ["meters"], "properties": {"meters": {"type": "array", "items": {"type": "string"}, "example": ["meter-subtropico-fridge", "meter-fv-building"]}}}, "TariffRule": {"type": "object", "required": ["id", "meter", "label", "multiplier", "days", "start_minute", "end_minute", "valid_from", "valid_to", "priority"], "properties": {"id": {"type": "integer", "example": 1}, "meter": {"type": "string", "example": "meter-subtropico-fridge"}, "label": {"type": "string", "example": "peak"}, "multiplier": {"type": "number", "example": 1.5}, "days": {"type": "string", "example": "weekdays"}, "start_minute": {"type": "integer", "example": 420}, "end_minute": {"type": "integer", "example": 600}, "valid_from": {"type": "string", "format": "date", "nullable": true, "example": "2026-03-01"}, "valid_to": {"type": "string", "format": "date", "nullable": true, "example": null}, "priority": {"type": "integer", "example": 50}}}, "TariffsResponse": {"type": "object", "required": ["meter", "rules"], "properties": {"meter": {"type": "string", "nullable": true, "example": "meter-subtropico-fridge"}, "rules": {"type": "array", "items": {"$ref": "#/components/schemas/TariffRule"}}}}, "UsageSummary": {"type": "object", "required": ["row_count", "total_kwh_net", "total_kwh_forward", "total_kwh_reverse", "total_billed_kwh_weighted", "max_kw", "rated_row_count", "first_bucket", "last_bucket"], "properties": {"row_count": {"type": "integer", "example": 744}, "total_kwh_net": {"type": "number", "example": 9697.539}, "total_kwh_forward": {"type": "number", "example": 9697.539}, "total_kwh_reverse": {"type": "number", "example": 0.0}, "total_billed_kwh_weighted": {"type": "number", "example": 11234.112}, "max_kw": {"type": "number", "nullable": true, "example": 42.7}, "rated_row_count": {"type": "integer", "example": 744}, "first_bucket": {"type": "string", "format": "date-time", "nullable": true, "example": "2026-03-01T00:00:00+02:00"}, "last_bucket": {"type": "string", "format": "date-time", "nullable": true, "example": "2026-03-31T23:00:00+02:00"}}}, "HourlyUsageRow": {"type": "object", "required": ["meter", "bucket_start", "kwh_net", "kwh_forward", "kwh_reverse", "avg_kw", "max_kw", "sample_count", "source_method", "quality", "first_sample_ts", "last_sample_ts", "rate_label", "rate_multiplier", "billed_kwh_weighted"], "properties": {"meter": {"type": "string", "example": "meter-subtropico-fridge"}, "bucket_start": {"type": "string", "format": "date-time", "example": "2026-03-01T13:00:00+02:00"}, "kwh_net": {"type": "number", "nullable": true, "example": 12.345}, "kwh_forward": {"type": "number", "nullable": true, "example": 12.345}, "kwh_reverse": {"type": "number", "nullable": true, "example": 0.0}, "avg_kw": {"type": "number", "nullable": true, "example": 12.345}, "max_kw": {"type": "number", "nullable": true, "example": 15.2}, "sample_count": {"type": "integer", "example": 2400}, "source_method": {"type": "string", "example": "counter_delta"}, "quality": {"type": "string", "example": "ok"}, "first_sample_ts": {"type": "string", "format": "date-time", "nullable": true, "example": "2026-03-01T13:00:05+02:00"}, "last_sample_ts": {"type": "string", "format": "date-time", "nullable": true, "example": "2026-03-01T13:59:55+02:00"}, "rate_label": {"type": "string", "nullable": true, "example": "standard"}, "rate_multiplier": {"type": "number", "nullable": true, "example": 1.2}, "billed_kwh_weighted": {"type": "number", "nullable": true, "example": 14.814}}}, "HourlyUsageResponse": {"type": "object", "required": ["meter", "start", "end", "summary", "rows"], "properties": {"meter": {"type": "string", "example": "meter-subtropico-fridge"}, "start": {"type": "string", "format": "date-time", "example": "2026-03-01T00:00:00+02:00"}, "end": {"type": "string", "format": "date-time", "example": "2026-04-01T00:00:00+02:00"}, "summary": {"$ref": "#/components/schemas/UsageSummary"}, "rows": {"type": "array", "items": {"$ref": "#/components/schemas/HourlyUsageRow"}}}}, "ApiInfoResponse": {"type": "object", "required": ["name", "version", "default_timezone", "ui_url", "docs_url", "openapi_url", "auth", "examples", "endpoints"], "properties": {"name": {"type": "string", "example": "GridView Reporting API"}, "version": {"type": "string", "example": "1.0.0"}, "default_timezone": {"type": "string", "example": "Africa/Johannesburg"}, "ui_url": {"type": "string", "format": "uri"}, "docs_url": {"type": "string", "format": "uri"}, "openapi_url": {"type": "string", "format": "uri"}, "auth": {"type": "object"}, "examples": {"type": "object"}, "endpoints": {"type": "array", "items": {"type": "object", "required": ["path", "auth_required", "purpose"], "properties": {"path": {"type": "string", "example": "/usage/hourly"}, "auth_required": {"type": "boolean", "example": true}, "purpose": {"type": "string", "example": "fetch hourly usage rows and summary totals"}}}}}}}}}