API reference

Products, plans, billing models, customers, subscriptions, payments, usage, and manual receipts.


All paths are under https://{host}/api/v2. Send X-API-Key and, when there is a body, Content-Type: application/json.

Typical success

json
{
  "success": true,
  "data": {},
  "message": "..."
}

A pricing-plan list returns { "pricing_plans": [] } and does not use success. Branch on the HTTP status, then read data for the id you need next.

Products

Create a product

POST/api/v2/biller/products/createHTTP 200

Creates the product, its first plans, and each plan's billing model. Message: Product created successfully.

Fiber Internet with four packages: Starter, Plus, Pro (fixed, advance; Plus default) and Flex (metered, arrears).

Request body

json
{
  "title": "Fiber Internet",
  "description": "Fiber packages for home and office",
  "category": "INTERNET",
  "callback_url": "https://hub.example/hooks/biller",
  "pricing_plans": [
    {
      "name": "Starter",
      "amount": 899,
      "billing_period": "MONTHLY",
      "is_default": false,
      "billing_model": { "model_type": "FIXED", "usage_source": "MANUAL" }
    },
    {
      "name": "Plus",
      "amount": 1499,
      "billing_period": "MONTHLY",
      "is_default": true,
      "penalty_max_cap": 200,
      "penalty_tiers": [
        {
          "penalty_type": "PERCENTAGE",
          "value": 2,
          "min_overdue_days": 7,
          "sort_order": 1
        }
      ],
      "billing_model": { "model_type": "FIXED", "usage_source": "MANUAL" }
    },
    {
      "name": "Pro",
      "amount": 2499,
      "billing_period": "MONTHLY",
      "is_default": false,
      "billing_model": { "model_type": "FIXED", "usage_source": "MANUAL" }
    },
    {
      "name": "Flex",
      "amount": 0,
      "billing_period": "MONTHLY",
      "is_default": false,
      "billing_model": {
        "model_type": "USAGE_BASED",
        "usage_source": "MANUAL",
        "usage_unit": "GB",
        "price_per_unit": 15
      }
    }
  ]
}
FieldRequiredMeaning
`title`yes3 to 100 characters.
`description`noAt most 255 characters.
`category`yesOne of the category values in Core concepts.
`callback_url`noHTTPS endpoint for events.
`image_url`noA URL.
`pricing_plans`yesAt least one plan. Each plan needs `billing_model`.

Other product calls

Related endpoints

MethodPathSuccessParameters
GET`/api/v2/biller/products/{product_id}`200Path: product UUID. Returns the product and its plans.
GET`/api/v2/biller/products/all`200Query: `page`, `page_size` (required). Optional: `text`, `category`, `billing_period`, `is_active`.
PUT`/api/v2/biller/products/{product_id}`200Body: `title`, `description`, `category`, `callback_url`, `is_active`.
DELETE`/api/v2/biller/products/{product_id}`200Message: `Product deleted successfully`.

Pricing plans

The first plan is created with the product. Add another with:

POST/api/v2/biller/products/{product_id}/pricing-plansHTTP 201

Pro Annual is twelve months of 100 Mbps, paid in advance (FIXED).

Request body

json
{
  "name": "Pro Annual",
  "amount": 24990,
  "billing_period": "YEARLY",
  "is_default": false,
  "billing_model": {
    "model_type": "FIXED",
    "usage_source": "MANUAL"
  }
}
FieldRequiredMeaning
`name`noEmpty becomes `Default`. At most 100 characters.
`amount`yesZero or greater. The charge for a `FIXED` plan.
`billing_period`yes`DAILY`, `WEEKLY`, `MONTHLY`, or `YEARLY`.
`recurring_interval`noAt least 1. Default 1.
`is_default`noWhether this is the default offer.
`penalty_tiers`noLate-fee windows. See Core concepts.
`penalty_max_cap`noMaximum late fee.
`billing_model`yes`model_type` and `usage_source` are required.

Related endpoints

MethodPathSuccessNotes
GET`/api/v2/biller/products/{product_id}/pricing-plans`200Response field `pricing_plans`.
PUT`/api/v2/biller/pricing-plans/{pricing_plan_id}`200Send only fields you change.
DELETE`/api/v2/biller/pricing-plans/{pricing_plan_id}`200Deactivates. Last active plan → HTTP 500.

Billing models

Created inside pricing_plans[].billing_model. Standalone create:

POST/api/v2/biller/billing-modelsHTTP 200

Request body

json
{
  "product_id": "{product_id}",
  "pricing_plan_id": "{pricing_plan_id}",
  "model_type": "USAGE_BASED",
  "usage_source": "MANUAL",
  "usage_unit": "GB",
  "price_per_unit": 15
}

FIXED is advance; USAGE_BASED, TIERED, HYBRID, MANUAL are arrears. For TIERED:

POST/api/v2/biller/billing-models/{billing_model_id}/tiers

Tiers body

json
{
  "tiers": [
    { "tier_min": 0, "tier_max": 20, "price_per_unit": 10, "tier_order": 1 },
    { "tier_min": 20, "price_per_unit": 20, "tier_order": 2 }
  ]
}

Related endpoints

MethodPathSuccessNotes
GET`/api/v2/biller/products/{product_id}/billing-model`200The model for the product.
GET`/api/v2/biller/billing-models/{billing_model_id}`200One model.
GET`/api/v2/biller/billing-models/usage-based`200Query: `page`, `page_size`.
PUT`/api/v2/biller/billing-models/{billing_model_id}`200Update model fields.
DELETE`/api/v2/biller/billing-models/{billing_model_id}`200Removes the model.

Customers

POST/api/v2/biller/customersHTTP 201

data.id is the customer id.

Request body

json
{
  "first_name": "Abel",
  "last_name": "Bekele",
  "phone_number": "251911000111",
  "email": "abel.bekele@example.com"
}
FieldRequiredMeaning
`phone_number`yes9 to 15 digits.
`first_name`, `last_name`no2 to 100 characters.
`email`noA valid email.

Related endpoints

MethodPathSuccessNotes
GET`/api/v2/biller/customers/{customer_id}`200Customer plus products, subscriptions, product users.
GET`/api/v2/biller/customers`200Query: `page` (> 0), `page_size`. Optional `phone_number`.
PUT`/api/v2/biller/customers/{customer_id}`200Name, phone, or email.
DELETE`/api/v2/biller/customers/{customer_id}`200A later get is `404`.

Subscriptions

POST/api/v2/biller/products-user/createHTTP 201

data.id is the product user id.

With an existing customer

Request body

json
{
  "product_id": "{product_id}",
  "pricing_plan_id": "{pricing_plan_id}",
  "customer_id": "{customer_id}",
  "unique_account_id": "HUB-10042",
  "phone_number": "251911000111",
  "subscribed_at": "2026-10-22",
  "send_checkout_notification": false,
  "subscription": {}
}

Or create the customer in the same call

Request body

json
{
  "product_id": "{product_id}",
  "pricing_plan_id": "{pricing_plan_id}",
  "unique_account_id": "HUB-10042",
  "phone_number": "251911000111",
  "subscribed_at": "2026-10-22",
  "send_checkout_notification": false,
  "customer": {
    "first_name": "Abel",
    "last_name": "Bekele",
    "phone_number": "251911000111"
  },
  "subscription": {}
}
FieldRequiredMeaning
`product_id`, `pricing_plan_id`yesUUIDs from create.
`unique_account_id`yesYour subscriber id, such as `HUB-10042`.
`phone_number`yes9 to 15 digits.
`subscribed_at`yes`YYYY-MM-DD`.
`customer_id` or `customer`one of themExisting customer, or a new one.
`subscription`yes`{}` renews until cancel. `duration` or `ends_at` is fixed term. `trial_ends_at` starts `TRIALING`.
`send_checkout_notification`no`false` when you deliver the checkout link yourself.
PUT/api/v2/biller/products-user/cancel/{product_user_id}HTTP 200

No body. Cancels the subscription.

PUT/api/v2/biller/products-user/resubscribe/{product_user_id}HTTP 200

Resubscribe body

json
{ "subscribed_at": "2026-10-22" }

Related endpoints

MethodPathSuccessNotes
GET`/api/v2/biller/products-user/{product_user_id}`200One subscription.
GET`/api/v2/biller/products-user/all/{product_id}`200Subscriptions on one product.
GET`/api/v2/biller/products-user/merchant/all`200Query: `page`, `page_size`.
PUT`/api/v2/biller/products-user/{product_user_id}`200Account id, subscribed day, or active flag.

Start checkout

POST/api/v2/biller/products-user/payment/initiateHTTP 200

Requires X-API-Key. data includes checkout_url, amount, due date, and names.

Request body

json
{
  "product_id": "{product_id}",
  "product_user_id": "{product_user_id}",
  "callback_url": "https://hub.example/hooks/biller"
}

Payments

You do not create the recurring bill with a POST. You read it.

Read endpoints

MethodPathSuccessNotes
GET`/api/v2/biller/payments`200Query: `page`, `page_size`. This merchant's bills.
GET`/api/v2/biller/payments/{payment_reference}`200One bill by reference.
GET`/api/v2/biller/payments/timeline/{payment_id}`200Lifecycle events for one payment.

Advance: due_date is period start. Arrears: period end. Both period start and end fields are present either way.

Usage

Record a quantity

POST/api/v2/biller/usage/recordHTTP 201

Post before an arrears period ends.

Request body

json
{
  "product_user_id": "{product_user_id}",
  "billing_period_start": "2026-10-01T00:00:00Z",
  "billing_period_end": "2026-11-01T00:00:00Z",
  "usage_quantity": 12.5,
  "usage_unit": "GB",
  "source_reference": "usage-2026-10"
}

Record a money amount

POST/api/v2/biller/usage/manualHTTP 201

manual_amount is required.

Request body

json
{
  "product_user_id": "{product_user_id}",
  "billing_period_start": "2026-10-01T00:00:00Z",
  "billing_period_end": "2026-11-01T00:00:00Z",
  "usage_unit": "etb",
  "manual_amount": 180,
  "source_reference": "adjustment-180"
}

Read usage

Read endpoints

MethodPathSuccessNotes
GET`/api/v2/biller/usage/{product_user_id}`200Readings for one subscription.
GET`/api/v2/biller/usage/product-user/period`200Query: ids + `period_start` / `period_end` as `YYYY-MM-DD`.
GET`/api/v2/biller/usage/billing-model/{billing_model_id}`200Readings for one model.

Manual payment banks

POST/api/v2/biller/manual-payment-banksHTTP 200

data.id is the bank id. bank_code, bank_name, and bank_account_number are required.

Request body

json
{
  "bank_code": "CBE",
  "bank_name": "Commercial Bank of Ethiopia",
  "bank_account_number": "1000123456789",
  "status": "ACTIVE"
}

Related endpoints

MethodPathSuccessNotes
GET`/api/v2/biller/manual-payment-banks`200This merchant's accounts.
GET`/api/v2/biller/manual-payment-banks/active`200Accounts that can take a new receipt.
PUT`/api/v2/biller/manual-payment-banks/{bank_id}`200Full bank body: code, name, account number, status.

Manual payments

POST/api/v2/biller/manual-payments/submitHTTP 200

The bill stays unpaid until approval.

Request body

json
{
  "payment_id": "{payment_id}",
  "bank_id": "{bank_id}",
  "amount": 100,
  "receipt_url": "https://files.example/receipts/cbe-1001.pdf",
  "receipt_file_name": "cbe-1001.pdf"
}
FieldRequiredMeaning
`payment_id`yesThe bill.
`bank_id`yesOne of your active banks.
`amount`yesThe amount on the receipt.
`receipt_url`yesA URL your staff can open.
`receipt_file_name`noThe file name you want stored.

Confirm / approve / reject / list

MethodPathBodyResult
POST`/api/v2/biller/manual-payments/confirm`Same as submit.Submits and approves. Bill marked paid.
POST`/api/v2/biller/manual-payments/{id}/approve`None.Approves pending receipt; bill paid.
POST`/api/v2/biller/manual-payments/{id}/reject``{ "reason": "..." }``reason` required. Bill stays unpaid.
GET`/api/v2/biller/manual-payments/pending`Receipts waiting for a decision.
GET`/api/v2/biller/manual-payments`This merchant's receipts.