> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chargeexpressltd.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Funding flow

> Quote a top-up, create a funding, send a person to the checkout, react to wallet.funded, and pay usage invoices.

Fleet charging is paid from the **fleet wallet**. The wallet holds prepaid energy in kWh (`balance_kwh`), and fleets with a credit line can also charge on credit, which is billed later as **usage invoices**.

Top-ups are paid through ExpressPay. The API cannot move money by itself: it creates a checkout, and **a person completes the payment** on the `checkout_url` page. Your system then hears about the result through webhooks.

You need a key with `wallet:write` (create fundings, pay invoices) and `fleet:read` (quotes, wallet, fundings, invoices).

> [!TIP]
>
> In the [sandbox](/guides/sandbox), nothing goes to ExpressPay. `POST /wallet/fundings` and `POST /invoices/usage/pay` with a sandbox key return a pending funding with `checkout_url: null` and `sandbox: true`. Settle it yourself with `POST /sandbox/fundings/{fundingId}/succeed` (the wallet is credited and `wallet.funded` fires) or `/fail` (`wallet.funding_failed` fires). Sandbox top-ups are never invoiced, so `invoice_number` stays `null`.

## 1. Check the wallet

```bash theme={null}
curl https://partners.chargeexpressltd.com/api/public/v1/wallet \
  -H "Authorization: Bearer $CHARGEEXPRESS_API_KEY"
```

```json Response theme={null}
{
  "object": "wallet",
  "id": "w2e9r4t7y1u6i3o8p5a0s4df",
  "balance_kwh": "412.5000",
  "reserved_kwh": "40.0000",
  "available_kwh": "372.5000",
  "debt_kwh": "0.0000",
  "debt": "0.00",
  "credit_limit": "2000.00",
  "credit_available": "2000.00",
  "currency": "GHS",
  "updated_at": "2026-10-13T09:02:11.000Z",
  "unit_price": "3.85",
  "max_charging_kwh": "891.9805"
}
```

`reserved_kwh` is held by sessions in progress. `debt` is credit used and not yet repaid. `max_charging_kwh` is roughly how much energy the wallet can cover right now, prepaid and credit together.

To be told when the balance runs low instead of polling, set a threshold with `PATCH /fleet` (`{"low_balance_threshold_kwh": "500"}`) and subscribe to [`wallet.low_balance`](/webhooks/events#wallet-low-balance).

## 2. Quote the top-up

```bash theme={null}
curl "https://partners.chargeexpressltd.com/api/public/v1/wallet/fundings/quote?amount=500" \
  -H "Authorization: Bearer $CHARGEEXPRESS_API_KEY"
```

```json Response theme={null}
{
  "object": "funding_quote",
  "amount": "500.00",
  "currency": "GHS",
  "unit_price": "3.85",
  "kwh": "129.8701",
  "max_charging_kwh": "891.9805"
}
```

`kwh` is how much energy the amount buys at the fleet's current `unit_price`. A quote is informational: the price is fixed when you create the funding.

## 3. Create the funding

```bash theme={null}
curl https://partners.chargeexpressltd.com/api/public/v1/wallet/fundings \
  -H "Authorization: Bearer $CHARGEEXPRESS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: topup-2026-10-13-001" \
  -d '{ "amount": 500 }'
```

`amount` is a JSON **number** in GHS with at most 2 decimal places. `Idempotency-Key` is required, and it also fixes the funding's id, so use a new key for each top-up (see [Idempotency](/idempotency#top-ups-and-the-idempotency-key)).

```json Response (201) theme={null}
{
  "object": "funding",
  "id": "wallet-6f1d2c3a-9b8e-5c4d-a7f6-0e1d2c3b4a59",
  "status": "pending",
  "amount": "500.00",
  "currency": "GHS",
  "unit_price": "3.85",
  "kwh": "129.8701",
  "credited_kwh": null,
  "debt_repaid": null,
  "invoice_number": null,
  "checkout_url": "https://expresspay.example/api/checkout.php?token=7311a1c6f3e04d0e9b8e5a2c",
  "credited_at": null,
  "created_at": "2026-10-13T10:00:00.000Z",
  "updated_at": "2026-10-13T10:00:01.000Z"
}
```

> [!NOTE]
>
> The person who pays is identified to ExpressPay with the name, email and phone number of the fleet manager **who created the API key**. That account needs a phone number, or the request fails with `400 bad_request`. If ExpressPay can't start a checkout you get `503 service_unavailable` and nothing is charged.

## 4. Send a person to `checkout_url`

Show or send the `checkout_url` to whoever pays for your fleet: open it in their browser, put it in an approval email, or link it from your finance tool. They complete the payment on ExpressPay's page. Treat the URL as opaque.

`checkout_url` is only present while the funding is `pending`. If someone loses the link, `GET /wallet/fundings/{fundingId}` returns it again.

## 5. Wait for `wallet.funded`

When ExpressPay confirms the payment, the wallet is credited and ChargeExpress sends [`wallet.funded`](/webhooks/events#wallet-funded) with the funding:

```json data.object theme={null}
{
  "object": "funding",
  "id": "wallet-6f1d2c3a-9b8e-5c4d-a7f6-0e1d2c3b4a59",
  "status": "success",
  "amount": "500.00",
  "currency": "GHS",
  "unit_price": "3.85",
  "kwh": "129.8701",
  "credited_kwh": "129.8701",
  "debt_repaid": "0.00",
  "invoice_number": null,
  "checkout_url": null,
  "credited_at": "2026-10-13T10:04:37.000Z",
  "created_at": "2026-10-13T10:00:00.000Z",
  "updated_at": "2026-10-13T10:04:37.000Z"
}
```

If the payment fails you get [`wallet.funding_failed`](/webhooks/events#wallet-funding-failed) with `status: "failed"`. Treat `failed` and `expired` as final.

**Outstanding credit is repaid first.** If the fleet owes money when a top-up is paid, that part of the amount repays the debt (`debt_repaid`, oldest usage invoices first) and only the rest is credited as kWh (`credited_kwh`). Each usage invoice that becomes fully repaid sends `invoice.paid`.

> [!TIP]
>
> No webhook endpoint yet? Poll `GET /wallet/fundings/{fundingId}`. While a funding is pending, that call also asks ExpressPay for the latest status, so it picks up payments even if a callback was missed.

Once ChargeExpress has issued the invoice for the purchase, you get [`invoice.created`](/webhooks/events#invoice-created) with a **purchase invoice**. Its `id` is the funding id, and `invoice_number` is also set on the funding. Download it with `GET /invoices/purchase/{fundingId}/pdf`.

## Pay usage invoices

When sessions run on credit, each one gets a usage invoice whose `id` is the **session id**. List the unpaid ones:

```bash theme={null}
curl "https://partners.chargeexpressltd.com/api/public/v1/invoices?kind=usage&status=unpaid" \
  -H "Authorization: Bearer $CHARGEEXPRESS_API_KEY"
```

```json Response theme={null}
{
  "data": [
    {
      "object": "invoice",
      "kind": "usage",
      "id": "48177",
      "session_id": "48177",
      "charge_point_id": "CX-ACC-0007",
      "invoice_number": "ACC-SINV-2026-00412",
      "status": "unpaid",
      "billed_kwh": "22.1000",
      "unit_price": "3.85",
      "amount": "85.09",
      "amount_due": "85.09",
      "currency": "GHS",
      "issued_at": "2026-10-11T16:20:05.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

Then pay up to 50 of them in one checkout:

```bash theme={null}
curl https://partners.chargeexpressltd.com/api/public/v1/invoices/usage/pay \
  -H "Authorization: Bearer $CHARGEEXPRESS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pay-usage-2026-10-13" \
  -d '{ "invoice_ids": ["48177"] }'
```

The response is a **funding** with a `checkout_url`, exactly as in step 3. The amount defaults to the invoices' total `amount_due`; pass `amount` to pay part of it. When the payment completes, the selected invoices are repaid first, `wallet.funded` fires, and each invoice that is now fully paid sends `invoice.paid`.

| Error | Why |
| - | - |
| `400 invoice_not_payable` | An id isn't an unpaid usage invoice of your fleet. |
| `400 amount_too_large` | `amount` is more than the selected invoices' outstanding total. |

Download any invoice as a PDF with `GET /invoices/{kind}/{invoiceId}/pdf`, where `kind` is `usage` or `purchase`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.