Skip to main content
Demonstration environment — illustrative data, simulated grid telemetry and sandbox payments (no real charges, TEST tokens only).
LEC Liberia Electricity
Corporation
Home My Account Buy Electricity Pay Bill Outages & Restoration Report a Problem Track Request News FAQs Careers Tenders Contact About LEC Staff Login

Developers & partners

Integration API

How field sensors, smart meters, payment providers, the vending system, banks and SMS gateways connect to LEC. Version 1.3.0.

Integration points for field sensors, the smart-meter head-end (AMI), payment providers, the licensed STS vending system, partner banks and the SMS gateway.

All endpoints are read-only towards the grid: none can operate switches, generators or meters.

Secrets are configured in the server's .env file and exchanged with each partner out of band. Use HTTPS only.

Signing requests

Telemetry endpoints use a timestamped HMAC so captured requests cannot be replayed. Compute hex(HMAC-SHA256(secret, timestamp + "." + body)) over the exact bytes you send.

curl

TS=$(date +%s)
BODY='{"events":[{"meter_no":"04100000043","type":"power_off","occurred_at":"2026-10-26T19:52:04Z"}]}'
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -X POST https://lec.stoneatech.com/api/v1/meter-events \
  -H 'Content-Type: application/json' \
  -H "X-LEC-Timestamp: $TS" -H "X-LEC-Signature: $SIG" \
  --data "$BODY"

PHP

$body = json_encode(['events' => $events]);
$ts = time();
$sig = hash_hmac('sha256', $ts . '.' . $body, $secret);
$ch = curl_init('https://lec.stoneatech.com/api/v1/meter-events');
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body,
  CURLOPT_HTTPHEADER => ['Content-Type: application/json',
    "X-LEC-Timestamp: $ts", "X-LEC-Signature: $sig"]]);
curl_exec($ch);

Python

import hmac, hashlib, json, time, requests
body = json.dumps({'events': events})
ts = str(int(time.time()))
sig = hmac.new(secret.encode(), f'{ts}.{body}'.encode(), hashlib.sha256).hexdigest()
requests.post('https://lec.stoneatech.com/api/v1/meter-events', data=body,
  headers={'Content-Type': 'application/json',
           'X-LEC-Timestamp': ts, 'X-LEC-Signature': sig})

Payment callbacks sign the body only: hex(HMAC-SHA256(LEC_PAYMENT_WEBHOOK_SECRET, body)) in X-LEC-Signature.

Telemetry

Line sensors and the smart-meter head-end send data to LEC.

post/api/v1/sensors/{code}/readings

Line sensor reading or fault indication

Secret: LEC_SENSOR_SECRET. A reading with an event raises a fault on the sensor's feeder, alerts Dispatch/TND and SMSes affected customers.

ParameterInDescription
code*path e.g. S-F-PV2-1
X-LEC-Timestamp*headerUnix time in seconds.
X-LEC-Signature*headerHex HMAC-SHA256 of "{X-LEC-Timestamp}.{raw request body}" using the shared secret. Requests older than 5 minutes are rejected.

Request body

{
    "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 Reading stored{"ok":true,"event":null}
  • 201 Fault raised{"ok":true,"event":"FLT-2026-000031"}
  • 401 Missing or invalid signature / timestamp{"error":"unauthorised"}
  • 404 Unknown sensor code
post/api/v1/meter-events

Smart-meter events (last gasp, restore, tamper…)

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.

ParameterInDescription
X-LEC-Timestamp*headerUnix time in seconds.
X-LEC-Signature*headerHex HMAC-SHA256 of "{X-LEC-Timestamp}.{raw request body}" using the shared secret. Requests older than 5 minutes are rejected.

Request body

{
    "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 Accepted{"ok":true,"stored":4,"duplicates":0,"unknown":0,"outages":["OUT-2026-000002"]}
  • 401 Missing or invalid signature / timestamp{"error":"unauthorised"}
  • 422 Validation error
post/api/v1/meter-readings

Smart-meter register reads

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.

ParameterInDescription
X-LEC-Timestamp*headerUnix time in seconds.
X-LEC-Signature*headerHex HMAC-SHA256 of "{X-LEC-Timestamp}.{raw request body}" using the shared secret. Requests older than 5 minutes are rejected.

Request body

{
    "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 Accepted{"ok":true,"accepted":98,"flagged":1,"skipped":1,"unknown":0}
  • 401 Missing or invalid signature / timestamp{"error":"unauthorised"}

Payments

Payment providers confirm payments to LEC.

post/webhooks/payments/{provider}

Payment confirmation (MTN MoMo, bank, other providers)

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.

ParameterInDescription
provider*path
X-LEC-Signature*header

Request body

{
    "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 Processed (idempotent){"ok":true,"status":"vended"}
  • 401 Invalid signature
  • 404 Unknown payment
  • 422 Amount mismatch
post/webhooks/orange-money/{ref}

Orange Money Web Payment notification

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.

ParameterInDescription
ref*path e.g. VND-2026-000412

Request body

{
    "type": "object",
    "properties": {
        "status": {
            "type": "string"
        },
        "notif_token": {
            "type": "string"
        },
        "txnid": {
            "type": "string"
        }
    }
}

Responses

  • 200 Processed
  • 401 Unknown payment or wrong notif_token

Public

Open data for the website and partners.

get/api/grid/public

Current generation, demand and active events (no authentication)

Same data as the animated power-flow on the website. mode is simulated until real telemetry is connected.

Responses

  • 200 Snapshot{"mode":"simulated","totals":{"capacity_mw":175,"available_mw":152,"demand_mw":84,"active_events":1}}

Called by LEC: vending gateway

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.

post{vendingBaseUrl}/meters/verify

Validate a prepaid meter

Base URL from LEC_VENDING_BASE_URL

Request body

{
    "meter_no": "04100000043"
}

Responses

  • 200 Result{"valid":true,"customer_name":"Augustine C. Gbowee","message":null}
post{vendingBaseUrl}/vend

Issue a credit token (idempotent per reference)

Base URL from LEC_VENDING_BASE_URL

Called only after LEC has confirmed payment. Repeating a reference must return the same token, never a new one.

Request body

{
    "meter_no": "04100000043",
    "amount": "500.00",
    "currency": "LRD",
    "reference": "VND-2026-000412"
}

Responses

  • 200 Token{"token":"12345678901234567890","units_kwh":11.1,"vend_ref":"V-998812"}
get{vendingBaseUrl}/vend/{reference}

Look up a token already issued (recovery after a timeout)

Base URL from LEC_VENDING_BASE_URL

ParameterInDescription
reference*path

Responses

  • 200 Same body as POST /vend
  • 404 No token for this reference
post{vendingBaseUrl}/tokens/engineering

Engineering tokens: key change, clear tamper, clear credit

Base URL from LEC_VENDING_BASE_URL

Request body

{
    "meter_no": "04100000043",
    "type": "key_change",
    "reference": "ENG-2026-000007"
}

Responses

  • 200 Tokens in entry order{"tokens":["11112222333344445555","66667777888899990000"],"vend_ref":"E-1201"}

Called by LEC: bank

Optional status API of a partner bank for reference payments.

get{bankBaseUrl}/payments/{reference}

Status of a reference payment (optional)

Base URL from BANK_GATEWAY_BASE_URL

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.

ParameterInDescription
reference*path e.g. LECX7Q000123

Responses

  • 200 Status{"status":"paid","amount":"500.00"}

Called by LEC: SMS

Any SMS gateway accepting this JSON (SMS_DRIVER=http).

post{smsUrl}/sms

Send one SMS

Base URL from SMS_HTTP_URL (full URL)

Header Authorization: Bearer SMS_HTTP_TOKEN. Any 2xx response counts as accepted.

Request body

{
    "to": "+231770000000",
    "message": "LEC: …",
    "sender": "LEC"
}

Responses

  • 200 Accepted

Settlement statements

Providers and banks send a daily CSV with a header row: payment_ref,amount (amount with a dot decimal separator). Finance uploads it under Finance › Reconciliation. Lines for payments still awaiting confirmation confirm them.