Build
Recipes
Every use case we know of, with the calls it takes.
Every use case we know of, with the calls it takes. Tags say when something depends on a later phase or on a
legal sign-off; everything untagged works today. Code is Python with the jusa SDK; every call has the same
shape in Node and in the reference.
- Reward a respondent for finishing a survey
- Claim cards and QR codes for offline surveys
- A micro-task earnings pot
- Reimburse SMS or USSD poll costs
- Reward in-app actions: sign-up, KYC, first transaction
- Streak rewards
- A two-sided referral chain
- Loyalty points to airtime, data or ZESA
- Reward links where the person chooses
- Radio or brand giveaways with an audit trail
- Conditional adherence incentives
- Community health worker allowances
- In-kind ZESA assistance
- Clinic or antenatal check-in
- Learn to earn data
- Exam-season data stipends
- A bursary split across data and ZESA
- Gig and rider allowances
- Employer ZESA as a benefit
- Pay per verified sale
- Outage apology credit
- Diaspora: send ZESA or airtime home
- Landlord and tenant split-meter settlement
- Checkout cashback as airtime
- Solar or IoT ZESA auto-buy
- Availability-aware buying
- Band-aware ZESA quotes
- AI agent tool calls over MCP
- WhatsApp chatbot commerce
- Escrowed conditional rewards
- Geofenced event check-in
- Farmer extension incentives
- Programmatic family allowance
- Bill-shock rescue
- Emergency power for medical devices
- Crowdsourced price or outage reports
- Data perk on ticket scan
- Fundraising thank-you airtime
- Payday ZESA nudge
Reward a respondent for finishing a survey
For KoboToolbox or ODK back-ends, SurveyCTO, research agencies. seed
The seed use case. One program holds the budget and the rules; each completed survey is one reward, keyed on the submission so it is paid once, forever.
- Create a program with a budget, a default amount and per_recipient caps (or in the dashboard).
- When your survey tool reports a completion, POST /rewards with the respondent and completion_id = the submission id.
- Listen for reward.delivered and reward.failed; a failure has already returned the budget.
program = jusa.Program.create(name="Household survey Q4", currency="USD",
default_amount="0.50", budget="500.00", funding="reserve",
choices=["airtime"], per_recipient={"lifetime": 1}, idempotency_key="prog-q4")
reward = jusa.Reward.create(program=program.id,
recipient={"external_id": s["respondent_id"], "phone": s["phone"]},
completion_id=s["submission_id"], metadata={"survey": s["form_id"]},
idempotency_key=f"survey-{s['submission_id']}")
Calls: programs.create · rewards.create · programs.create_completion
Claim cards and QR codes for offline surveys
For NGOs with enumerators.
Print a link or QR per respondent. They claim it on their own phone later; an OTP binding stops a forwarded link being claimed by someone else.
- Mint a batch of links with a label per enumerator and an expiry.
- Hand out the url or the qr_png_url; the hosted page at /r/{token} does the rest.
- Watch reward_link.claimed and pull a spend report grouped by program.
links = jusa.RewardLink.create(program="prg_…", count=200, label="enumerator-7",
bind="otp", expires_at="2026-12-31T23:59:59Z")
for link in links.data:
print(link.url, link.qr_png_url)
Calls: reward_links.create · reward_links.list · reports.spend · exports.create
A micro-task earnings pot
For Premise-style task apps. P3
Each task earns into a pot; the pot pays out as airtime once it reaches a threshold.
- Set a threshold rule on the program: earn per activity, pay_at the threshold.
- POST an activity for each finished task; activity_id dedupes it for good.
- Show the person their pot with /recipients/{id}/balance.
jusa.Program.set_rules("prg_…", rules=[
{"kind": "threshold", "activity": "task.done", "earn": "0.25", "pay_at": "1.00"}])
jusa.Activity.create(recipient="ext:user_123", name="task.done", activity_id=task_id)
jusa.Recipient.balance("ext:user_123")
Calls: programs.set_rules · activities.create · recipients.balance
Reimburse SMS or USSD poll costs
For Pollsters.
Pay back what answering a poll cost each participant, in one batch at the end of the day.
- Collect the numbers that answered.
- Preview, then create one batch with a line each.
items = [{"target": p, "amount": "0.10", "client_reference": f"poll-7-{p}"} for p in phones]
jusa.Batch.preview(items=items)
batch = jusa.Batch.create(items=items, title="Poll 7 reimbursement",
idempotency_key="poll-7-reimburse")
Calls: batches.preview · batches.create · batches.retrieve
Reward in-app actions: sign-up, KYC, first transaction
For Fintech and e-commerce apps.
Reward what a user does, once per user, with sign-up rewards held in escrow until they stick.
- A program per action, with one_per_phone and per_recipient caps.
- Reward with your own external_id so caps follow the person across numbers.
- Hold sign-up rewards (hold=true) and release them after your own checks.
reward = jusa.Reward.create(program="prg_signup", completion_id=f"signup-{user.id}",
recipient={"external_id": str(user.id), "phone": user.phone}, hold=True)
# … after 7 days of real use:
jusa.Reward.release(reward.id, idempotency_key=f"release-{reward.id}")
Calls: rewards.create · rewards.release · rewards.void
Streak rewards
For Edtech, health and fitness apps. P3
Pay when someone does the thing N times in D days.
- Set a streak rule: activity, count, days, amount.
- POST an activity each time; the reward is made when the streak completes.
jusa.Program.set_rules("prg_…", rules=[
{"kind": "streak", "activity": "lesson.completed", "count": 5, "days": 7, "amount": "1.00"}])
jusa.Activity.create(recipient="ext:learner-9", name="lesson.completed", activity_id=lesson_log_id)
Calls: programs.set_rules · activities.create
A two-sided referral chain
For Any app. P3
Pay the referrer (and optionally the referee) once the referee does something that counts. Depth 1, held in escrow by default.
- Set a referral rule with a qualifying_activity.
- Record the referral when they sign up.
- The referee's qualifying activity (or POST …/qualify) pays out.
jusa.Referral.create(program="prg_…",
referrer={"external_id": "u-1", "phone": "0772123456"},
referee={"external_id": "u-2", "phone": "0772654321"})
Calls: programs.set_rules · referrals.create · referrals.qualify
Loyalty points to airtime, data or ZESA
For Retailers, fuel stations, supermarkets. data: P2
Let members spend points on airtime or a ZESA token at face value.
- Show the catalog; for ZESA, quote first so the member sees the units.
- Send with the quote: it carries a confirmed meter.
quote = jusa.Quote.create(product="zesa-usd", amount="10.00", target="37261502217")
print(quote.zesa.units_now, quote.meter.customer_name)
jusa.Send.create(quote=quote.id, token_phone="0772123456",
idempotency_key=f"redeem-{redemption.id}")
Calls: products.list · quotes.create · sends.create · meters.retrieve
Reward links where the person chooses
For Brands. data: P2
Send a link over WhatsApp; the person picks airtime or ZESA and confirms their meter.
- Mint links with choices and bind=otp.
- Share them; the hosted page confirms the meter.
link = jusa.RewardLink.create(program="prg_…", recipient="ext:cust-88",
choices=["airtime", "zesa"], bind="otp", phone_hint="0772123456")
Calls: reward_links.create · meters.retrieve
Radio or brand giveaways with an audit trail
For Media. prize draws: P3, needs a licence
First-come links for listeners, with an export the auditor can check. Prize draws stay off until counsel signs them off.
- Mint the links; read them out or post them.
- Export the rewards for the audit.
jusa.RewardLink.create(program="prg_…", count=50, label="breakfast-show") export = jusa.Export.create(type="rewards", from_="2026-10-01")
Calls: reward_links.create · exports.create · exports.retrieve
Conditional adherence incentives
For Clinics, research trials. needs counsel: health data
Hold a reward and release it when a visit is confirmed. Sensitive-data programs stay off until Jusa approves them.
- A sensitive_data program with a lawful_basis, pseudonymous external_ids.
- Hold rewards; release or void them from your clinical system.
reward = jusa.Reward.create(program="prg_trial", completion_id=f"visit-{visit.id}",
recipient={"external_id": participant.code, "phone": participant.phone}, hold=True)
Calls: rewards.create · rewards.release · rewards.void
Community health worker allowances
For Health NGOs. data: P2
A monthly allowance to every CHW, billed to the right cost centre.
- Recipients in a group, a cost centre with a GL code.
- A monthly schedule to the group.
cc = jusa.CostCenter.create(name="CHW programme", gl_code="6100")
group = jusa.Group.create(name="CHWs", cost_center=cc.id)
jusa.Schedule.create(name="CHW airtime", cadence="monthly", day_of_month=1, hour=8, minute=0,
group=group.id, product="airtime-usd", amount="5.00")
Calls: recipients.bulk · groups.create · cost_centers.create · schedules.create · reports.spend
In-kind ZESA assistance
For WFP-style programmes.
ZESA tokens to households in a batch, credit reserved up front, dry-run in test mode first.
- Each line carries the meter and a token_phone.
- Run it in test mode, then live.
jusa.Batch.create(items=[
{"type": "zesa", "meter": "37261502217", "token_phone": "0772123456", "amount": "10.00",
"client_reference": "hh-0001"},
], title="October assistance", idempotency_key="oct-assistance")
Calls: batches.preview · batches.create · statements.retrieve
Clinic or antenatal check-in
For Health. P3 needs counsel: health data
A reward link paid only by checking in at the clinic, inside its geofence and hours.
- A program with a geofence.
- The person checks in from the widget or your app.
jusa.Checkin.create(link="lnk_…", lat=-17.8252, lng=31.0522, accuracy_m=25,
phone="0772123456", choice="airtime")
Calls: checkins.create · reward_links.create
Learn to earn data
For Edtech. data: P2 rules: P3
Finish lessons, earn data bundles. Live data bundles ship in P2; test mode has a simulated list.
- List a data product's bundles.
- Reward with type=data and a bundle_id.
bundles = jusa.Product.list_bundles("econet-data-usd")
jusa.Reward.create(program="prg_…", completion_id=f"course-{c.id}",
recipient={"phone": "0772123456"}, type="data", bundle_id=bundles.data[0].bundle_id)
Calls: products.list_bundles · rewards.create
Exam-season data stipends
For Schools and tutors. data: P2
A weekly stipend through exam season, stopped by deleting the schedule.
- A weekly schedule to the class group.
jusa.Schedule.create(name="Exam data", cadence="weekly", day_of_week=0, hour=7, minute=0,
group="grp_…", product="airtime-usd", amount="2.00")
Calls: schedules.create · schedules.delete
A bursary split across data and ZESA
For Bursary funds. data: P2
Two schedules to the same people: airtime for data, ZESA to their meter.
- Recipients carry a meter and a token_phone.
- One airtime and one ZESA schedule.
jusa.Schedule.create(name="Bursary ZESA", cadence="monthly", day_of_month=1, hour=8, minute=0,
recipients=["rcp_…"], product="zesa-usd", amount="10.00")
Calls: recipients.create · schedules.create
Gig and rider allowances
For Delivery and ride apps.
Airtime per completed trip, capped per rider per day.
- A per-recipient daily cap on the rider.
- Reward each trip, keyed on the trip id.
jusa.Recipient.update("ext:rider-4", daily_cap="3.00")
jusa.Reward.create(program="prg_trips", completion_id=f"trip-{trip.id}",
recipient="ext:rider-4", amount="0.50")
Calls: recipients.update · rewards.create
Employer ZESA as a benefit
For Employers.
Workforce as it is in the Jusa app: departments, plans, allocation and the GL journal.
- Allocate to people by department.
- Pull the journal for your GL.
client = jusa.Jusa("jusa_sk_live_…")
client.workforce.journal(from_="2026-09-01", to="2026-09-30", format="csv")
Calls: workforce.allocate · workforce.journal · meters.retrieve
Pay per verified sale
For Field sales teams.
Escrow the commission until the return window closes; void it if the sale comes back.
- A program with hold.mode=escrow and auto_release_after.
- Void on a return.
jusa.Program.create(name="Sales commission", default_amount="1.00", budget="200.00",
hold={"mode": "escrow", "auto_release_after": "14d"})
Calls: programs.create · rewards.create · rewards.void
Outage apology credit
For Banks and ISPs.
One batch to every affected customer, safe to retry with one Idempotency-Key.
- Build the items from your incident list.
- Create the batch with an idempotency key.
jusa.Batch.create(items=[{"target": p, "amount": "1.00"} for p in affected],
title="Sorry for Tuesday", idempotency_key="incident-2026-10-07")
Calls: batches.create
Diaspora: send ZESA or airtime home
For Remittance apps.
The sender pays by card on a checkout session; you get the attribution and the token. Recurring by card is not possible (there is no stored card): use your own Jusa Credit and a schedule.
- Create a checkout session; send the payer to its url.
- Hear checkout.session.fulfilled.
session = jusa.CheckoutSession.create(type="zesa", target="37261502217", amount="20.00",
currency="USD", token_phone="0772123456", success_url="https://app.example/thanks",
cancel_url="https://app.example/basket", client_reference=f"order-{order.id}")
redirect(session.url)
Calls: checkout_sessions.create · checkout_sessions.retrieve · meters.retrieve
Landlord and tenant split-meter settlement
For Property apps.
Each tenant pays their share by card; the receipt proves what went to the meter.
- A checkout session per tenant.
- Share the receipt of the delivered send.
receipt = jusa.Send.receipt("snd_…")
print(receipt.units, receipt.customer_name)
Calls: checkout_sessions.create · sends.receipt
Checkout cashback as airtime
For E-commerce. data: P2
A percentage back as airtime after each order, from a program budget.
- Reward on order completion, keyed on the order id.
jusa.Reward.create(program="prg_cashback", completion_id=f"order-{order.id}",
recipient={"phone": order.phone}, amount=f"{order.total * 0.02:.2f}")
Calls: rewards.create
Solar or IoT ZESA auto-buy
For Smart-plug and monitor vendors.
The device reads the token from the send.delivered webhook. token_phone is required: ZETDC needs a number to notify. Cap the device's key.
- A restricted key with tokens:read and a daily spend cap.
- Send ZESA when the monitor says so; read the token from the webhook.
jusa.Send.create(type="zesa", target=device.meter, amount="5.00",
token_phone=owner.phone, client_reference=f"auto-{reading.id}")
Calls: keys.create · sends.create · webhook_endpoints.create
Availability-aware buying
For Any ZESA seller.
Check ZESA before promising a token now; queue with max_hold, or fail fast with on_zesa_down=fail.
- Read /status or hear zesa.availability.*.
- Choose on_zesa_down and max_hold per send.
if jusa.Status.retrieve().zesa.state == "down":
notify_user("ZESA is down; your token will come when it is back.")
jusa.Send.create(type="zesa", target=meter, amount="10.00", token_phone=phone,
on_zesa_down="queue", max_hold="6h")
Calls: status.retrieve · status.history · sends.create
Band-aware ZESA quotes
For Budgeting apps.
Show how many units the money buys now and after the monthly reset.
- Quote with the meter.
q = jusa.Quote.create(product="zesa-usd", amount="20.00", target="37261502217") print(q.zesa.units_now, q.zesa.units_after_reset, q.zesa.resets_on)
Calls: quotes.create
AI agent tool calls over MCP
For Assistants and chat bots.
Give an agent an ak key: it sends within a threshold and to known numbers; anything more asks a person, who approves with their payment PIN. Live agents are off until an owner turns them on.
- Create an agent key with a threshold.
- Point your MCP client at /dev/v1/mcp.
key = jusa.ApiKey.create(kind="ak", label="assistant",
approval_threshold={"USD": "10.00"})
# MCP server: https://jusa.localhost.co.zw/dev/v1/mcp (Authorization: Bearer <ak key>)
Calls: keys.create · mcp.call · approvals.list
WhatsApp chatbot commerce
For Bot builders.
Confirm the meter in the chat, send a checkout link, confirm delivery from the webhook.
- meters.retrieve for the name to confirm.
- A checkout session; reply with its url.
meter = jusa.Meter.retrieve("37261502217")
reply(f"Is this {meter.customer_name} in {meter.address}?")
Calls: meters.retrieve · checkout_sessions.create
Escrowed conditional rewards
For Anyone.
The general primitive: reserve now, decide later. Held credit shows as credit.reserved.
- hold=true on the reward.
- release (optionally for less) or void.
r = jusa.Reward.create(program="prg_…", completion_id="task-9", recipient="ext:u-9", hold=True)
jusa.Reward.release(r.id, amount="0.40", idempotency_key=f"rel-{r.id}")
Calls: rewards.create · rewards.release · rewards.void · credit.list_reservations
Geofenced event check-in
For Ag shows, churches, retail. P3
Links that pay only at the venue, during the event.
- A program with a geofence and a window.
- Check-ins from the widget.
jusa.Program.create(name="Ag show", default_amount="0.50", budget="100.00", sources=["links"],
geofence={"lat": -17.8252, "lng": 31.0522, "radius_m": 300})
Calls: programs.create · checkins.create
Farmer extension incentives
For EcoFarmer-scale programmes.
Reward attendance and adoption, keyed on your farmer ids; batches for the season-end bonus.
- Reward per attendance.
- A batch at season end.
jusa.Reward.create(program="prg_extension", completion_id=f"attend-{session_id}-{farmer.id}",
recipient={"external_id": farmer.id, "phone": farmer.phone})
Calls: rewards.create · batches.create
Programmatic family allowance
For Bank standing orders. OAuth family: P3
For your own customers' families: recipients plus a payday schedule. /family needs OAuth user tokens and answers 501 until then.
- The family members as recipients.
- A payday schedule.
jusa.Schedule.create(name="Gogo's airtime", cadence="payday", hour=9, minute=0,
recipients=["rcp_…"], product="airtime-usd", amount="5.00")
Calls: recipients.create · schedules.create
Bill-shock rescue
For Telco-adjacent fintech.
You spot a zero balance in your own systems (Jusa cannot see it) and send US$0.50, capped per person per day.
- A per-recipient daily cap.
- One send per rescue, keyed on your alert.
jusa.Send.create(target=customer.phone, amount="0.50", product="airtime-usd",
recipient=f"ext:{customer.id}", client_reference=f"rescue-{alert.id}")
Calls: sends.create · recipients.update
Emergency power for medical devices
For Health IoT. needs counsel: health data
A monitor triggers a ZESA reward from the patient's own program: the program's budget is the cap, and on_zesa_down=fail on the program so a caregiver hears at once. (A send's program is attribution only: it draws on no budget.)
- A program per patient: choices ["zesa"], a budget, on_zesa_down="fail".
- A direct reward per alarm, keyed on it; listen to zesa.availability.* and reward.failed.
jusa.Program.create(name=f"Patient {patient.id}", currency="USD", default_amount="5.00",
budget="60.00", choices=["zesa"], on_zesa_down="fail")
jusa.Reward.create(program="prg_patient_12", completion_id=f"alarm-{alarm.id}", type="zesa",
recipient={"external_id": patient.id, "meter": patient.meter, "token_phone": carer.phone})
Calls: programs.create · rewards.create · status.retrieve
Crowdsourced price or outage reports
For Civic tech. P3
Each verified report is an activity; a threshold rule pays out.
- A threshold rule.
- An activity per verified report.
jusa.Activity.create(recipient="ext:reporter-3", name="report.verified", activity_id=report.id)
Calls: activities.create · rewards.create
Data perk on ticket scan
For Promoters. data: P2
A link printed on the ticket, bound to the buyer's phone.
- A link per ticket, bind=otp with the buyer's phone_hint.
jusa.RewardLink.create(program="prg_gig", recipient={"phone": ticket.phone},
bind="otp", phone_hint=ticket.phone, label=ticket.code)
Calls: reward_links.create
Fundraising thank-you airtime
For Churches and schools.
Thank each donor with a little airtime after their gift.
- Reward per donation, keyed on the gift.
jusa.Reward.create(program="prg_thanks", completion_id=f"gift-{gift.id}",
recipient={"phone": gift.phone}, amount="0.50")
Calls: rewards.create
Payday ZESA nudge
For HR platforms.
ZESA units are banded and reset monthly: a quote shows what the money buys before and after the reset, so staff choose when to buy.
- Quote on payday.
- A payday schedule for those who opt in.
q = jusa.Quote.create(product="zesa-usd", amount="30.00", target=employee.meter) send_nudge(employee, q.zesa.units_now, q.zesa.units_after_reset)
Calls: quotes.create · schedules.create