meter.ng
Developer API

Build electricity payments into any product. Verify meters, purchase tokens, and automate recharges for every DISCO in Nigeria โ€” with one API.

โšก 11 DISCOs
๐Ÿ”„ Failsafe vend engine
๐Ÿ“ฑ SMS delivery
๐Ÿค– AutoPower
๐Ÿฆ Wallet + DVA
๐Ÿช Merchant network

The flow

Every electricity purchase follows three steps. Verify once, purchase forever.

1 โ€” Verify Meter
โ†’
2 โ€” Purchase Token
โ†’
3 โ€” Token by SMS
โ†’
AutoPower (optional)
โ„น

All amounts are in kobo. โ‚ฆ1,000 = 100000 kobo. This eliminates floating-point errors on financial amounts.

Base URL

https://api.meter.ng/api/v1

Quick start

# 1 โ€” Request OTP
curl -X POST https://api.meter.ng/api/v1/auth/request-otp \
  -H "Content-Type: application/json" \
  -d '{"phone":"+2348012345678"}'

# 2 โ€” Verify OTP โ†’ get accessToken
curl -X POST https://api.meter.ng/api/v1/auth/verify-otp \
  -H "Content-Type: application/json" \
  -d '{"phone":"+2348012345678","otp":"123456"}'

# 3 โ€” Verify a meter
curl -X POST https://api.meter.ng/api/v1/meters/verify \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"meterNumber":"04177243013","disco":"AEDC"}'

# 4 โ€” Purchase electricity
curl -X POST https://api.meter.ng/api/v1/tokens/purchase \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "meterNumber": "04177243013",
    "disco": "AEDC",
    "amount": 100000,
    "paymentMethod": "WALLET",
    "paymentReference": "ref-001",
    "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
    "accessToken": "TOKEN_FROM_VERIFY_STEP"
  }'
๐Ÿ’ก

Always use idempotency keys (UUID v4) on purchases. If your request times out and you retry with the same key, you get the original transaction back โ€” never a duplicate charge.

Authentication

meter.ng uses phone number + OTP. No passwords. Every session returns a short-lived accessToken (15 minutes) and a long-lived refreshToken (30 days).

Using tokens

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Lifecycle

Phone + OTP
โ†’
accessToken 15 min
+
refreshToken 30 days
โ†’
Refresh โ†’ new accessToken
โš 

Never store accessTokens in localStorage. Use secure in-memory storage or httpOnly cookies. AccessTokens can authorize real money transfers.

Auth Endpoints

POST
/auth/request-otp
Send a one-time code to a phone number. Works for new and existing users.

Body

FieldTypeDescription
phonestringrequiredE.164 format โ€” +2348012345678
200 OK400 VALIDATION_ERROR429 TOO_MANY_REQUESTS
{
  "success": true,
  "message": "Verification code sent to +234****78",
  "expiresIn": 600
}
POST
/auth/verify-otp
Verify the OTP. Returns auth tokens and creates a new account if the phone is new.

Body

FieldTypeDescription
phonestringrequiredSame phone used in request-otp
otpstringrequired6-digit code from SMS
namestringoptionalRequired for new registrations
200 OK400 OTP_INVALID400 OTP_EXPIRED
{
  "success": true,
  "isNewUser": false,
  "user": {
    "id": "0a43fcfb-6d4a-4126-a4b6-9113402db798",
    "phone": "+2348012345678",
    "name": "Ada Okafor",
    "role": "CUSTOMER"
  },
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
POST
/auth/refresh
Exchange a refresh token for a new access token.

Body

FieldTypeDescription
refreshTokenstringrequiredThe refresh token from verify-otp
200 OK401 INVALID_REFRESH_TOKEN

Meter Endpoints

Register a meter once โ€” your customer can recharge forever with a single call. Meter details come live from the DISCO at registration time.

POST
/meters/verify
Verify a meter against a DISCO. Returns customer details and a short-lived accessToken required for purchase.
๐Ÿ’ก

The accessToken returned is a DISCO-issued validation reference. It expires within minutes. Pass it immediately to /tokens/purchase.

Body

FieldTypeDescription
meterNumberstringrequired11โ€“13 digit meter number
discostringrequiredDISCO code โ€” see DISCO Reference
200 OK404 METER_NOT_FOUND503 SERVICE_UNAVAILABLE
{
  "success": true,
  "meter": {
    "meterNumber": "04177243013",
    "customerName": "OKAFOR ADA CHIDINMA",
    "address": "NO 5 GIMBIYA ST AREA 11 GARKI ABUJA",
    "tariff": "NON-MD",
    "minimumAmount": "900",
    "accessToken": "20260911001862199779",
    "disco": "AEDC"
  }
}
POST
/meters
Save a verified meter to the account. Enables one-tap future purchases and AutoPower.

Body

FieldTypeDescription
meterNumberstringrequired11โ€“13 digit meter number
discostringrequiredDISCO code
accessTokenstringrequiredFrom the verify step โ€” do not generate yourself
customerNamestringoptionalFrom verify response
addressstringoptionalFrom verify response
minimumAmountnumberoptionalIn naira, from verify response
nicknamestringoptional"Home", "Office", "Mum's place"
201 Created409 METER_ALREADY_REGISTERED
GET
/meters
List all meters on the account, with latest transaction per meter.
200 OK
{
  "success": true,
  "meters": [{
    "id": "cb74be0e-f40f-41d3-bd3d-d2b66255dbe5",
    "meterNumber": "04177243013",
    "disco": "AEDC",
    "customerName": "OKAFOR ADA CHIDINMA",
    "nickname": "Home",
    "autoPowerEnabled": true,
    "autoPowerAmountKobo": "500000",
    "minimumPurchaseKobo": "90000"
  }]
}

Token Endpoints

Purchase electricity tokens and track their delivery. The failsafe vend engine tries multiple providers in ranked order until one succeeds.

โ„น

Use a fresh UUID v4 as idempotencyKey on every purchase. Retrying with the same key returns the original transaction โ€” never a duplicate charge.

POST
/tokens/purchase
Purchase an electricity token. Triggers the multi-provider failsafe engine.

Body

FieldTypeDescription
meterNumberstringrequired11โ€“13 digit meter number
discostringrequiredDISCO code
amountnumberrequiredIn kobo. Min 50000 (โ‚ฆ500) ยท Max 10000000 (โ‚ฆ100k)
paymentMethodstringrequiredWALLET ยท CARD ยท VIRTUAL_ACCOUNT
paymentReferencestringrequiredYour Paystack reference, or any string for WALLET
idempotencyKeystringrequiredUUID v4. Generate fresh per purchase. Never reuse.
accessTokenstringrequiredFrom the verify step โ€” expires quickly. Verify and purchase in the same flow.
200 Delivered202 Processing400 BELOW_DISCO_MINIMUM402 INSUFFICIENT_BALANCE

Token delivered immediately

{
  "success": true,
  "transactionId": "ac6f30e7-dfa1-4368-a436-23de33b14c28",
  "token": "6311-2411-2060-2008-2022",
  "units": "117.6",
  "message": "Token sent to your phone."
}

Still processing (202)

{
  "success": true,
  "transactionId": "ac6f30e7-dfa1-4368-a436-23de33b14c28",
  "message": "Payment received. Token arrives shortly via SMS."
}
๐Ÿ’ก

On 202, poll GET /tokens/:id every 5 seconds โ€” or use the token.delivered webhook instead. Webhooks are more efficient.

GET
/tokens/:transactionId
Get the status and token for a transaction.
200 OK404 NOT_FOUND
{
  "success": true,
  "transaction": {
    "id": "ac6f30e7-dfa1-4368-a436-23de33b14c28",
    "status": "COMPLETED",
    "amountPaid": "100000",
    "disco": "AEDC",
    "meterNumber": "04177243013",
    "token": "6311-2411-2060-2008-2022",
    "units": "117.6",
    "tokenDeliveredAt": "2026-09-11T00:27:04.000Z"
  }
}

Transaction statuses

PAYMENT_CONFIRMEDPayment received, vending in progress
VENDINGActive request sent to provider
VENDING_PENDINGFirst attempt failed, retrying with next provider
COMPLETEDToken generated and delivered by SMS โœ“
FAILEDAll providers failed โ€” refund initiated
REQUERY_REQUIREDManual review needed โ€” contact support
GET
/tokens/history
Paginated transaction history. Query params: page, limit (max 100).
200 OK

Wallet

Every account has a NGN wallet backed by a dedicated Wema Bank virtual account. Fund it by bank transfer โ€” credits automatically within 30 seconds.

POST
/wallet/setup
Initiate wallet and DVA setup. Requires email for Paystack. DVA details arrive asynchronously โ€” poll GET /wallet until status is ready.

Body

FieldTypeDescription
emailstringrequiredValid email โ€” used by Paystack for the virtual account
200 pending / ready409 EMAIL_IN_USE
GET
/wallet
Get wallet balance and bank account details.
200 OK
{
  "success": true,
  "wallet": {
    "balanceKobo": "500000",
    "status": "ready",
    "accountNumber": "9816907903",
    "bankName": "Wema Bank",
    "accountName": "WWWMETERNG/ADA OKAFOR"
  }
}
โ„น

Transfer any amount to the account number from any Nigerian bank. A Paystack DVA fee of 1% (capped at โ‚ฆ300) applies per transfer.

AutoPower

After 3โ€“4 purchases, meter.ng calculates the customer's electricity burn rate and automatically recharges the meter at 75% depletion โ€” before they ever run out.

PATCH
/tokens/autopower/:meterId
Enable or disable AutoPower for a registered meter.

Body

FieldTypeDescription
enabledbooleanrequiredtrue to enable, false to disable
amountKobonumberoptionalAuto-recharge amount. Default: 500000 (โ‚ฆ5,000)
200 OK404 METER_NOT_FOUND

Merchants

Merchants are community agents who purchase electricity on behalf of customers, earn commission per transaction, and act as guarantors for Borrow Power loans.

POST
/merchants/apply
Apply to become a merchant. KYC review within 24 hours.

Body

FieldTypeDescription
businessNamestringrequiredTrading or business name
businessAddressstringrequiredPhysical location
ninstringrequired11-digit National ID Number
referralCodestringoptionalCode from the merchant who recruited you
201 Applied409 ALREADY_APPLIED
GET
/merchants/me
Get merchant profile โ€” tier, wallet balance, earnings, progress to next tier.
200 OK403 NOT_A_MERCHANT403 MERCHANT_NOT_APPROVED
{
  "success": true,
  "merchant": {
    "tier": "SILVER",
    "vendsThisMonth": 87,
    "walletBalanceKobo": "565500",
    "commissionRate": 6500,
    "referralCode": "ABCD123456",
    "nextTier": { "tier": "GOLD", "vendsNeeded": 113 }
  }
}

Commission tiers

TierMonthly VendsCommission
Bronze0โ€“50โ‚ฆ50 per vend
Silver51โ€“200โ‚ฆ65 per vend
Gold200+โ‚ฆ80 per vend
Sub-agent bonusโ€”+โ‚ฆ20 per vend from agents you recruited
POST
/merchants/vend
Buy electricity for any meter on behalf of a customer. Debits merchant wallet, credits commission instantly on success.

Body

FieldTypeDescription
meterNumberstringrequiredCustomer's meter number
discostringrequiredDISCO code
amountKobonumberrequiredAmount in kobo. Min 90000 (โ‚ฆ900)
accessTokenstringrequiredFrom verify step
idempotencyKeystringrequiredUUID v4
customerPhonestringoptionalSend token to customer's phone instead of yours
200 Delivered202 Processing402 INSUFFICIENT_MERCHANT_BALANCE

DISCO Reference

Use these exact codes. Case-sensitive.

AEDC
Abuja Electric
PHED
Port Harcourt Electric
IKEDC
Ikeja Electric
EKEDC
Eko Electric
IBEDC
Ibadan Electric
EEDC
Enugu Electric
JEDC
Jos Electric
KAEDCO
Kaduna Electric
BEDC
Benin Electric
YEDC
Yola Electric
ABA
Aba Power
โš 

Minimum purchase amounts vary per meter. Always use the minimumAmount from the verify endpoint โ€” never hardcode amounts.

Errors

All errors follow one shape:

{
  "success": false,
  "error": "ERROR_CODE",
  "message": "Human-readable description"
}

Error codes

CodeHTTPMeaning
VALIDATION_ERROR400Request body invalid
OTP_INVALID400Wrong OTP
OTP_EXPIRED400OTP expired โ€” request a new one
BELOW_DISCO_MINIMUM400Amount below DISCO minimum for this meter
METER_NOT_VERIFIED400accessToken missing โ€” verify meter first
UNAUTHORIZED401Missing or invalid Authorization header
FORBIDDEN403Insufficient role for this endpoint
NOT_A_MERCHANT403Merchant-only endpoint
MERCHANT_NOT_APPROVED403KYC pending or rejected
METER_NOT_FOUND404Meter not in DISCO database
TRANSACTION_NOT_FOUND404Transaction not found or not owned by this user
METER_ALREADY_REGISTERED409Meter already on this account
EMAIL_IN_USE409Email linked to a different account
INSUFFICIENT_WALLET_BALANCE402Wallet too low โ€” top up first
INSUFFICIENT_MERCHANT_BALANCE402Merchant wallet too low for this vend
SERVICE_UNAVAILABLE503All electricity providers temporarily unavailable

Webhooks

Register a webhook URL to receive real-time events. More efficient than polling โ€” especially for token delivery.

Security

Every delivery includes X-Meterng-Signature. Verify it before processing:

// Verify webhook signature (Node.js)
const crypto = require('crypto');

function verifyWebhook(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)  // must be the raw Buffer, not parsed JSON
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}
โš 

Return 200 OK within 5 seconds even if processing asynchronously. We retry failed deliveries up to 5 times with exponential backoff.

Events

EVENT
token.delivered
Fired when an electricity token is generated and sent to the customer's phone.
{
  "event": "token.delivered",
  "data": {
    "transactionId": "ac6f30e7-dfa1-4368-a436-23de33b14c28",
    "meterNumber": "04177243013",
    "disco": "AEDC",
    "token": "6311-2411-2060-2008-2022",
    "units": "117.6",
    "amountKobo": "300000",
    "deliveredAt": "2026-09-11T00:27:04.000Z"
  }
}
EVENT
wallet.credited
Fired when a bank transfer credits a wallet.
{
  "event": "wallet.credited",
  "data": {
    "userId": "0a43fcfb-6d4a-4126-a4b6-9113402db798",
    "amountKobo": "500000",
    "newBalanceKobo": "1000000",
    "creditedAt": "2026-09-11T02:49:52.000Z"
  }
}
EVENT
autopower.fired
Fired when AutoPower triggers an automatic recharge.
{
  "event": "autopower.fired",
  "data": {
    "meterId": "cb74be0e-f40f-41d3-bd3d-d2b66255dbe5",
    "meterNumber": "04177243013",
    "amountKobo": "500000",
    "transactionId": "550e8400-e29b-41d4-a716-446655440000",
    "firedAt": "2026-09-11T06:00:00.000Z"
  }
}