Docs menuSell airtime, data and ZESA

Start

Sell airtime, data and ZESA

Put every network and every meter on your till, POS or app, in USD and ZiG: check, sell, tell the cashier, print the receipt, close the day.

Put Econet, NetOne and Telecel airtime, data bundles and ZESA tokens on your till, in your POS software or in your app. Your customer pays you; Jusa sends the airtime, bundle or token in seconds and debits your prepaid Jusa Credit. Every network and every meter, in USD and ZiG, through one API, so you never hold airtime stock or a stack of provider accounts.

Margin

Buy below faceSell at face value. On the Dealer tier your Jusa Credit is debited less than face, per network, at the moment of the sale: the margin is yours on every sale, with nothing to claim later.

Safety

Never sell twiceKey each sale on your till's own sale number. A dropped connection, a retry or a double tap is the same sale, never a second one.

Books

Ties to the cash drawerEvery sale carries its till and its cost. The day's sales per till, and a monthly statement of face sold, credit drawn and discount earned.

What POS vendors ask us

Is there an API?Yes: REST and JSON at https://jusa.localhost.co.zw/dev/v1, signed webhooks, a sandbox, and SDKs for Python, Node, PHP, Go, Java and Kotlin, .NET, Dart and Ruby.
What do we earn?A business we have verified buys below face on the Dealer tier: a percentage off face per network and currency, set when you are approved, taken as a lower debit when each sale is made. Without it you buy at face value, with no fees, which suits apps that sell at cost or reward users.
What are the terms?Apply from the dashboard or with POST /dealer/application and upload your company documents (incorporation certificate, directors, a director's ID, proof of address, bank letter, tax clearance). Jusa staff review them and set your rates. How the Dealer tier works.
How is a sale's status reported?Every sale has one status, a webhook for each change, and a promise for what happens to its money. The table below says what the cashier does for each.
USD and ZiG?Both. Products come in each currency, and USD and ZWG Jusa Credit are separate balances with no exchange between them.
How do we fund it?Prepaid Jusa Credit, topped up any time by card or bank transfer; dealers can have cash collected by Jusa staff. Set a low-balance alert so a till never runs dry.

A sale, step by step

Everything below runs in test mode first with a test key: test numbers act out every outcome, and nothing real moves until you switch to a live key. The samples use the client you make once:

Set up once

curl

export JUSA_SECRET_KEY=jusa_sk_test_…   # a test key: nothing real moves
  1. Give each till its own key

    A restricted key per till (or per branch) holds only what a till needs, with spend caps so a lost device cannot empty your balance. Its id also tags every sale it makes, which is how you close each till's day. Add allowed_ips if your tills sit behind fixed addresses.

    Once per till

    curl

    curl https://jusa.localhost.co.zw/dev/v1/keys \
      --fail-with-body \
      -H "Authorization: Bearer $JUSA_SECRET_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "kind": "rk",
        "label": "till-4",
        "scopes": [
          "catalog:read",
          "lookups:read",
          "sends:read",
          "sends:write",
          "tokens:read"
        ],
        "spend_caps": {"USD": {"per_send": "50.00", "daily": "1500.00"}}
      }'
    
    # Shown once: put it straight into the till's configuration.
    
  2. Load the catalogue

    Products, their networks, currency, and the smallest and largest amount each takes. Keep it on the till and refresh it daily; available goes false when a product is paused.

    Daily

    curl

    # Once a day, or when a sale is refused as product_unavailable:
    curl -G https://jusa.localhost.co.zw/dev/v1/products \
      --fail-with-body \
      -H "Authorization: Bearer $JUSA_SECRET_KEY" \
      -d currency=USD
    
  3. Check before the customer pays

    For airtime and data, a phone lookup says which network the number is on and writes it properly; it asks no provider, so it is instant. For ZESA, a quote confirms the meter with ZETDC and gives the name and address to read back to the customer, the units their money buys, and your cost.

    Airtime and data

    curl

    # Before the customer pays: the network, and the number written properly.
    curl -G https://jusa.localhost.co.zw/dev/v1/lookups/phone \
      --fail-with-body \
      -H "Authorization: Bearer $JUSA_SECRET_KEY" \
      -d phone=0771230000
    
    ZESA

    curl

    # Read the name back to the customer before they pay:
    curl https://jusa.localhost.co.zw/dev/v1/quotes \
      --fail-with-body \
      -H "Authorization: Bearer $JUSA_SECRET_KEY" \
      -H "Content-Type: application/json" \
      -d '{"product": "zesa-usd", "target": "37261502217", "amount": "20.00"}'
    
  4. Make the sale

    Use your till's sale number as both client_reference and Idempotency-Key. If the connection drops, send the same request again: you get the first answer back, and the customer is charged once. metadata carries your own fields (till, cashier, branch) onto the sale, its webhooks and your exports.

    Airtime

    curl

    # The till's sale number keys it: sent twice, it is one sale.
    curl https://jusa.localhost.co.zw/dev/v1/sends \
      --fail-with-body \
      -H "Authorization: Bearer $JUSA_SECRET_KEY" \
      -H "Idempotency-Key: sale-T4-000187" \
      -H "Content-Type: application/json" \
      -d '{
        "product": "airtime-usd",
        "target": "0771230000",
        "amount": "2.00",
        "client_reference": "sale-T4-000187",
        "metadata": {"till": "4"}
      }'
    
    ZESA

    curl

    # Passing the quote carries its meter confirmation; the token is texted to token_phone too.
    curl https://jusa.localhost.co.zw/dev/v1/sends \
      --fail-with-body \
      -H "Authorization: Bearer $JUSA_SECRET_KEY" \
      -H "Idempotency-Key: sale-T4-000187" \
      -H "Content-Type: application/json" \
      -d '{
        "quote": "quo_…",
        "target": "37261502217",
        "amount": "20.00",
        "currency": "USD",
        "token_phone": "0772123456",
        "client_reference": "sale-T4-000187",
        "metadata": {"till": "4"}
      }'
    

    The answer is 202 with "status": "pending": the sale is written and your Jusa Credit debited by its cost (below face on the Dealer tier). The customer still receives the full face value.

  5. Tell the cashier

    Most sales are delivered within seconds. Your webhook hears send.delivered or send.failed; while the customer is at the counter, you can also ask for the sale every couple of seconds.

    While the customer waits

    curl

    # While the customer waits; your webhook hears send.delivered as well.
    curl https://jusa.localhost.co.zw/dev/v1/sends/snd_… \
      --fail-with-body \
      -H "Authorization: Bearer $JUSA_SECRET_KEY"
    
    StatusAt the counterYour Jusa Credit
    pending, submittedSending. Ask the customer to wait a moment.Debited
    deliveredDone. Print the receipt; for ZESA, the token and units are on it.Spent
    queuedZESA is down nationally. The token goes out when ZESA is back and is texted to the customer's phone; they need not wait.Debited
    retryingThe network is busy. Jusa keeps trying; the customer gets an SMS when it lands and need not wait.Debited
    unknown, requires_reviewThe network has not answered. Do not sell it again: it may have landed. Jusa settles it, usually within minutes.Debited
    failed_creditedIt did not go through. Give the customer their money back or sell again; failure_code says why (a wrong number, a blocked meter).Returned

    Each state's guarantees and timings are in settlement and money; every failure_code is in errors.

  6. Print the receipt

    The receipt has the network, the number or meter, the amount and, for ZESA, the token and units. Read the token in the clear with the tokens:read scope; without it the token is masked. If the customer loses the SMS, POST /sends/{id}/resend-token texts it again.

    After delivered

    curl

    # JSON for your own slip, or ?format=pdf for ours:
    curl https://jusa.localhost.co.zw/dev/v1/sends/snd_…/receipt \
      --fail-with-body \
      -H "Authorization: Bearer $JUSA_SECRET_KEY"
    
  7. Close the day

    List a till's sales by its key and the day to tie them to the cash drawer, and pull the monthly statement for your books. On the Dealer tier the statement adds face_sold, cost_drawn and dealer_discount_earned; it is also a CSV and a PDF.

    End of day and month

    curl

    # One till's sales, newest first: page back to the start of the day and tie them to the drawer.
    curl -G https://jusa.localhost.co.zw/dev/v1/sends \
      --fail-with-body \
      -H "Authorization: Bearer $JUSA_SECRET_KEY" \
      -d api_key=key_… \
      -d limit=100
    
    # The month: face value sold, credit drawn and the dealer discount earned.
    curl -G https://jusa.localhost.co.zw/dev/v1/statements \
      --fail-with-body \
      -H "Authorization: Bearer $JUSA_SECRET_KEY" \
      -d period=2026-10 \
      -d currency=USD
    

Going live

  • Rehearse every row of the status table with the test numbers: …0000 delivers, …0002 fails and credits back, …0005 is unknown, then delivered.
  • Add a webhook endpoint and verify its signatures.
  • Set a low-balance alert (PUT /credit/alerts) so you top up before a till runs dry.
  • Apply for the Dealer tier if you resell, and accept the API terms with your first live key.

Questions about volumes or rates: jusa@localhost.co.zw.