Start

SDKs and tools

Python, Node, the jusa CLI, Postman, the OpenAPI document and MCP.

Python: jusa

No dependencies beyond the standard library; Python 3.9 and later.

pip install jusa
import jusa
jusa.api_key = "jusa_sk_test_…"
send = jusa.Send.create(product="airtime-usd", target="0772123456", amount="1.00",
                        client_reference="order-8812")

client = jusa.Jusa("jusa_sk_test_…")                     # or one client per key
for s in client.sends.list(status="delivered", created={"gte": 1759230000}).auto_paging_iter():
    print(s.id, s.amount)

try:
    client.sends.create(target="0772123456", amount="500.00")
except jusa.InsufficientCreditError as e:
    print(e.code, e.request_id)
  • Every endpoint is a method: client.<resource>.<action>(ids…, **params), generated from the OpenAPI document. Each resource is also a class on the module: jusa.Reward.create(…).
  • Idempotency keys are automatic on every operation that takes one, and reused across that call's retries. They only protect retries inside one process: pass idempotency_key= (an order or submission id) to be safe across restarts.
  • Retries on connection errors, 429 and 5xx, with backoff and Retry-After (max_retries=2).
  • Typed errors: InvalidRequestError, AuthenticationError, InsufficientCreditError, PermissionDeniedError, NotFoundError, ConflictError, IdempotencyError, RateLimitError, ProviderUnavailableError, APIError, APIConnectionError, all jusa.JusaError with code, param and request_id.
  • jusa.Webhook.construct_event(raw_body, signature_header, secret, tolerance=300) verifies and parses a delivery; event.id is what you dedupe on.
  • Parameters named like Python keywords take a trailing underscore: from_="2026-09-01".

Node and TypeScript: @jusa/node

Node 18 and later, no dependencies, typed from the OpenAPI document.

npm install @jusa/node
import Jusa, { InsufficientCreditError } from '@jusa/node';
const jusa = new Jusa(process.env.JUSA_SECRET_KEY);

const send = await jusa.sends.create(
  { product: 'airtime-usd', target: '0772123456', amount: '1.00' },
  { idempotencyKey: 'order-8812' },
);

const event = Jusa.webhooks.constructEvent(rawBody, req.headers['jusa-signature'], secret);

The jusa CLI

Comes with the Python package.

jusa login                                        # store a key (a test key while you build)
jusa listen --forward-to localhost:8000/jusa/webhooks
jusa listen --events 'send.*,reward.*'            # only these
jusa trigger send.delivered                       # fire a sample event (test mode)
jusa logs tail                                    # your requests as they happen
jusa status                                       # ZESA and the networks

listen needs no public URL: it polls GET /events and forwards each new event to your local URL, signed with a whsec_cli_… secret it prints, so your handler verifies it exactly as it will verify Jusa's own deliveries.

Postman and OpenAPI

MCP for AI agents

An MCP server at https://jusa.localhost.co.zw/dev/v1/mcp (JSON-RPC 2.0 over HTTP, stateless) for agent keys (jusa_ak_…). Tools: send_airtime, buy_zesa, send_data (once live data bundles ship; simulated in test mode), confirm_meter, lookup_phone, quote_zesa, create_reward, get_balance, get_send, list_sends, cancel_send, resend_token, list_products, list_programs, list_recipients, get_status, zesa_status and search_docs; the key's scopes decide which it sees. An agent never mints reward links. An agent spends within its rolling 24-hour threshold and to numbers it knows; anything more waits for a person, who approves with their payment PIN. Live agent keys are off until an owner or admin turns them on.

In the browser

Only publishable keys (jusa_pk_…) work from a web page: the catalog, status, reward-link claims and check-ins. Secret, restricted and agent keys sent from a browser are refused. The widget /static/developers/jusa.js claims a link (Jusa.claim(el, {publishableKey, token})) or picks a product for your server to sell with a checkout session (Jusa.buy).