Meter charges
Choose a package for the wallet, confirm its total with the customer and request the charge. The charge continues after acceptance. Keep its operation id until you know whether it succeeded and, if it failed, whether money returned.
Choose a package
Sign GET /v1/wallets/{walletId}/meter-packages. It requires meter_charges:read, also included in meter_charges:create. The answer is unsigned. Pass nextCursor unchanged as cursor; the final page has nextCursor: null.
{
"items": [
{
"id": "5f3b8c21-7a0d-4e96-b1c4-2d8e6f9a0b37",
"name": "باقة 100 دينار",
"gecolPackageName": "PKG_100",
"logo": "https://cdn.anis.ly/meter/packages/100.png",
"unitPrice": {
"amount": "97.500",
"currency": "LYD"
},
"businessPrice": {
"amount": "97.500",
"currency": "LYD"
},
"personalPrice": {
"amount": "100.000",
"currency": "LYD"
}
}
],
"nextCursor": null
}
unitPrice is what this wallet pays. Send that Money value as expectedTotal. personalPrice is the retail display price; businessPrice appears only when applicable. The package’s name is one stored name, not a bilingual object. Do not calculate the payable total from the other display prices.
Request a charge
Save a new UUID and the exact body before sending. Use that UUID as Idempotency-Key; it is also the charge’s operationId in reads, notifications and references. Sign with the create profile: body digest, fresh nonce and idempotency key. Requires meter_charges:create.
A temporarily missing read does not prove a charge was never created. Retry the exact saved body with the same id. A duplicate remains 202 processing during lag; if the scoped read is still missing after a duplicate and 60 seconds since admission, Anis closes the retry with idempotency_conflict, 409. That code does not prove nothing was charged: the first request may already have been accepted, so read the operation before any new attempt.
POST /v1/wallets/{walletId}/meter-charges
Idempotency-Key: 3c1e9b7a-0d42-4f5e-8a61-2b9c7d0e5f13
{
"packageId": "5f3b8c21-7a0d-4e96-b1c4-2d8e6f9a0b37",
"meterNumber": "0268000710262",
"expectedTotal": {
"amount": "97.500",
"currency": "LYD"
},
"externalReference": "POS-77-INV-1093"
}
A signed 202 reports acceptance or continuing uncertainty; it is not a token. An accepted charge can answer as follows, with Location: /v1/meter-charges/{operationId}:
{
"operationId": "3c1e9b7a-0d42-4f5e-8a61-2b9c7d0e5f13",
"operationType": "meterCharge",
"status": "processing"
}
Verify the signed answer before using it. If it is lost, cannot be verified or says processing, read the same operation or retry the exact saved body with the same key. Sign every attempt afresh. Never create a second charge just because the first answer is late. An unreachable meter check is an unknown outcome, not a definitive refusal.
Read the result and tokens
GET /v1/meter-charges/{operationId} requires read or create permission. Sign it as a read and verify its signed answer. Another application’s charge looks exactly like a missing charge. A processing read has the charge’s head fields and no receipt. A successful read carries:
{
"operationId": "3c1e9b7a-0d42-4f5e-8a61-2b9c7d0e5f13",
"status": "succeeded",
"walletId": "71825266-7182-4182-8182-718252667182",
"meterNumber": "0268000710262",
"package": {
"id": "5f3b8c21-5f3b-4c21-8f3b-5f3b8c215f3b",
"name": "باقة 100 دينار",
"gecolPackageName": "PKG_100"
},
"total": {
"amount": "97.500",
"currency": "LYD"
},
"externalReference": "POS-77-INV-1093",
"createdAt": "2026-10-20T08:12:44Z",
"completedAt": "2026-10-20T08:13:02Z",
"receipt": {
"receiptNumber": "260020103744",
"creditedAmount": "88.640",
"tokens": [
{
"sequence": 1,
"label": "KCT1",
"code": "1234 5678 9012 3456 7890",
"description": "Key change token 1"
},
{
"sequence": 2,
"label": "KCT2",
"code": "2345 6789 0123 4567 8901",
"description": "Key change token 2"
},
{
"sequence": 3,
"label": "STS",
"code": "5821 0034 7719 2265 0418",
"description": "Credit token"
}
],
"taxes": [
{
"description": "ضريبة الاستهلاك",
"amount": "5.000"
},
{
"description": "رسوم الخدمة",
"amount": "3.860"
}
]
},
"personalPrice": {
"amount": "100.000",
"currency": "LYD"
}
}
Show every token in ascending sequence: key-change tokens first, then the credit token. Preserve label, code and description, including leading zeros and spaces; do not show only the last token. A token works only on the meter charged. receiptNumber, creditedAmount and taxes are the electricity supplier’s values. They need not equal the wallet total. Receipt amount strings pass through unchanged; Money’s three-decimal scale is not promised for them.
A read can also answer a refusal that was recorded for the create under this id, with Idempotency-Replayed: true: 409 (insufficient_balance, price_changed or package_unavailable) or 422 (meter_rejected). These refusals happen before money moves: nothing was charged. On a read, idempotency_conflict (409) means no meter charge of your application exists under this id; use a new Idempotency-Key only for a genuinely new operation.
Find my operations
Sign GET /v1/wallets/{walletId}/meter-charges with meter_charges:read or meter_charges:create. The answer is unsigned. Only this application’s operations in the selected authorized wallet are returned, newest first. Optional filters, which combine: status (processing, succeeded or failed), meterNumber (exact match, 1 to 64 ASCII digits; keep leading zeros), from and to (UTC), cursor and pageSize. Each window is at most 31 days; omitted dates mean the last 31 days. The page size defaults to 50 and is at most 100. Pass nextCursor unchanged with the same filters and pageSize until it is null. An empty result has items: [] and nextCursor: null. After validation_failed (422), correct the filters or cursor; do not repeat a purchase to recover a list. Receipts and every token remain on the signed charge read.
{
"items": [
{
"operationId": "3c1e9b7a-0d42-4f5e-8a61-2b9c7d0e5f13",
"status": "succeeded",
"meterNumber": "0268000710262",
"package": {
"id": "5f3b8c21-7a0d-4e96-b1c4-2d8e6f9a0b37",
"name": "باقة 100 دينار",
"gecolPackageName": "PKG_100"
},
"total": {
"amount": "97.500",
"currency": "LYD"
},
"externalReference": "POS-77-INV-1093",
"createdAt": "2026-10-20T08:12:44Z",
"completedAt": "2026-10-20T08:13:02Z"
}
],
"nextCursor": null
}
Failure and money returned
Every state includes total and personalPrice. completedAt appears only on success. Electricity company tax descriptions stay in Arabic exactly as supplied.
A read can show status: "failed" while money is still being returned. Check refund.status before telling the customer money is back.
{
"operationId": "3c1e9b7a-0d42-4f5e-8a61-2b9c7d0e5f13",
"status": "failed",
"walletId": "71825266-7182-4182-8182-718252667182",
"meterNumber": "0268000710262",
"package": {
"id": "5f3b8c21-5f3b-4c21-8f3b-5f3b8c215f3b",
"name": "باقة 100 دينار",
"gecolPackageName": "PKG_100"
},
"total": {
"amount": "97.500",
"currency": "LYD"
},
"externalReference": "POS-77-INV-1093",
"createdAt": "2026-10-20T08:12:44Z",
"failure": {
"code": "meter_not_whitelisted",
"message": {
"ar": "العداد غير مسجل",
"en": "Meter not enrolled"
}
},
"refund": {
"status": "refunded",
"amount": {
"amount": "97.500",
"currency": "LYD"
}
},
"personalPrice": {
"amount": "100.000",
"currency": "LYD"
}
}
refund.status |
Meaning |
|---|---|
refunded |
The money is back |
notCharged |
The wallet refused payment; nothing was taken |
pending |
Money has not returned yet; Anis staff are alerted |
failure.message carries Arabic and English display messages. Branch on failure.code. These values belong to a charge read; they are not HTTP problem codes.
failure.code |
Meaning |
|---|---|
meter_number_invalid |
Meter number invalid |
meter_not_whitelisted |
Meter not enrolled |
meter_not_linked |
Meter not linked |
meter_removed |
Meter removed |
meter_inactive |
Meter inactive |
meter_account_blocked |
Account blocked |
meter_data_incomplete |
Meter data incomplete |
meter_debt_exceeds_package |
Debt exceeds package |
package_unavailable |
Package unavailable |
package_limit_reached |
Package limit reached |
token_not_written |
Token not written |
insufficient_balance |
Insufficient balance |
gecol_unavailable |
The electricity company did not answer in time |
charge_failed |
Charge failed |
A meterCharge.failed notification is sent only after the refund lands or when nothing was taken. A pending refund can be visible in the read before a failed notification is sent. Success notifications contain the full read body, including every token.
Refused before money moves
| Code | HTTP | What to do |
|---|---|---|
meter_rejected |
422 |
Check the meter number |
package_unavailable |
409 |
Read current packages |
price_changed |
409 |
Confirm the current payable price again |
insufficient_balance |
409 |
Make funds available |
spending_limit_exceeded |
429 |
Wait for the cap reset given by Retry-After |
idempotency_conflict |
409 |
Restore a changed saved body; read the operation first, because it may already be charged; use a fresh id only for a genuinely new operation |
After a definitive refusal closes the operation, a corrected new charge needs a new id. An access, signature, rate or transport failure does not prove an earlier attempt failed. Meter charges share the application’s purchase limit and daily spending cap with card orders and direct recharges.
idempotency_conflict and replay_detected do not say the first request failed: it may already have been accepted and charged, so never assume nothing was charged. Read the original operation with GET /v1/meter-charges/{operationId} before any new attempt. To repeat the same intent after replay_detected, keep the Idempotency-Key and send a fresh nonce and signature. After idempotency_conflict, reuse the key only for identical intent and use a new Idempotency-Key only for a genuinely new operation. insufficient_balance, price_changed and the unavailable-product refusals happen before money moves: nothing was charged.
Receive outcomes through webhooks and verify each notification before processing it.
Packages · Create · Read · Find my operations
A pending refund is visible in the read, but has no sendable notification yet:
{
"operationId": "3c1e9b7a-0d42-4f5e-8a61-2b9c7d0e5f13",
"status": "failed",
"walletId": "71825266-7182-4182-8182-718252667182",
"meterNumber": "0268000710262",
"package": {
"id": "5f3b8c21-5f3b-4c21-8f3b-5f3b8c215f3b",
"name": "باقة 100 دينار",
"gecolPackageName": "PKG_100"
},
"total": {
"amount": "97.500",
"currency": "LYD"
},
"externalReference": "POS-77-INV-1093",
"createdAt": "2026-10-20T08:12:44Z",
"failure": {
"code": "meter_not_whitelisted",
"message": {
"ar": "العداد غير مسجل",
"en": "Meter not enrolled"
}
},
"refund": {
"status": "pending",
"amount": {
"amount": "97.500",
"currency": "LYD"
}
},
"personalPrice": {
"amount": "100.000",
"currency": "LYD"
}
}