{
    "openapi": "3.0.3",
    "info": {
        "title": "LEC Smart Electricity Management System \u2014 integration API",
        "version": "1.3.0",
        "description": "Integration points for field sensors, the smart-meter head-end (AMI), payment providers, the licensed STS vending system, partner banks and the SMS gateway.\n\nAll endpoints are read-only towards the grid: none can operate switches, generators or meters.\n\nSecrets are configured in the server's `.env` file and exchanged with each partner out of band. Use HTTPS only.",
        "contact": {
            "name": "LEC ICT",
            "email": "admin@lec.stoneatech.com"
        }
    },
    "servers": [
        {
            "url": "https://lec.stoneatech.com",
            "description": "This LEC installation"
        }
    ],
    "tags": [
        {
            "name": "Telemetry",
            "description": "Line sensors and the smart-meter head-end send data to LEC."
        },
        {
            "name": "Payments",
            "description": "Payment providers confirm payments to LEC."
        },
        {
            "name": "Public",
            "description": "Open data for the website and partners."
        },
        {
            "name": "Called by LEC: vending gateway",
            "description": "Implemented by the licensed STS vending system (or a bridge in front of it). Auth: `Authorization: Bearer LEC_VENDING_API_KEY`; when LEC_VENDING_SIGNING_SECRET is set also X-LEC-Timestamp / X-LEC-Signature as above."
        },
        {
            "name": "Called by LEC: bank",
            "description": "Optional status API of a partner bank for reference payments."
        },
        {
            "name": "Called by LEC: SMS",
            "description": "Any SMS gateway accepting this JSON (SMS_DRIVER=http)."
        }
    ],
    "paths": {
        "/api/v1/sensors/{code}/readings": {
            "post": {
                "tags": [
                    "Telemetry"
                ],
                "summary": "Line sensor reading or fault indication",
                "operationId": "sensorReading",
                "description": "Secret: `LEC_SENSOR_SECRET`. A reading with an `event` raises a fault on the sensor's feeder, alerts Dispatch/TND and SMSes affected customers.",
                "parameters": [
                    {
                        "name": "code",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "example": "S-F-PV2-1"
                    },
                    {
                        "name": "X-LEC-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Unix time in seconds."
                    },
                    {
                        "name": "X-LEC-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Hex HMAC-SHA256 of \"{X-LEC-Timestamp}.{raw request body}\" using the shared secret. Requests older than 5 minutes are rejected."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "value": {
                                        "type": "number",
                                        "example": 1240.5
                                    },
                                    "unit": {
                                        "type": "string",
                                        "example": "A"
                                    },
                                    "event": {
                                        "type": "string",
                                        "nullable": true,
                                        "enum": [
                                            "overcurrent",
                                            "earth_fault",
                                            "voltage_loss",
                                            "line_down",
                                            "overload",
                                            "transformer_overheat"
                                        ]
                                    },
                                    "severity": {
                                        "type": "string",
                                        "enum": [
                                            "low",
                                            "medium",
                                            "high",
                                            "critical"
                                        ]
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Reading stored",
                        "content": {
                            "application/json": {
                                "example": {
                                    "ok": true,
                                    "event": null
                                }
                            }
                        }
                    },
                    "201": {
                        "description": "Fault raised",
                        "content": {
                            "application/json": {
                                "example": {
                                    "ok": true,
                                    "event": "FLT-2026-000031"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing or invalid signature / timestamp",
                        "content": {
                            "application/json": {
                                "example": {
                                    "error": "unauthorised"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Unknown sensor code"
                    }
                }
            }
        },
        "/api/v1/meter-events": {
            "post": {
                "tags": [
                    "Telemetry"
                ],
                "summary": "Smart-meter events (last gasp, restore, tamper\u2026)",
                "operationId": "meterEvents",
                "description": "Secret: `LEC_METER_EVENT_SECRET` (falls back to LEC_SENSOR_SECRET). Up to 500 events per call; duplicates (same meter, type and time) are ignored. Power-off events from several meters on one transformer within `LEC_OUTAGE_WINDOW` minutes create a suspected outage; tamper events open a Revenue Protection lead.",
                "parameters": [
                    {
                        "name": "X-LEC-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Unix time in seconds."
                    },
                    {
                        "name": "X-LEC-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Hex HMAC-SHA256 of \"{X-LEC-Timestamp}.{raw request body}\" using the shared secret. Requests older than 5 minutes are rejected."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "events"
                                ],
                                "properties": {
                                    "events": {
                                        "type": "array",
                                        "maxItems": 500,
                                        "items": {
                                            "type": "object",
                                            "required": [
                                                "meter_no",
                                                "type",
                                                "occurred_at"
                                            ],
                                            "properties": {
                                                "meter_no": {
                                                    "type": "string",
                                                    "example": "04100000043"
                                                },
                                                "type": {
                                                    "type": "string",
                                                    "enum": [
                                                        "power_off",
                                                        "power_restored",
                                                        "tamper",
                                                        "cover_open",
                                                        "reverse_energy",
                                                        "low_credit"
                                                    ]
                                                },
                                                "occurred_at": {
                                                    "type": "string",
                                                    "format": "date-time",
                                                    "example": "2026-10-26T19:52:04Z"
                                                },
                                                "payload": {
                                                    "type": "object"
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Accepted",
                        "content": {
                            "application/json": {
                                "example": {
                                    "ok": true,
                                    "stored": 4,
                                    "duplicates": 0,
                                    "unknown": 0,
                                    "outages": [
                                        "OUT-2026-000002"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing or invalid signature / timestamp",
                        "content": {
                            "application/json": {
                                "example": {
                                    "error": "unauthorised"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation error"
                    }
                }
            }
        },
        "/api/v1/meter-readings": {
            "post": {
                "tags": [
                    "Telemetry"
                ],
                "summary": "Smart-meter register reads",
                "operationId": "meterReadings",
                "description": "Same signing as meter events. Postpaid meters feed monthly billing (unusual reads are flagged for review); prepaid meters update the register only. Up to 1000 reads per call.",
                "parameters": [
                    {
                        "name": "X-LEC-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Unix time in seconds."
                    },
                    {
                        "name": "X-LEC-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Hex HMAC-SHA256 of \"{X-LEC-Timestamp}.{raw request body}\" using the shared secret. Requests older than 5 minutes are rejected."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "readings"
                                ],
                                "properties": {
                                    "readings": {
                                        "type": "array",
                                        "maxItems": 1000,
                                        "items": {
                                            "type": "object",
                                            "required": [
                                                "meter_no",
                                                "reading_kwh",
                                                "read_at"
                                            ],
                                            "properties": {
                                                "meter_no": {
                                                    "type": "string",
                                                    "example": "05200000001"
                                                },
                                                "reading_kwh": {
                                                    "type": "number",
                                                    "example": 18452.6
                                                },
                                                "read_at": {
                                                    "type": "string",
                                                    "format": "date-time"
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Accepted",
                        "content": {
                            "application/json": {
                                "example": {
                                    "ok": true,
                                    "accepted": 98,
                                    "flagged": 1,
                                    "skipped": 1,
                                    "unknown": 0
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing or invalid signature / timestamp",
                        "content": {
                            "application/json": {
                                "example": {
                                    "error": "unauthorised"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/webhooks/payments/{provider}": {
            "post": {
                "tags": [
                    "Payments"
                ],
                "summary": "Payment confirmation (MTN MoMo, bank, other providers)",
                "operationId": "paymentWebhook",
                "description": "Header `X-LEC-Signature` = hex HMAC-SHA256 of the raw body with `LEC_PAYMENT_WEBHOOK_SECRET`. On `paid` the prepaid token is issued or the postpaid bill credited. If `amount` is sent it must equal the requested amount.",
                "parameters": [
                    {
                        "name": "provider",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "mtn_momo",
                                "orange_money",
                                "bank"
                            ]
                        }
                    },
                    {
                        "name": "X-LEC-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "payment_ref",
                                    "status"
                                ],
                                "properties": {
                                    "payment_ref": {
                                        "type": "string",
                                        "description": "Provider reference returned when the payment was started (bank: the customer's LEC payment reference)",
                                        "example": "LECX7Q000123"
                                    },
                                    "status": {
                                        "type": "string",
                                        "enum": [
                                            "paid",
                                            "failed"
                                        ]
                                    },
                                    "amount": {
                                        "type": "string",
                                        "example": "500.00"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Processed (idempotent)",
                        "content": {
                            "application/json": {
                                "example": {
                                    "ok": true,
                                    "status": "vended"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Invalid signature"
                    },
                    "404": {
                        "description": "Unknown payment"
                    },
                    "422": {
                        "description": "Amount mismatch"
                    }
                }
            }
        },
        "/webhooks/orange-money/{ref}": {
            "post": {
                "tags": [
                    "Payments"
                ],
                "summary": "Orange Money Web Payment notification",
                "operationId": "orangeNotify",
                "description": "Sent by Orange to the notif_url LEC supplied when creating the payment. Must contain the `notif_token` Orange issued for that payment; LEC then re-checks the status with Orange (transactionstatus) before releasing anything.",
                "parameters": [
                    {
                        "name": "ref",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "example": "VND-2026-000412"
                    }
                ],
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "status": {
                                        "type": "string"
                                    },
                                    "notif_token": {
                                        "type": "string"
                                    },
                                    "txnid": {
                                        "type": "string"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Processed"
                    },
                    "401": {
                        "description": "Unknown payment or wrong notif_token"
                    }
                }
            }
        },
        "/api/grid/public": {
            "get": {
                "tags": [
                    "Public"
                ],
                "summary": "Current generation, demand and active events (no authentication)",
                "operationId": "gridPublic",
                "description": "Same data as the animated power-flow on the website. `mode` is `simulated` until real telemetry is connected.",
                "responses": {
                    "200": {
                        "description": "Snapshot",
                        "content": {
                            "application/json": {
                                "example": {
                                    "mode": "simulated",
                                    "totals": {
                                        "capacity_mw": 175,
                                        "available_mw": 152,
                                        "demand_mw": 84,
                                        "active_events": 1
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/meters/verify": {
            "post": {
                "tags": [
                    "Called by LEC: vending gateway"
                ],
                "summary": "Validate a prepaid meter",
                "servers": [
                    {
                        "url": "{vendingBaseUrl}",
                        "description": "LEC_VENDING_BASE_URL",
                        "variables": {
                            "vendingBaseUrl": {
                                "default": "https://partner.example",
                                "description": "LEC_VENDING_BASE_URL"
                            }
                        }
                    }
                ],
                "requestBody": {
                    "content": {
                        "application/json": {
                            "example": {
                                "meter_no": "04100000043"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Result",
                        "content": {
                            "application/json": {
                                "example": {
                                    "valid": true,
                                    "customer_name": "Augustine C. Gbowee",
                                    "message": null
                                }
                            }
                        }
                    }
                }
            }
        },
        "/vend": {
            "post": {
                "tags": [
                    "Called by LEC: vending gateway"
                ],
                "summary": "Issue a credit token (idempotent per reference)",
                "servers": [
                    {
                        "url": "{vendingBaseUrl}",
                        "description": "LEC_VENDING_BASE_URL",
                        "variables": {
                            "vendingBaseUrl": {
                                "default": "https://partner.example",
                                "description": "LEC_VENDING_BASE_URL"
                            }
                        }
                    }
                ],
                "description": "Called only after LEC has confirmed payment. Repeating a reference must return the same token, never a new one.",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "example": {
                                "meter_no": "04100000043",
                                "amount": "500.00",
                                "currency": "LRD",
                                "reference": "VND-2026-000412"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Token",
                        "content": {
                            "application/json": {
                                "example": {
                                    "token": "12345678901234567890",
                                    "units_kwh": 11.1,
                                    "vend_ref": "V-998812"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/vend/{reference}": {
            "get": {
                "tags": [
                    "Called by LEC: vending gateway"
                ],
                "summary": "Look up a token already issued (recovery after a timeout)",
                "servers": [
                    {
                        "url": "{vendingBaseUrl}",
                        "description": "LEC_VENDING_BASE_URL",
                        "variables": {
                            "vendingBaseUrl": {
                                "default": "https://partner.example",
                                "description": "LEC_VENDING_BASE_URL"
                            }
                        }
                    }
                ],
                "parameters": [
                    {
                        "name": "reference",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Same body as POST /vend"
                    },
                    "404": {
                        "description": "No token for this reference"
                    }
                }
            }
        },
        "/tokens/engineering": {
            "post": {
                "tags": [
                    "Called by LEC: vending gateway"
                ],
                "summary": "Engineering tokens: key change, clear tamper, clear credit",
                "servers": [
                    {
                        "url": "{vendingBaseUrl}",
                        "description": "LEC_VENDING_BASE_URL",
                        "variables": {
                            "vendingBaseUrl": {
                                "default": "https://partner.example",
                                "description": "LEC_VENDING_BASE_URL"
                            }
                        }
                    }
                ],
                "requestBody": {
                    "content": {
                        "application/json": {
                            "example": {
                                "meter_no": "04100000043",
                                "type": "key_change",
                                "reference": "ENG-2026-000007"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Tokens in entry order",
                        "content": {
                            "application/json": {
                                "example": {
                                    "tokens": [
                                        "11112222333344445555",
                                        "66667777888899990000"
                                    ],
                                    "vend_ref": "E-1201"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/payments/{reference}": {
            "get": {
                "tags": [
                    "Called by LEC: bank"
                ],
                "summary": "Status of a reference payment (optional)",
                "servers": [
                    {
                        "url": "{bankBaseUrl}",
                        "description": "BANK_GATEWAY_BASE_URL",
                        "variables": {
                            "bankBaseUrl": {
                                "default": "https://partner.example",
                                "description": "BANK_GATEWAY_BASE_URL"
                            }
                        }
                    }
                ],
                "description": "Headers: `Authorization: Bearer BANK_GATEWAY_API_KEY`, `X-Merchant-Id: BANK_GATEWAY_MERCHANT_ID`. Banks without an API can instead call the payment webhook or send a daily CSV statement (`payment_ref,amount`) that Finance uploads.",
                "parameters": [
                    {
                        "name": "reference",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "example": "LECX7Q000123"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Status",
                        "content": {
                            "application/json": {
                                "example": {
                                    "status": "paid",
                                    "amount": "500.00"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/sms": {
            "post": {
                "tags": [
                    "Called by LEC: SMS"
                ],
                "summary": "Send one SMS",
                "servers": [
                    {
                        "url": "{smsUrl}",
                        "description": "SMS_HTTP_URL (full URL)",
                        "variables": {
                            "smsUrl": {
                                "default": "https://partner.example",
                                "description": "SMS_HTTP_URL (full URL)"
                            }
                        }
                    }
                ],
                "description": "Header `Authorization: Bearer SMS_HTTP_TOKEN`. Any 2xx response counts as accepted.",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "example": {
                                "to": "+231770000000",
                                "message": "LEC: \u2026",
                                "sender": "LEC"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Accepted"
                    }
                }
            }
        }
    }
}