Reference

API reference

Every endpoint, generated from the OpenAPI document.

Generated from the OpenAPI 3.1 document, which every response is tested against. Base URL https://jusa.localhost.co.zw/dev/v1; every request carries Authorization: Bearer <key> unless it says otherwise. Money is a decimal string, times are unix seconds, and every list pages with limit, starting_after and ending_before.

Account

Your organisation, members and the dashboard home.

GET /account

Retrieve the account

account.retrieve

Returns: 200 Account. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/account \
  -H "Authorization: Bearer jusa_sk_test_…"
PATCH /account

Update the account

account.update scope account:manage

Owners and admins only (a key counts as its maker). Scope: account:manage.

Body (AccountUpdate)TypeAbout
namestring
contact_emailstring
support_phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
statement_descriptorstring
batch_approval_thresholdobject
agent_live_enabledbooleanDashboard only: a person turns it on.

Returns: 200 Account. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/account \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"statement_descriptor": "ACME SURVEYS", "support_phone": "0772123456"}'
GET /invites

List pending invitations

invites.list scope account:manage

Scope: account:manage.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.

Returns: 200 InviteList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/invites \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /invites/{invite_id}

Retrieve an invitation

invites.retrieve scope account:manage

Scope: account:manage.

ParameterInAbout
invite_id requiredpathinv_…

Returns: 200 Invite. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/invites/{invite_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
DELETE /invites/{invite_id}

Revoke an invitation

invites.revoke scope account:manage

Owners and admins only. Scope: account:manage.

ParameterInAbout
invite_id requiredpathinv_…

Returns: 200 Invite. Errors: the envelope.

curl -X DELETE https://jusa.localhost.co.zw/dev/v1/invites/{invite_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /members

List members

members.list scope account:manage

Scope: account:manage.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
rolequeryOnly members with this role.

Returns: 200 MemberList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/members \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /members

Invite a member

members.create scope account:manage Idempotency-Key optional

An invitation, pending until the person signs in and accepts it from their dashboard (it lapses after 14 days). Until then they are not a member and see nothing of the account. Scope: account:manage.

Body (MemberInvite)TypeAbout
phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
emailstring
role"owner" | "admin" | "developer" | "viewer"

Returns: 201 Invite. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/members \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"email": "dev@example.com", "role": "developer"}'
GET /members/{member_id}

Retrieve a member

members.retrieve scope account:manage

Scope: account:manage.

ParameterInAbout
member_id requiredpathmem_…

Returns: 200 Member. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/members/{member_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
PATCH /members/{member_id}

Change a member's role

members.update scope account:manage

Scope: account:manage.

ParameterInAbout
member_id requiredpathmem_…
Body (MemberUpdate)TypeAbout
role required"owner" | "admin" | "developer" | "viewer"

Returns: 200 Member. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/members/{member_id} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"role": "owner"}'
DELETE /members/{member_id}

Remove a member

members.delete scope account:manage

Scope: account:manage.

ParameterInAbout
member_id requiredpathmem_…

Returns: 200 Member. Errors: the envelope.

curl -X DELETE https://jusa.localhost.co.zw/dev/v1/members/{member_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /organisations

Your organisations

organisations.list

Dashboard only (a signed-in person): each organisation you belong to and whether it is a developer organisation.

Returns: 200 OrganisationList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/organisations \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /organisations

Create a developer organisation

organisations.create

Dashboard only (a signed-in person): makes the live org and its test-mode twin.

Body (OrganisationCreate)TypeAbout
namestring

Returns: 201 Account. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/organisations \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
GET /organisations/invites

Invitations waiting for you

organisations.list_invites

Dashboard only (a signed-in person).

Returns: 200 InviteList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/organisations/invites \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /organisations/invites/{invite_id}/accept

Accept an invitation

organisations.accept_invite

Dashboard only: the person invited, signed in.

ParameterInAbout
invite_id requiredpathinv_…

Returns: 200 Organisation. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/organisations/invites/{invite_id}/accept \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /organisations/invites/{invite_id}/decline

Decline an invitation

organisations.decline_invite

Dashboard only: the person invited, signed in.

ParameterInAbout
invite_id requiredpathinv_…

Returns: 200 Invite. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/organisations/invites/{invite_id}/decline \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /overview

The dashboard home

overview.retrieve

Jusa Credit with runway, sends today, success by network, webhook health and what needs attention. Sections the caller cannot read are null.

Returns: 200 Overview. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/overview \
  -H "Authorization: Bearer jusa_sk_test_…"

Keys

API keys: sk secret, rk restricted, pk publishable, ak agent.

GET /keys

List API keys

keys.list scope account:manage

Scope: account:manage.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.

Returns: 200 ApiKeyList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/keys \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /keys

Create an API key

keys.create scope account:manage

The key is in secret, once. The first live key needs accept_terms: true (the API Terms, the Acceptable Use policy and the DPA). No Idempotency-Key: a replay would have to show the secret again. Scope: account:manage.

Body (KeyCreate)TypeAbout
kind"sk" | "rk" | "pk" | "ak"
labelstring
scopesarray of "catalog:read" | "lookups:read" | "sends:read" | "sends:write" | "tokens:read" | "batches:write" | "schedules:write" | "credit:read" | "credit:topup" | "recipients:read" | "recipients:write" | "rewards:read" | "rewards:write" | "programs:manage" | "webhooks:manage" | "reports:read" | "workforce:admin" | "family:write" | "account:manage"
allowed_ipsarray of string
expires_atinteger or stringUnix seconds or an ISO 8601 time.
accept_termsbooleanThe first live key: accept the API Terms, AUP and DPA.
allowed_productsarray of string
allowed_networksarray of string
spend_capsobject
per_recipient_daily_capobject
approval_thresholdobject
recipient_allowlistarray of string
allowed_originsarray of string

Returns: 201 ApiKey. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/keys \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"kind": "rk", "label": "survey relay", "scopes": ["rewards:write"]}'
GET /keys/{key_id}

Retrieve an API key

keys.retrieve scope account:manage

With usage: what it has spent against its caps. Scope: account:manage.

ParameterInAbout
key_id requiredpathkey_…

Returns: 200 ApiKey. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/keys/{key_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
PATCH /keys/{key_id}

Update an API key

keys.update scope account:manage

Scope: account:manage.

ParameterInAbout
key_id requiredpathkey_…
Body (KeyUpdate)TypeAbout
labelstring
scopesarray of "catalog:read" | "lookups:read" | "sends:read" | "sends:write" | "tokens:read" | "batches:write" | "schedules:write" | "credit:read" | "credit:topup" | "recipients:read" | "recipients:write" | "rewards:read" | "rewards:write" | "programs:manage" | "webhooks:manage" | "reports:read" | "workforce:admin" | "family:write" | "account:manage"
allowed_ipsarray of string
expires_atinteger or stringUnix seconds or an ISO 8601 time.
allowed_productsarray of string
allowed_networksarray of string
spend_capsobject
per_recipient_daily_capobject
approval_thresholdobject
recipient_allowlistarray of string
allowed_originsarray of string

Returns: 200 ApiKey. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/keys/{key_id} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
DELETE /keys/{key_id}

Revoke an API key

keys.revoke scope account:manage

Takes effect at once. Scope: account:manage.

ParameterInAbout
key_id requiredpathkey_…

Returns: 200 ApiKey. Errors: the envelope.

curl -X DELETE https://jusa.localhost.co.zw/dev/v1/keys/{key_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /keys/{key_id}/roll

Roll an API key

keys.roll scope account:manage

A new key in secret; the old one (in rolled) works for the grace period. Scope: account:manage.

ParameterInAbout
key_id requiredpathkey_…
Body (KeyRoll)TypeAbout
grace_period"0" | "1h" | "24h" | "7d" | 0How long the old key keeps working.

Returns: 201 ApiKey. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/keys/{key_id}/roll \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'

Logs

The request log and the audit log.

GET /activity

Recent account activity

activity_feed.list scope reports:read

What happened lately (events: sends, rewards, top-ups, batches …), newest first, and, with account:manage, who changed what (the audit log). Pages back with starting_after. Scope: reports:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.

Returns: 200 AccountActivityList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/activity \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /audit-log

List audit events

audit_log.list scope account:manage

Who (a key, a member, or Jusa staff viewing as you) changed what. Scope: account:manage.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
actionqueryOnly this action (send or reward; for the audit log, e.g. api_key.created).
objectqueryEvents about this object, e.g. snd_…

Returns: 200 AuditEventList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/audit-log \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /requests

List API requests

requests.list scope account:manage

Kept 30 days. status is a code (402) or a class (4xx). Scope: account:manage.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
api_keyquerykey_…
statusqueryOnly objects in this status.
pathqueryPaths starting with this.
methodqueryGET, POST, …
idempotency_keyqueryRequests sent with this key.

Returns: 200 RequestLogList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/requests \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /requests/{request_id}

Retrieve an API request

requests.retrieve scope account:manage

With the masked request and response bodies. Scope: account:manage.

ParameterInAbout
request_id requiredpathreq_…

Returns: 200 RequestLog. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/requests/{request_id} \
  -H "Authorization: Bearer jusa_sk_test_…"

Catalog

Products and networks. Face value, always.

GET /networks

List networks

networks.list scope catalog:read publishable keys

Scope: catalog:read. Publishable keys may call it (from a browser).

Returns: 200 NetworkList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/networks \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /products

List products

products.list scope catalog:read publishable keys

Face value, always: there is no pricing endpoint. Scope: catalog:read. Publishable keys may call it (from a browser).

ParameterInAbout
typequeryOnly this type.
networkqueryOnly this network.
currencyqueryOnly this currency.

Returns: 200 ProductList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/products \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /products/{product_id}

Retrieve a product

products.retrieve scope catalog:read publishable keys

Scope: catalog:read. Publishable keys may call it (from a browser).

ParameterInAbout
product_id requiredpathA product slug.

Returns: 200 Product. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/products/{product_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /products/{product_id}/bundles

List a data product's bundles

products.list_bundles scope catalog:read publishable keys

Test mode has a simulated list; live data bundles ship in P2. Scope: catalog:read. Publishable keys may call it (from a browser).

ParameterInAbout
product_id requiredpathA product slug.

Returns: 200 BundleList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/products/{product_id}/bundles \
  -H "Authorization: Bearer jusa_sk_test_…"

Lookups

Phone lookups, ZESA meter confirmation and quotes.

GET /lookups/phone

Look up a phone number

lookups.phone scope lookups:read

Its network and E.164 form. No provider is asked. Scope: lookups:read.

ParameterInAbout
phonequeryA phone number, any Zimbabwean format.
currencyqueryOnly this currency.

Returns: 200 PhoneLookup. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/lookups/phone \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /meters/{number}

Confirm a ZESA meter

meters.retrieve scope lookups:read

The name is masked and the address is the suburb. Rationed: 30 a minute per key and a daily quota per account. 503 zesa_unavailable when ZESA is down. Scope: lookups:read.

ParameterInAbout
number requiredpathAn 11-digit meter number.
currencyqueryOnly this currency.

Returns: 200 Meter. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/meters/{number} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /quotes

Quote a send

quotes.create scope lookups:read Idempotency-Key optional

you_pay is always the face value. For ZESA: the units and band now and after the reset, and a strictly confirmed meter that a send carries by passing the quote. Scope: lookups:read.

Body (QuoteCreate)TypeAbout
productstring
amountstring or numberA decimal amount, e.g. "5.00".
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
targetstring
bundle_idstring

Returns: 201 Quote. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/quotes \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"product": "zesa-usd", "amount": "10.00", "target": "37261502217"}'
GET /quotes/{quote_id}

Retrieve a quote

quotes.retrieve scope lookups:read

Scope: lookups:read.

ParameterInAbout
quote_id requiredpathquo_…

Returns: 200 Quote. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/quotes/{quote_id} \
  -H "Authorization: Bearer jusa_sk_test_…"

Status

ZESA and network availability.

GET /status

ZESA and network status

status.retrieve publishable keys

Publishable keys may call it (from a browser).

Returns: 200 Status. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/status \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /status/history

ZESA uptime history

status.history publishable keys

The last 7 days by default. Publishable keys may call it (from a browser).

ParameterInAbout
fromqueryThe start (inclusive).
toqueryThe end (inclusive).

Returns: 200 StatusHistory. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/status/history \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /zesa/observations

The purchases behind Rate Watch

zesa.observations scope lookups:read

Anonymised and coarsened: no meter, no order. Scope: lookups:read.

ParameterInAbout
currencyqueryOnly this currency.
daysqueryHow many days back (90 by default).
limitqueryRows per page.

Returns: 200 ZesaObservations. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/zesa/observations \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /zesa/quote

What an amount buys on ZESA

zesa.quote scope lookups:read

Units now versus after the reset on the 1st; with meter, from where that meter stands this month (a meter is rationed like a lookup). POST /v1/quotes also confirms the meter. Scope: lookups:read.

ParameterInAbout
currencyqueryOnly this currency.
amountqueryA decimal amount, e.g. 10.00.
meterqueryA meter number.

Returns: 200 ZesaRate. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/zesa/quote \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /zesa/rate

ZESA Rate Watch

zesa.rate scope catalog:read

ZETDC's published bands, what real purchases show a kWh costs, and the cheapest-units rule (the bands reset on the 1st). Public data, the same in test and live mode. Scope: catalog:read.

ParameterInAbout
currencyqueryOnly this currency.

Returns: 200 ZesaRate. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/zesa/rate \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /zesa/series

ZESA rate series

zesa.series scope catalog:read

The sparkline of the observed rate and where it changed. Scope: catalog:read.

ParameterInAbout
currencyqueryOnly this currency.

Returns: 200 ZesaSeries. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/zesa/series \
  -H "Authorization: Bearer jusa_sk_test_…"

Sends

Airtime, data and ZESA tokens, one at a time. Async: 202, then webhooks.

GET /sends

List sends

sends.list scope sends:read

Scope: sends:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.
typequeryOnly this type.
targetqueryA phone (any format) or meter.
client_referencequeryYour own reference.
currencyqueryOnly this currency.
needs_attentionqueryunknown over 1h, requires_review, or queued past half its max_hold.
recipientqueryrcp_… or ext:<external_id>.
programqueryprg_…
batchquerybch_…
cost_centerquerycc_… (Workforce: a department's id, or none for sends booked to none).
api_keyquerykey_…
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.
expand[]queryInline a related object in place of its id (repeat for more): recipient, program or batch on a send; recipient, program or send on a reward.

Returns: 200 SendList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/sends \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /sends

Create a send

sends.create scope sends:write Idempotency-Key required

Always async: the send is written and charged, then dispatched; webhooks and GET tell the rest. validate_only: true runs every check and writes nothing (200, no Idempotency-Key needed). A client_reference already used returns that send (200). An agent key over its threshold or to a new target gets 202 with an agent_approval instead. Scope: sends:write. Moves money: an Idempotency-Key is required.

Body (SendCreate)TypeAbout
type"airtime" | "data" | "zesa"Inferred from product or target when left out.
productstring
targetstringA phone (any Zimbabwean format) or an 11-digit meter.
amountstring or numberA decimal amount, e.g. "5.00".
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
bundle_idstringData: the bundle to send.
token_phonestringZESA: required. The token is texted here.
token_emailstring
notify_numberstringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
confirm"strict" | "best_effort"ZESA meter confirmation. strict (default) refuses when ZESA cannot answer.
on_zesa_down"queue" | "fail"
max_holdstring or integerSeconds, or "30m", "24h", "3d".
recipientstring or objectrcp_… or ext:<external_id>.
programstringprg_…: attribution only (reports, filters). A send never draws on or is capped by a program's budget: pay a program's budget out with POST /v1/rewards.
cost_centerstring
sms_messagestringMerge fields: {sender}, {name}, {first_name}, {amount}, {currency}, {date}… Left out, airtime and data carry a default that names you (statement_descriptor).
sms_locale"en" | "sn" | "nd"The default SMS's language: English, Shona or Ndebele.
scheduled_forinteger or stringA later send, 90 seconds to 180 days ahead: charged now, status scheduled until then, cancellable until it goes.
client_referencestring
quotestringquo_…: carries a strict meter confirmation.
validate_onlyboolean
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.
reasonstringAgent keys: shown to the person asked to approve.

Returns: 200 Send or SendValidation · 202 Send or AgentApproval. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/sends \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"product": "airtime-usd", "target": "0772123456", "amount": "1.00", "client_reference": "order-8812"}'
GET /sends/{send_id}

Retrieve a send

sends.retrieve scope sends:read

Scope: sends:read.

ParameterInAbout
send_id requiredpathsnd_… (or the bare hex of a legacy order).
expand[]queryInline a related object in place of its id (repeat for more): recipient, program or batch on a send; recipient, program or send on a reward.

Returns: 200 Send. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/sends/{send_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /sends/{send_id}/attempts

A send's attempts

sends.list_attempts scope sends:read

When it went to the provider, and each time Hot Recharge was asked what it recorded (outcomes only). Scope: sends:read.

ParameterInAbout
send_id requiredpathsnd_… (or the bare hex of a legacy order).

Returns: 200 SendAttemptList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/sends/{send_id}/attempts \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /sends/{send_id}/cancel

Cancel a send not yet sent

sends.cancel scope sends:write Idempotency-Key optional

A scheduled send, or one still waiting to go: the credit comes back (cancelled_credited). 409 already_sent once it has gone. Scope: sends:write.

ParameterInAbout
send_id requiredpathsnd_… (or the bare hex of a legacy order).

Returns: 200 Send. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/sends/{send_id}/cancel \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /sends/{send_id}/meter

Change a ZESA send's meter

sends.change_meter scope sends:write Idempotency-Key optional

Only while it has not gone out (queued while ZESA is down, or scheduled). The new meter is confirmed now when ZESA answers, else before the send goes out. Audited, and send.meter_changed goes to every endpoint. Scope: sends:write.

ParameterInAbout
send_id requiredpathsnd_… (or the bare hex of a legacy order).
Body (SendMeterChange)TypeAbout
meter requiredstringAn 11-digit ZESA prepaid meter number.

Returns: 200 Send. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/sends/{send_id}/meter \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"meter": "37261502217"}'
GET /sends/{send_id}/receipt

A delivered send's receipt

sends.receipt scope sends:read

JSON, or ?format=pdf for the same receipt as a PDF. The token is masked without tokens:read. Scope: sends:read.

ParameterInAbout
send_id requiredpathsnd_… (or the bare hex of a legacy order).
formatqueryjson (default); csv for reports and the Workforce files (the ledger also takes csv-json, the file inside JSON); pdf for a receipt.

Returns: 200 Receipt. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/sends/{send_id}/receipt \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /sends/{send_id}/resend-token

Text a ZESA token again

sends.resend_token scope sends:write

Only to the phone and email it went to first. Rate-limited. Scope: sends:write.

ParameterInAbout
send_id requiredpathsnd_… (or the bare hex of a legacy order).

Returns: 202 TokenResend. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/sends/{send_id}/resend-token \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /sends/{send_id}/retry

Retry a failed send

sends.retry scope sends:write Idempotency-Key required

A terminal failed send (failed_credited or failed) only, sent again as a new send charged now, with retry_of. Hot Recharge is asked about the first send before anything is resent: delivered after all is 409 send_delivered_late, no answer is 503 provider_unavailable. 409 send_not_retryable from any other state, for a card checkout's send, and for a send already retried. Scope: sends:write. Moves money: an Idempotency-Key is required.

ParameterInAbout
send_id requiredpathsnd_… (or the bare hex of a legacy order).

Returns: 202 Send. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/sends/{send_id}/retry \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)"

Disputes

A send your user says did not arrive.

GET /disputes

List disputes

disputes.list scope sends:read

Scope: sends:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.
sendquerysnd_…
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.

Returns: 200 DisputeList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/disputes \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /disputes

Open a dispute

disputes.create scope sends:write Idempotency-Key optional

Opens a staff case. A delivered send is never reversed. Scope: sends:write.

Body (DisputeCreate)TypeAbout
send requiredstring
reason required"token_not_received" | "wrong_number" | "not_delivered" | "other"
evidencestring
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.

Returns: 201 Dispute. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/disputes \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"send": "snd_\u2026", "reason": "token_not_received", "evidence": "Customer says no SMS came."}'
GET /disputes/{dispute_id}

Retrieve a dispute

disputes.retrieve scope sends:read

Scope: sends:read.

ParameterInAbout
dispute_id requiredpathdsp_…

Returns: 200 Dispute. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/disputes/{dispute_id} \
  -H "Authorization: Bearer jusa_sk_test_…"

Batches

Up to 10,000 sends at once.

GET /batches

List batches

batches.list scope sends:read

Scope: sends:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.

Returns: 200 BatchList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/batches \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /batches

Create a batch

batches.create scope batches:write Idempotency-Key required

Up to 10,000 items (JSON or CSV). Credit is reserved at once by default; above the account's approval threshold it waits for a second person. Scope: batches:write. Moves money: an Idempotency-Key is required.

Body (BatchCreate)TypeAbout
itemsarray of object
csvstringOr a CSV with a header row.
titlestring
sms_messagestring
cost_centerstring
on_invalid"reject" | "skip"
reserve_creditboolean
on_insufficient"stop" | "continue"
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.

Returns: 202 Batch. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/batches \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"title": "October allowance", "items": [{"target": "0772123456", "amount": "5.00"}, {"target": "0712345678", "amount": "5.00"}]}'
GET /batches/{batch_id}

Retrieve a batch

batches.retrieve scope sends:read

Scope: sends:read.

ParameterInAbout
batch_id requiredpathbch_…

Returns: 200 Batch. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/batches/{batch_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /batches/{batch_id}/approve

Approve a batch

batches.approve scope batches:write Idempotency-Key required

By an owner or admin other than the batch's maker. Scope: batches:write. Moves money: an Idempotency-Key is required.

ParameterInAbout
batch_id requiredpathbch_…

Returns: 200 Batch. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/batches/{batch_id}/approve \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)"
POST /batches/{batch_id}/cancel

Cancel a batch

batches.cancel scope batches:write Idempotency-Key optional

Items not yet sent are cancelled; unspent reserved credit is released. Scope: batches:write.

ParameterInAbout
batch_id requiredpathbch_…

Returns: 200 Batch. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/batches/{batch_id}/cancel \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /batches/{batch_id}/items

List a batch's items

batches.list_items scope sends:read

Scope: sends:read.

ParameterInAbout
batch_id requiredpathbch_…
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.

Returns: 200 BatchItemList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/batches/{batch_id}/items \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /batches/{batch_id}/results.csv

Download a batch's results

batches.results_csv scope sends:read

Scope: sends:read.

ParameterInAbout
batch_id requiredpathbch_…

Returns: 200 CSV. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/batches/{batch_id}/results.csv \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /batches/{batch_id}/retry-failed

Retry a batch's failed items

batches.retry_failed scope batches:write Idempotency-Key required

A new batch, charged again. Each failed item is retried once. Scope: batches:write. Moves money: an Idempotency-Key is required.

ParameterInAbout
batch_id requiredpathbch_…
Body (BatchRetryFailed)TypeAbout
reserve_creditboolean

Returns: 202 Batch. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/batches/{batch_id}/retry-failed \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /batches/preview

Preview a batch

batches.preview scope batches:write

Per-line validity, totals, whether the credit and the provider float suffice. Scope: batches:write.

Body (BatchCreate)TypeAbout
itemsarray of object
csvstringOr a CSV with a header row.
titlestring
sms_messagestring
cost_centerstring
on_invalid"reject" | "skip"
reserve_creditboolean
on_insufficient"stop" | "continue"
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.

Returns: 200 BatchPreview. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/batches/preview \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"title": "October allowance", "items": [{"target": "0772123456", "amount": "5.00"}, {"target": "0712345678", "amount": "5.00"}]}'

Schedules

Recurring sends.

GET /schedules

List schedules

schedules.list scope sends:read

Scope: sends:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
activequeryActive or not.
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.

Returns: 200 ScheduleList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/schedules \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /schedules

Create a schedule

schedules.create scope schedules:write Idempotency-Key optional

Scope: schedules:write.

Body (ScheduleCreate)TypeAbout
namestring
cadence"daily" | "weekly" | "biweekly" | "monthly" | "yearly" | "payday" | "last_day"
hourinteger
minuteinteger
day_of_weekinteger
day_of_monthinteger
month_of_yearinteger
anchor_datestringYYYY-MM-DD, Harare time.
groupstring
recipientsarray of string
productstring
type"airtime" | "data" | "zesa"
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
amountstring or numberA decimal amount, e.g. "5.00".
sms_messagestring
custom_smsstring
cost_centerstring
meterstringAn 11-digit ZESA prepaid meter number.
token_phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
activeboolean
precheck_creditboolean
on_insufficient"skip" | "retry_after_topup"
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.

Returns: 201 Schedule. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/schedules \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"name": "Monthly allowance", "cadence": "monthly", "day_of_month": 1, "hour": 8, "minute": 0, "group": "grp_\u2026", "product": "airtime-usd", "amount": "5.00"}'
GET /schedules/{schedule_id}

Retrieve a schedule

schedules.retrieve scope sends:read

Scope: sends:read.

ParameterInAbout
schedule_id requiredpathsch_…

Returns: 200 Schedule. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/schedules/{schedule_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
PATCH /schedules/{schedule_id}

Update a schedule

schedules.update scope schedules:write

Scope: schedules:write.

ParameterInAbout
schedule_id requiredpathsch_…
Body (ScheduleCreate)TypeAbout
namestring
cadence"daily" | "weekly" | "biweekly" | "monthly" | "yearly" | "payday" | "last_day"
hourinteger
minuteinteger
day_of_weekinteger
day_of_monthinteger
month_of_yearinteger
anchor_datestringYYYY-MM-DD, Harare time.
groupstring
recipientsarray of string
productstring
type"airtime" | "data" | "zesa"
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
amountstring or numberA decimal amount, e.g. "5.00".
sms_messagestring
custom_smsstring
cost_centerstring
meterstringAn 11-digit ZESA prepaid meter number.
token_phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
activeboolean
precheck_creditboolean
on_insufficient"skip" | "retry_after_topup"
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.

Returns: 200 Schedule. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/schedules/{schedule_id} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"name": "Monthly allowance", "cadence": "monthly", "day_of_month": 1, "hour": 8, "minute": 0, "group": "grp_\u2026", "product": "airtime-usd", "amount": "5.00"}'
DELETE /schedules/{schedule_id}

Delete a schedule

schedules.delete scope schedules:write

Scope: schedules:write.

ParameterInAbout
schedule_id requiredpathsch_…

Returns: 200 DeletedSchedule. Errors: the envelope.

curl -X DELETE https://jusa.localhost.co.zw/dev/v1/schedules/{schedule_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /schedules/{schedule_id}/occurrences

List upcoming runs

schedules.list_occurrences scope sends:read

From today to 60 days on by default (YYYY-MM-DD). Scope: sends:read.

ParameterInAbout
schedule_id requiredpathsch_…
fromqueryThe start (inclusive).
toqueryThe end (inclusive).

Returns: 200 ScheduleOccurrenceList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/schedules/{schedule_id}/occurrences \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /schedules/{schedule_id}/occurrences/{day}

Retrieve one run

schedules.retrieve_occurrence scope sends:read

ran with its batch once it has gone out. Scope: sends:read.

ParameterInAbout
schedule_id requiredpathsch_…
day requiredpathThe run's date, YYYY-MM-DD.

Returns: 200 ScheduleOccurrence. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/schedules/{schedule_id}/occurrences/{day} \
  -H "Authorization: Bearer jusa_sk_test_…"
PATCH /schedules/{schedule_id}/occurrences/{day}

Change one run's amount

schedules.update_occurrence scope schedules:write

Scope: schedules:write.

ParameterInAbout
schedule_id requiredpathsch_…
day requiredpathThe run's date, YYYY-MM-DD.
Body (OccurrenceUpdate)TypeAbout
amount_overridestring or numberA decimal amount, e.g. "5.00".
notestring

Returns: 200 ScheduleOccurrence. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/schedules/{schedule_id}/occurrences/{day} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /schedules/{schedule_id}/occurrences/{day}/combine

Combine one run into another

schedules.combine_occurrence scope schedules:write

The other run (this schedule's on into, or schedule's) sends both amounts in one recharge per person, and this one is combined. Same people, currency and cost centre only. Reset undoes it. Scope: schedules:write.

ParameterInAbout
schedule_id requiredpathsch_…
day requiredpathThe run's date, YYYY-MM-DD.
Body (OccurrenceCombine)TypeAbout
into requiredstringYYYY-MM-DD, Harare time.
schedulestringsch_…; this schedule by default.

Returns: 200 ScheduleOccurrence. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/schedules/{schedule_id}/occurrences/{day}/combine \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"into": "2026-11-02"}'
POST /schedules/{schedule_id}/occurrences/{day}/move

Move one run

schedules.move_occurrence scope schedules:write

Scope: schedules:write.

ParameterInAbout
schedule_id requiredpathsch_…
day requiredpathThe run's date, YYYY-MM-DD.
Body (OccurrenceMove)TypeAbout
to requiredstringYYYY-MM-DD, Harare time.

Returns: 200 ScheduleOccurrence. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/schedules/{schedule_id}/occurrences/{day}/move \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"to": "string"}'
POST /schedules/{schedule_id}/occurrences/{day}/reset

Undo a skip, move or amount change

schedules.reset_occurrence scope schedules:write

Scope: schedules:write.

ParameterInAbout
schedule_id requiredpathsch_…
day requiredpathThe run's date, YYYY-MM-DD.

Returns: 200 ScheduleOccurrence. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/schedules/{schedule_id}/occurrences/{day}/reset \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /schedules/{schedule_id}/occurrences/{day}/skip

Skip one run

schedules.skip_occurrence scope schedules:write

Scope: schedules:write.

ParameterInAbout
schedule_id requiredpathsch_…
day requiredpathThe run's date, YYYY-MM-DD.

Returns: 200 ScheduleOccurrence. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/schedules/{schedule_id}/occurrences/{day}/skip \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /schedules/{schedule_id}/run-now

Run a schedule now

schedules.run_now scope schedules:write Idempotency-Key required

A run is a batch: it reserves its total and may wait for approval. Scope: schedules:write. Moves money: an Idempotency-Key is required.

ParameterInAbout
schedule_id requiredpathsch_…
Body (ScheduleRunNow)TypeAbout
againbooleanRun even if it already ran today.

Returns: 202 Batch. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/schedules/{schedule_id}/run-now \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /schedules/{schedule_id}/run-now/preview

Preview running now

schedules.preview_run_now scope schedules:write

Scope: schedules:write.

ParameterInAbout
schedule_id requiredpathsch_…

Returns: 200 ScheduleRunPreview. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/schedules/{schedule_id}/run-now/preview \
  -H "Authorization: Bearer jusa_sk_test_…"

Recipients

The people you send to, their groups and cost centres.

GET /cost-centers

List cost centres

cost_centers.list scope recipients:read

Scope: recipients:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
is_activequeryActive or not.

Returns: 200 CostCenterList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/cost-centers \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /cost-centers

Create a cost centre

cost_centers.create scope recipients:write Idempotency-Key optional

Scope: recipients:write.

Body (CostCenterWrite)TypeAbout
namestring
gl_codestring
monthly_budgetstring or numberA decimal amount, e.g. "5.00".
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
is_activeboolean

Returns: 201 CostCenter. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/cost-centers \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"name": "Marketing", "gl_code": "6100", "monthly_budget": "250.00"}'
GET /cost-centers/{cost_center_id}

Retrieve a cost centre

cost_centers.retrieve scope recipients:read

Scope: recipients:read.

ParameterInAbout
cost_center_id requiredpathcc_…

Returns: 200 CostCenter. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/cost-centers/{cost_center_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
PATCH /cost-centers/{cost_center_id}

Update a cost centre

cost_centers.update scope recipients:write

Scope: recipients:write.

ParameterInAbout
cost_center_id requiredpathcc_…
Body (CostCenterWrite)TypeAbout
namestring
gl_codestring
monthly_budgetstring or numberA decimal amount, e.g. "5.00".
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
is_activeboolean

Returns: 200 CostCenter. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/cost-centers/{cost_center_id} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"name": "Marketing", "gl_code": "6100", "monthly_budget": "250.00"}'
GET /cost-centers/{cost_center_id}/spend

A cost centre's spend

cost_centers.spend scope recipients:read

Scope: recipients:read.

ParameterInAbout
cost_center_id requiredpathcc_…
fromqueryThe start (inclusive).
toqueryThe end (inclusive).

Returns: 200 CostCenterSpend. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/cost-centers/{cost_center_id}/spend \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /groups

List groups

groups.list scope recipients:read

Scope: recipients:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.

Returns: 200 GroupList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/groups \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /groups

Create a group

groups.create scope recipients:write Idempotency-Key optional

Scope: recipients:write.

Body (GroupWrite)TypeAbout
namestring
descriptionstring
cost_centerstring
membersarray of string

Returns: 201 Group. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/groups \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"name": "Field team"}'
GET /groups/{group_id}

Retrieve a group

groups.retrieve scope recipients:read

Scope: recipients:read.

ParameterInAbout
group_id requiredpathgrp_…

Returns: 200 Group. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/groups/{group_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
PATCH /groups/{group_id}

Update a group

groups.update scope recipients:write

Scope: recipients:write.

ParameterInAbout
group_id requiredpathgrp_…
Body (GroupWrite)TypeAbout
namestring
descriptionstring
cost_centerstring
membersarray of string

Returns: 200 Group. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/groups/{group_id} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"name": "Field team"}'
DELETE /groups/{group_id}

Delete a group

groups.delete scope recipients:write

Scope: recipients:write.

ParameterInAbout
group_id requiredpathgrp_…

Returns: 200 DeletedGroup. Errors: the envelope.

curl -X DELETE https://jusa.localhost.co.zw/dev/v1/groups/{group_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /groups/{group_id}/members

Add recipients to a group

groups.add_members scope recipients:write

Scope: recipients:write.

ParameterInAbout
group_id requiredpathgrp_…
Body (GroupMembers)TypeAbout
recipients requiredarray of string

Returns: 200 Group. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/groups/{group_id}/members \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"recipients": []}'
DELETE /groups/{group_id}/members

Take recipients out of a group

groups.remove_members scope recipients:write

Scope: recipients:write.

ParameterInAbout
group_id requiredpathgrp_…
Body (GroupMembers)TypeAbout
recipients requiredarray of string

Returns: 200 Group. Errors: the envelope.

curl -X DELETE https://jusa.localhost.co.zw/dev/v1/groups/{group_id}/members \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"recipients": []}'
GET /recipients

List recipients

recipients.list scope recipients:read

Scope: recipients:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
external_idqueryYour id for the person.
phonequeryA phone number, any Zimbabwean format.
meterqueryA meter number.
groupquerygrp_…
cost_centerquerycc_… (Workforce: a department's id, or none for sends booked to none).
blockedqueryBlocked or not.
is_activequeryActive or not.
erasedqueryErased or not.
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.

Returns: 200 RecipientList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/recipients \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /recipients

Create or update a recipient

recipients.create scope recipients:write Idempotency-Key optional

Upserts on external_id, then phone: 201 when made, 200 when updated. Scope: recipients:write.

Body (RecipientWrite)TypeAbout
external_idstring
phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
meterstringAn 11-digit ZESA prepaid meter number.
token_phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
namestring
payroll_idstring
cost_centerstring
notesstring
is_activeboolean
is_pinnedboolean
network_hintstring
daily_capstring or numberA decimal amount, e.g. "5.00".
weekly_capstring or numberA decimal amount, e.g. "5.00".
monthly_capstring or numberA decimal amount, e.g. "5.00".
lifetime_capstring or numberA decimal amount, e.g. "5.00".
blockedboolean
blocked_reasonstring
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.

Returns: 200 Recipient · 201 Recipient. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/recipients \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"external_id": "user_123", "name": "Tendai Moyo", "phone": "0772123456"}'
GET /recipients/{recipient_id}

Retrieve a recipient

recipients.retrieve scope recipients:read

Scope: recipients:read.

ParameterInAbout
recipient_id requiredpathrcp_… or ext:<external_id>.

Returns: 200 Recipient. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/recipients/{recipient_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
PATCH /recipients/{recipient_id}

Update a recipient

recipients.update scope recipients:write

Scope: recipients:write.

ParameterInAbout
recipient_id requiredpathrcp_… or ext:<external_id>.
Body (RecipientWrite)TypeAbout
external_idstring
phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
meterstringAn 11-digit ZESA prepaid meter number.
token_phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
namestring
payroll_idstring
cost_centerstring
notesstring
is_activeboolean
is_pinnedboolean
network_hintstring
daily_capstring or numberA decimal amount, e.g. "5.00".
weekly_capstring or numberA decimal amount, e.g. "5.00".
monthly_capstring or numberA decimal amount, e.g. "5.00".
lifetime_capstring or numberA decimal amount, e.g. "5.00".
blockedboolean
blocked_reasonstring
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.

Returns: 200 Recipient. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/recipients/{recipient_id} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"external_id": "user_123", "name": "Tendai Moyo", "phone": "0772123456"}'
DELETE /recipients/{recipient_id}

Deactivate or erase a recipient

recipients.delete scope recipients:write

Deactivates. With erase=true, erases their data and keeps the history masked; refused (409 erasure_deferred) while a send is in flight or a schedule is active. Scope: recipients:write.

ParameterInAbout
recipient_id requiredpathrcp_… or ext:<external_id>.
erasequerytrue: erase the person's data (deferred while anything is in flight).

Returns: 200 DeletedRecipient or Recipient. Errors: the envelope.

curl -X DELETE https://jusa.localhost.co.zw/dev/v1/recipients/{recipient_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /recipients/{recipient_id}/balance

A recipient's earnings pots

recipients.balance scope recipients:read

Scope: recipients:read.

ParameterInAbout
recipient_id requiredpathrcp_… or ext:<external_id>.

Returns: 200 RecipientBalance. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/recipients/{recipient_id}/balance \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /recipients/{recipient_id}/sends

List a recipient's sends

recipients.list_sends scope sends:read

Scope: sends:read.

ParameterInAbout
recipient_id requiredpathrcp_… or ext:<external_id>.
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.

Returns: 200 SendList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/recipients/{recipient_id}/sends \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /recipients/{recipient_id}/spend

A recipient's spend against their caps

recipients.spend scope recipients:read

Scope: recipients:read.

ParameterInAbout
recipient_id requiredpathrcp_… or ext:<external_id>.

Returns: 200 RecipientSpend. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/recipients/{recipient_id}/spend \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /recipients/{recipient_id}/statement

A person's statement

recipients.statement scope recipients:read

Everything sent to them in a period (this month by default; YYYY-MM-DD, inclusive), totalled by currency and outcome, with each send (up to 500). Scope: recipients:read.

ParameterInAbout
recipient_id requiredpathrcp_… or ext:<external_id>.
fromqueryThe start (inclusive).
toqueryThe end (inclusive).

Returns: 200 RecipientStatement. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/recipients/{recipient_id}/statement \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /recipients/bulk

Import recipients

recipients.bulk scope recipients:write Idempotency-Key optional

Up to 5,000, as JSON rows or CSV. Scope: recipients:write.

Body (RecipientBulk)TypeAbout
recipientsarray of anyRecipient rows (as POST /recipients); a bad row is reported in its place.
csvstring

Returns: 200 RecipientImport. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/recipients/bulk \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'

Programs

Reward programs: budget, caps, fraud rules.

GET /programs

List programs

programs.list scope rewards:read

Scope: rewards:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.

Returns: 200 ProgramList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/programs \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /programs

Create a program

programs.create scope programs:manage Idempotency-Key optional

The completions HMAC secret is shown once, here. A reserve program sets its whole budget aside (402 when the credit is short). Scope: programs:manage.

Body (ProgramCreate)TypeAbout
name requiredstring
descriptionstring
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
default_amount requiredstring or numberA decimal amount, e.g. "5.00".
budget requiredstring or numberA decimal amount, e.g. "5.00".
funding"draw" | "reserve"
choicesarray of "airtime" | "data" | "zesa"
status"draft" | "active"
sourcesarray of "api" | "completions" | "links"
one_per_phoneboolean
per_recipientobject
require_networkstring
throttle_per_hourinteger
min_account_agestring or integerSeconds, or "30m", "24h", "3d".
holdobject
on_zesa_down"queue" | "fail"
max_holdstring or integerSeconds, or "30m", "24h", "3d".
sms_messagestring
brandingobject
lawful_basisstring
sensitive_databoolean
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.
geofenceobject

Returns: 201 Program. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/programs \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"name": "Household survey Q4", "currency": "USD", "default_amount": "0.50", "budget": "500.00", "funding": "reserve", "per_recipient": {"lifetime": 1}}'
GET /programs/{program_id}

Retrieve a program

programs.retrieve scope rewards:read

Scope: rewards:read.

ParameterInAbout
program_id requiredpathprg_…

Returns: 200 Program. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/programs/{program_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
PATCH /programs/{program_id}

Update a program

programs.update scope programs:manage

Scope: programs:manage.

ParameterInAbout
program_id requiredpathprg_…
Body (ProgramUpdate)TypeAbout
namestring
descriptionstring
default_amountstring or numberA decimal amount, e.g. "5.00".
budgetstring or numberA decimal amount, e.g. "5.00".
choicesarray of "airtime" | "data" | "zesa"
sourcesarray of "api" | "completions" | "links"
one_per_phoneboolean
per_recipientobject
require_networkstring
throttle_per_hourinteger
min_account_agestring or integerSeconds, or "30m", "24h", "3d".
holdobject
on_zesa_down"queue" | "fail"
max_holdstring or integerSeconds, or "30m", "24h", "3d".
sms_messagestring
brandingobject
lawful_basisstring
sensitive_databoolean
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.
geofenceobject

Returns: 200 Program. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/programs/{program_id} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
DELETE /programs/{program_id}

Close a program

programs.delete scope programs:manage

The same as POST …/close. Scope: programs:manage.

ParameterInAbout
program_id requiredpathprg_…

Returns: 200 Program. Errors: the envelope.

curl -X DELETE https://jusa.localhost.co.zw/dev/v1/programs/{program_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /programs/{program_id}/activate

Activate a draft program

programs.activate scope programs:manage

Scope: programs:manage.

ParameterInAbout
program_id requiredpathprg_…

Returns: 200 Program. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/programs/{program_id}/activate \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /programs/{program_id}/close

Close a program

programs.close scope programs:manage

Scope: programs:manage.

ParameterInAbout
program_id requiredpathprg_…

Returns: 200 Program. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/programs/{program_id}/close \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /programs/{program_id}/completions

Report a completion (signed)

programs.create_completion signed with Jusa-Signature

For survey tools that can sign a webhook but hold no key: Jusa-Signature: t=<unix>,v1=<hex HMAC-SHA256(program secret, "{t}.{raw body}")>, within 5 minutes. completion_id (or ref) is required; a repeat returns the first reward (200).

ParameterInAbout
program_id requiredpathprg_…
Body (Completion)TypeAbout
completion_idstring
refstringAlias of completion_id.
recipientstring or objectrcp_… or ext:<external_id>.
phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
targetstringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
meterstringAn 11-digit ZESA prepaid meter number.
token_phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
amountstring or numberA decimal amount, e.g. "5.00".
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.

Returns: 200 Reward · 201 Reward. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/programs/{program_id}/completions \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /programs/{program_id}/defund

Take budget back

programs.defund scope programs:manage Idempotency-Key required

Without an amount: everything no escrow or unclaimed link has earmarked. Scope: programs:manage. Moves money: an Idempotency-Key is required.

ParameterInAbout
program_id requiredpathprg_…
Body (ProgramDefund)TypeAbout
amountstring or numberA decimal amount, e.g. "5.00".

Returns: 200 Program. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/programs/{program_id}/defund \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /programs/{program_id}/fund

Add budget (and reserved credit)

programs.fund scope programs:manage Idempotency-Key required

Scope: programs:manage. Moves money: an Idempotency-Key is required.

ParameterInAbout
program_id requiredpathprg_…
Body (ProgramFund)TypeAbout
amount requiredstring or numberA decimal amount, e.g. "5.00".

Returns: 200 Program. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/programs/{program_id}/fund \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"amount": "5.00"}'
POST /programs/{program_id}/pause

Pause a program

programs.pause scope programs:manage

Scope: programs:manage.

ParameterInAbout
program_id requiredpathprg_…

Returns: 200 Program. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/programs/{program_id}/pause \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /programs/{program_id}/resume

Resume a paused program

programs.resume scope programs:manage

Scope: programs:manage.

ParameterInAbout
program_id requiredpathprg_…

Returns: 200 Program. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/programs/{program_id}/resume \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /programs/{program_id}/roll-secret

Roll the completions secret

programs.roll_secret scope programs:manage

The old secret keeps working for 24 hours. Scope: programs:manage.

ParameterInAbout
program_id requiredpathprg_…

Returns: 200 Program. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/programs/{program_id}/roll-secret \
  -H "Authorization: Bearer jusa_sk_test_…"

Rewards

The seed primitive: reward a person for something they did.

GET /rewards

List rewards

rewards.list scope rewards:read

Scope: rewards:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.
programqueryprg_…
recipientqueryrcp_… or ext:<external_id>.
completion_idqueryThe completion it answers.
external_idqueryYour id for the person.
expand[]queryInline a related object in place of its id (repeat for more): recipient, program or batch on a send; recipient, program or send on a reward.
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.

Returns: 200 RewardList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/rewards \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /rewards

Reward someone

rewards.create scope rewards:write Idempotency-Key required

The seed primitive: caps, fraud rules and budget under one lock, then a send or a link. The same completion_id again returns the first reward (200). A refusal writes nothing, so it can be retried. Scope: rewards:write. Moves money: an Idempotency-Key is required.

Body (RewardCreate)TypeAbout
program requiredstring
recipient requiredstring or objectrcp_… or ext:<external_id>.
amountstring or numberA decimal amount, e.g. "5.00".
delivery"direct" | "link" | "choice"
type"airtime" | "data" | "zesa"
productstring
bundle_idstring
meterstringAn 11-digit ZESA prepaid meter number.
token_phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
completion_id requiredstringRequired: one reward per completion, for good.
holdboolean
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.
linkobject
reasonstring

Returns: 200 Reward · 201 Reward · 202 AgentApproval. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/rewards \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"program": "prg_\u2026", "recipient": {"external_id": "resp-8812", "phone": "0771230000"}, "completion_id": "8812", "metadata": {"survey": "form-hh-q4"}}'
GET /rewards/{reward_id}

Retrieve a reward

rewards.retrieve scope rewards:read

Scope: rewards:read.

ParameterInAbout
reward_id requiredpathrwd_…
expand[]queryInline a related object in place of its id (repeat for more): recipient, program or batch on a send; recipient, program or send on a reward.

Returns: 200 Reward. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/rewards/{reward_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /rewards/{reward_id}/release

Release an escrowed reward

rewards.release scope rewards:write Idempotency-Key required

Optionally for less. Scope: rewards:write. Moves money: an Idempotency-Key is required.

ParameterInAbout
reward_id requiredpathrwd_…
Body (RewardRelease)TypeAbout
amountstring or numberA decimal amount, e.g. "5.00".

Returns: 200 Reward. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/rewards/{reward_id}/release \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /rewards/{reward_id}/void

Void a reward

rewards.void scope rewards:write Idempotency-Key optional

Its budget goes back to the program. Scope: rewards:write.

ParameterInAbout
reward_id requiredpathrwd_…

Returns: 200 Reward. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/rewards/{reward_id}/void \
  -H "Authorization: Bearer jusa_sk_test_…"

Links and QR codes a person claims.

Reviews

Rewards held by a fraud rule, and your blocklist.

GET /blocklist

List blocked numbers and meters

blocklist.list scope rewards:read

Scope: rewards:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
targetqueryA phone (any format) or meter.

Returns: 200 BlockedTargetList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/blocklist \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /blocklist

Block a number or meter

blocklist.create scope account:manage

Live mode only. Scope: account:manage.

Body (BlockCreate)TypeAbout
target requiredstring
reasonstring

Returns: 201 BlockedTarget. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/blocklist \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"target": "string"}'
GET /blocklist/{entry_id}

Retrieve a block

blocklist.retrieve scope rewards:read

Scope: rewards:read.

ParameterInAbout
entry_id requiredpathblk_…

Returns: 200 BlockedTarget. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/blocklist/{entry_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
DELETE /blocklist/{entry_id}

Unblock

blocklist.delete scope account:manage

Scope: account:manage.

ParameterInAbout
entry_id requiredpathblk_…

Returns: 200 BlockedTarget. Errors: the envelope.

curl -X DELETE https://jusa.localhost.co.zw/dev/v1/blocklist/{entry_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /reviews

List fraud reviews

reviews.list scope rewards:read

Scope: rewards:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.
programqueryprg_…

Returns: 200 ReviewList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/reviews \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /reviews/{review_id}

Retrieve a review

reviews.retrieve scope rewards:read

Scope: rewards:read.

ParameterInAbout
review_id requiredpathrev_…

Returns: 200 Review. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/reviews/{review_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /reviews/{review_id}/approve

Approve a held reward

reviews.approve scope programs:manage Idempotency-Key required

Re-checks the caps and budget under lock, then sends. Scope: programs:manage. Moves money: an Idempotency-Key is required.

ParameterInAbout
review_id requiredpathrev_…
Body (ReviewDecision)TypeAbout
notestring

Returns: 200 Review. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/reviews/{review_id}/approve \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /reviews/{review_id}/reject

Reject a held reward

reviews.reject scope programs:manage Idempotency-Key optional

Scope: programs:manage.

ParameterInAbout
review_id requiredpathrev_…
Body (ReviewDecision)TypeAbout
notestring

Returns: 200 Review. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/reviews/{review_id}/reject \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'

Rules

Earnings pots, streaks, referrals and geofenced check-ins.

GET /activities

List activities

activities.list scope rewards:read

Scope: rewards:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
namequeryOnly activities with this name.
recipientqueryrcp_… or ext:<external_id>.
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.

Returns: 200 ActivityList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/activities \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /activities

Record an activity

activities.create scope rewards:write Idempotency-Key optional

Feeds the program rules; matches says what it triggered. activity_id dedupes for good (200). Scope: rewards:write.

Body (ActivityCreate)TypeAbout
recipient requiredstring or objectrcp_… or ext:<external_id>.
name requiredstring
activity_id requiredstring
occurred_atinteger or stringUnix seconds or an ISO 8601 time.
valuestring or numberA decimal amount, e.g. "5.00".
programstring
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.

Returns: 200 Activity · 201 Activity. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/activities \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"recipient": "ext:user_123", "name": "lesson.completed", "activity_id": "log-9981"}'
GET /activities/{activity_id}

Retrieve an activity

activities.retrieve scope rewards:read

Scope: rewards:read.

ParameterInAbout
activity_id requiredpathact_…

Returns: 200 Activity. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/activities/{activity_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /checkins

List check-ins

checkins.list scope rewards:read publishable keys

Scope: rewards:read. Publishable keys may call it (from a browser).

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.

Returns: 200 CheckinList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/checkins \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /checkins

Check in to claim a geofenced link

checkins.create Idempotency-Key optional publishable keys

Publishable keys may call it from the browser. Publishable keys may call it (from a browser).

Body (CheckinCreate)TypeAbout
link requiredstring
lat requirednumber or string
lng requirednumber or string
accuracy_mnumber or string
attestationstring
phone requiredstringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
choice"airtime" | "data" | "zesa"
meterstringAn 11-digit ZESA prepaid meter number.
bundle_idstring
codestring
challenge_tokenstring
challenge_answerstring

Returns: 201 Checkin. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/checkins \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"link": "string", "lat": 1, "lng": 1, "phone": "0772123456"}'
GET /programs/{program_id}/rules

List a program's rules

programs.list_rules scope rewards:read

Scope: rewards:read.

ParameterInAbout
program_id requiredpathprg_…
activequeryActive or not.

Returns: 200 ProgramRuleList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/programs/{program_id}/rules \
  -H "Authorization: Bearer jusa_sk_test_…"
PUT /programs/{program_id}/rules

Set a program's rules

programs.set_rules scope programs:manage

The whole set: rules left out are switched off. lottery and prize_draw are refused. Scope: programs:manage.

ParameterInAbout
program_id requiredpathprg_…
Body (ProgramRules)TypeAbout
rules requiredarray of object

Returns: 200 ProgramRuleList. Errors: the envelope.

curl -X PUT https://jusa.localhost.co.zw/dev/v1/programs/{program_id}/rules \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"rules": []}'
GET /referrals

List referrals

referrals.list scope rewards:read

Scope: rewards:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.
programqueryprg_…
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.

Returns: 200 ReferralList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/referrals \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /referrals

Record a referral

referrals.create scope rewards:write Idempotency-Key optional

Scope: rewards:write.

Body (ReferralCreate)TypeAbout
program requiredstring
referrer requiredstring or objectrcp_… or ext:<external_id>.
referee requiredstring or objectrcp_… or ext:<external_id>.
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.

Returns: 200 Referral · 201 Referral. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/referrals \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"program": "string", "referrer": "string", "referee": "string"}'
GET /referrals/{referral_id}

Retrieve a referral

referrals.retrieve scope rewards:read

Scope: rewards:read.

ParameterInAbout
referral_id requiredpathref_…

Returns: 200 Referral. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/referrals/{referral_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /referrals/{referral_id}/qualify

Qualify a referral

referrals.qualify scope rewards:write Idempotency-Key required

Pays the direct referrer (depth 1), in escrow by default. Scope: rewards:write. Moves money: an Idempotency-Key is required.

ParameterInAbout
referral_id requiredpathref_…

Returns: 200 Referral. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/referrals/{referral_id}/qualify \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)"

Claims

What a publishable key may do with a reward link.

GET /claims/{token}

Read a reward link by its token

claims.retrieve publishable keys

For publishable keys (the widget). Publishable keys may call it (from a browser).

ParameterInAbout
token requiredpathThe link's token.

Returns: 200 RewardLinkClaim. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/claims/{token} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /claims/{token}

Claim a reward link

claims.create Idempotency-Key optional publishable keys

Publishable keys may call it (from a browser).

ParameterInAbout
token requiredpathThe link's token.
Body (Claim)TypeAbout
phone requiredstringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
choice"airtime" | "data" | "zesa"
meterstringAn 11-digit ZESA prepaid meter number.
bundle_idstring
codestring
challenge_tokenstring
challenge_answerstring

Returns: 200 RewardLinkClaim · 201 RewardLinkClaim. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/claims/{token} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"phone": "0772123456"}'
POST /claims/{token}/code

Text the claim code (OTP-bound links)

claims.send_code publishable keys

Publishable keys may call it (from a browser).

ParameterInAbout
token requiredpathThe link's token.
Body (ClaimCodeRequest)TypeAbout
phone requiredstringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
challenge_tokenstring
challenge_answerstring

Returns: 200 ClaimCode. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/claims/{token}/code \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"phone": "0772123456"}'

Agent approvals

An agent key's request waiting for a person.

GET /approvals

List agent approvals

approvals.list
ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.
actionqueryOnly this action (send or reward; for the audit log, e.g. api_key.created).

Returns: 200 AgentApprovalList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/approvals \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /approvals/{approval_id}

Retrieve an agent approval

approvals.retrieve
ParameterInAbout
approval_id requiredpathapr_…

Returns: 200 AgentApproval. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/approvals/{approval_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /approvals/{approval_id}/approve

Approve an agent's request

approvals.approve

Dashboard only: an owner or admin, with their payment PIN.

ParameterInAbout
approval_id requiredpathapr_…
Body (ApprovalDecision)TypeAbout
pinstringYour payment PIN (dashboard only).

Returns: 200 AgentApproval. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/approvals/{approval_id}/approve \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /approvals/{approval_id}/reject

Reject an agent's request

approvals.reject

Dashboard only.

ParameterInAbout
approval_id requiredpathapr_…

Returns: 200 AgentApproval. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/approvals/{approval_id}/reject \
  -H "Authorization: Bearer jusa_sk_test_…"

MCP

The Model Context Protocol server for AI agents.

POST /mcp

The MCP server (JSON-RPC 2.0)

mcp.call

Agent keys only. Tools: send_airtime, buy_zesa, confirm_meter, create_reward, get_balance, get_send, list_products. 202 with no body answers a notification; a message that is not JSON-RPC is a JSON-RPC error with status 400.

Returns: 200 JsonRpcResponse · 202 no body · 400 JsonRpcResponse. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/mcp \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'

Jusa Credit

Your prepaid balance: transactions, top-ups, statements.

GET /credit

Jusa Credit balances

credit.retrieve scope credit:read

Scope: credit:read.

Returns: 200 Credit. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/credit \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /credit/alerts

Low-balance and shortfall alerts

credit.retrieve_alerts scope credit:read

Scope: credit:read.

Returns: 200 CreditAlerts. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/credit/alerts \
  -H "Authorization: Bearer jusa_sk_test_…"
PUT /credit/alerts

Set the alerts

credit.update_alerts scope account:manage

Scope: account:manage.

Body (CreditAlertsUpdate)TypeAbout
low_balanceobject
shortfall_projectionboolean
shortfall_daysinteger
channelsarray of "webhook" | "email" | "sms"

Returns: 200 CreditAlerts. Errors: the envelope.

curl -X PUT https://jusa.localhost.co.zw/dev/v1/credit/alerts \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
GET /credit/reservations

List credit set aside

credit.list_reservations scope credit:read

Scope: credit:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.
currencyqueryOnly this currency.

Returns: 200 CreditReservationList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/credit/reservations \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /credit/topups

List top-ups

credit.list_topups scope credit:read

Scope: credit:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
methodqueryGET, POST, …
statusqueryOnly objects in this status.

Returns: 200 TopupList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/credit/topups \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /credit/topups

Top up Jusa Credit

credit.create_topup scope credit:topup Idempotency-Key required

pesepay: send a person to redirect_url; the card fee is paid by the payer. bank_transfer: our bank details and a reference to quote. Any amount; spendable once paid. Scope: credit:topup. Moves money: an Idempotency-Key is required.

Body (TopupCreate)TypeAbout
method"pesepay" | "bank_transfer"
amount requiredstring or numberA decimal amount, e.g. "5.00".
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
descriptionstring

Returns: 201 Topup. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/credit/topups \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"method": "pesepay", "amount": "100.00", "currency": "USD"}'
GET /credit/topups/{topup_id}

Retrieve a top-up

credit.retrieve_topup scope credit:read

Scope: credit:read.

ParameterInAbout
topup_id requiredpathtop_…

Returns: 200 Topup. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/credit/topups/{topup_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /credit/transactions

List Jusa Credit transactions

credit.list_transactions scope credit:read

Scope: credit:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
currencyqueryOnly this currency.
typequeryOnly this type.
sourcequeryWhat it came from: snd_, top_, rwd_, bch_ or rsv_.

Returns: 200 CreditTransactionList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/credit/transactions \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /credit/transactions/{transaction_id}

Retrieve a transaction

credit.retrieve_transaction scope credit:read

Scope: credit:read.

ParameterInAbout
transaction_id requiredpathtxn_…

Returns: 200 CreditTransaction. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/credit/transactions/{transaction_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /statements

A monthly statement

statements.retrieve scope credit:read

Scope: credit:read.

ParameterInAbout
periodqueryYYYY-MM; this month by default.
currencyqueryOnly this currency.

Returns: 200 Statement. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/statements \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /statements.csv

A monthly statement as CSV

statements.csv scope credit:read

Scope: credit:read.

ParameterInAbout
periodqueryYYYY-MM; this month by default.
currencyqueryOnly this currency.

Returns: 200 CSV. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/statements.csv \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /statements.pdf

A monthly statement as PDF

statements.pdf scope credit:read

Scope: credit:read.

ParameterInAbout
periodqueryYYYY-MM; this month by default.
currencyqueryOnly this currency.

Returns: 200 no body. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/statements.pdf \
  -H "Authorization: Bearer jusa_sk_test_…"

Checkout

Your user pays by card; you get the attribution.

GET /checkout-sessions

List checkout sessions

checkout_sessions.list scope sends:read

Scope: sends:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.
client_referencequeryYour own reference.
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.

Returns: 200 CheckoutSessionList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/checkout-sessions \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /checkout-sessions

Create a checkout session

checkout_sessions.create scope sends:write Idempotency-Key required

Your user pays by card on Pesepay (Jusa is the merchant of record and the card fee is the payer's); the send is booked to you. A repeated client_reference returns the first session (200). Scope: sends:write. Moves money: an Idempotency-Key is required.

Body (CheckoutSessionCreate)TypeAbout
type"airtime" | "data" | "zesa"
productstring
target requiredstring
amount requiredstring or numberA decimal amount, e.g. "5.00".
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
bundle_idstring
token_phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
customer_phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
success_url requiredstring
cancel_url requiredstring
client_referencestring
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.
sms_messagestring

Returns: 200 CheckoutSession · 201 CheckoutSession. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/checkout-sessions \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"type": "zesa", "target": "37261502217", "amount": "20.00", "currency": "USD", "token_phone": "0772123456", "success_url": "https://example.com/thanks", "cancel_url": "https://example.com/basket"}'
GET /checkout-sessions/{session_id}

Retrieve a checkout session

checkout_sessions.retrieve scope sends:read

Scope: sends:read.

ParameterInAbout
session_id requiredpathcs_…

Returns: 200 CheckoutSession. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/checkout-sessions/{session_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /checkout-sessions/{session_id}/meter

Fix the meter while it waits

checkout_sessions.update_meter scope sends:write Idempotency-Key optional

Scope: sends:write.

ParameterInAbout
session_id requiredpathcs_…
Body (CheckoutMeter)TypeAbout
meter requiredstringAn 11-digit ZESA prepaid meter number.

Returns: 200 CheckoutSession. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/checkout-sessions/{session_id}/meter \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"meter": "37261502217"}'

Workforce

Employer allocation and GL, as the Jusa app has it.

GET /workforce/access

What this key may do in Workforce

workforce.access scope workforce:admin

Scope: workforce:admin.

Returns: 200 WorkforceAccess. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/workforce/access \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /workforce/allocate

Allocate airtime or ZESA

workforce.allocate scope workforce:admin Idempotency-Key optional

Scope: workforce:admin.

Body (WorkforceAllocate)TypeAbout
kind required"airtime" | "zesa"
product_refstring
cost_center_idinteger or stringA numeric id.
titlestring
custom_smsstring
idempotency_keystring8 to 64 of A-Z a-z 0-9 _ -. A repeat returns the send it made; the Idempotency-Key header does the same.
acknowledge_over_budgetboolean or stringtrue or false.
lines requiredarray of object

Returns: 200 WorkforceSendResult. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/workforce/allocate \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"kind": "airtime", "product_ref": "airtime-usd", "cost_center_id": 3, "idempotency_key": "stipend-2026-10", "lines": [{"recipient_id": 12, "amount": "2.00"}]}'
POST /workforce/allocate/preview

Preview an allocation

workforce.preview_allocation scope workforce:admin

Scope: workforce:admin.

Body (WorkforceAllocate)TypeAbout
kind required"airtime" | "zesa"
product_refstring
cost_center_idinteger or stringA numeric id.
titlestring
custom_smsstring
idempotency_keystring8 to 64 of A-Z a-z 0-9 _ -. A repeat returns the send it made; the Idempotency-Key header does the same.
acknowledge_over_budgetboolean or stringtrue or false.
lines requiredarray of object

Returns: 200 WorkforcePreview. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/workforce/allocate/preview \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"kind": "airtime", "product_ref": "airtime-usd", "cost_center_id": 3, "idempotency_key": "stipend-2026-10", "lines": [{"recipient_id": 12, "amount": "2.00"}]}'
GET /workforce/batches

Past allocations

workforce.list_batches scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
fromqueryThe start (inclusive).
toqueryThe end (inclusive).
statusqueryOnly objects in this status.
cost_centerquerycc_… (Workforce: a department's id, or none for sends booked to none).
pagequeryWorkforce pages from 1.
page_sizequeryWorkforce rows per page (batches up to 50, the ledger up to 100).

Returns: 200 WorkforceBatchPage. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/workforce/batches \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /workforce/batches/{reference}

One allocation

workforce.retrieve_batch scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
reference requiredpathA Workforce batch reference.

Returns: 200 WorkforceBatch. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/workforce/batches/{reference} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /workforce/batches/{reference}/resend

Resend the failed lines

workforce.resend scope workforce:admin Idempotency-Key optional

Scope: workforce:admin.

ParameterInAbout
reference requiredpathA Workforce batch reference.
Body (WorkforceResend)TypeAbout
item_idsarray of integer or stringOnly these lines (default: every line that can go again).
idempotency_keystring8 to 64 of A-Z a-z 0-9 _ -. A repeat returns the send it made; the Idempotency-Key header does the same.
acknowledge_over_budgetboolean or stringtrue or false.

Returns: 200 WorkforceSendResult. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/workforce/batches/{reference}/resend \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /workforce/batches/{reference}/resend/preview

Preview resending the failed lines

workforce.preview_resend scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
reference requiredpathA Workforce batch reference.
Body (WorkforceResend)TypeAbout
item_idsarray of integer or stringOnly these lines (default: every line that can go again).
idempotency_keystring8 to 64 of A-Z a-z 0-9 _ -. A repeat returns the send it made; the Idempotency-Key header does the same.
acknowledge_over_budgetboolean or stringtrue or false.

Returns: 200 WorkforcePreview. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/workforce/batches/{reference}/resend/preview \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
GET /workforce/cost-centers

Departments

workforce.list_cost_centers scope workforce:admin

Scope: workforce:admin.

Returns: 200 WorkforceDepartmentList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/workforce/cost-centers \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /workforce/cost-centers

Add a department

workforce.create_cost_center scope workforce:admin

Scope: workforce:admin.

Body (WorkforceDepartmentWrite)TypeAbout
namestring
codestringThe GL code.
monthly_budgetstring or numberA decimal amount, e.g. "5.00".
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
is_activeboolean or stringOn a change: false archives it.

Returns: 201 WorkforceDepartment. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/workforce/cost-centers \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"name": "Field sales", "code": "6100", "monthly_budget": "250.00", "currency": "USD"}'
PATCH /workforce/cost-centers/{cost_center_id}

Change a department

workforce.update_cost_center scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
cost_center_id requiredpathcc_…
Body (WorkforceDepartmentWrite)TypeAbout
namestring
codestringThe GL code.
monthly_budgetstring or numberA decimal amount, e.g. "5.00".
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
is_activeboolean or stringOn a change: false archives it.

Returns: 200 WorkforceDepartment. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/workforce/cost-centers/{cost_center_id} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"name": "Field sales", "code": "6100", "monthly_budget": "250.00", "currency": "USD"}'
GET /workforce/cost-centers/{cost_center_id}/people

A department's people

workforce.list_people scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
cost_center_id requiredpathcc_…

Returns: 200 WorkforceDepartmentPeople. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/workforce/cost-centers/{cost_center_id}/people \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /workforce/cost-centers/{cost_center_id}/people

Move people into a department

workforce.add_people scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
cost_center_id requiredpathcc_…
Body (WorkforcePeopleChange)TypeAbout
addarray of integer or stringPeople to move into it.
removearray of integer or stringPeople to take out.
newarray of objectPeople to add to the register and to it.

Returns: 200 WorkforceDepartmentPeople. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/workforce/cost-centers/{cost_center_id}/people \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"add": [12], "new": [{"name": "Rudo Moyo", "phone": "0772123456"}]}'
GET /workforce/cost-centers/{cost_center_id}/spend

A department's spend

workforce.cost_center_spend scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
cost_center_id requiredpathcc_…
fromqueryThe start (inclusive).
toqueryThe end (inclusive).

Returns: 200 WorkforceDepartmentSpend. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/workforce/cost-centers/{cost_center_id}/spend \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /workforce/journal

The GL journal

workforce.journal scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
fromqueryThe start (inclusive).
toqueryThe end (inclusive).
cost_centerquerycc_… (Workforce: a department's id, or none for sends booked to none).
formatqueryjson (default); csv for reports and the Workforce files (the ledger also takes csv-json, the file inside JSON); pdf for a receipt.

Returns: 200 WorkforceJournal or CSV. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/workforce/journal \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /workforce/ledger

The ledger

workforce.ledger scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
fromqueryThe start (inclusive).
toqueryThe end (inclusive).
currencyqueryOnly this currency.
kindqueryOnly this kind of entry (topup, debit, refund, adjustment).
qqueryEntries whose reference contains this.
formatqueryjson (default); csv for reports and the Workforce files (the ledger also takes csv-json, the file inside JSON); pdf for a receipt.
pagequeryWorkforce pages from 1.
page_sizequeryWorkforce rows per page (batches up to 50, the ledger up to 100).

Returns: 200 WorkforceLedger or WorkforceExport or CSV. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/workforce/ledger \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /workforce/meters

Meters on file

workforce.meters scope workforce:admin

Scope: workforce:admin.

Returns: 200 WorkforceMeters. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/workforce/meters \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /workforce/products

What Workforce can send

workforce.products scope workforce:admin

Scope: workforce:admin.

Returns: 200 WorkforceProducts. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/workforce/products \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /workforce/recipients

Add a person

workforce.create_recipient scope workforce:admin

Scope: workforce:admin.

Body (WorkforcePersonCreate)TypeAbout
name requiredstring
phone requiredstringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
payroll_idstring
cost_center_idinteger or stringA numeric id.
monthly_capstring or numberA decimal amount, e.g. "5.00".

Returns: 201 WorkforcePersonCreated. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/workforce/recipients \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"name": "Rudo Moyo", "phone": "0772123456", "cost_center_id": 3}'
PATCH /workforce/recipients/{recipient_id}

Change a person

workforce.update_recipient scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
recipient_id requiredpathrcp_… or ext:<external_id>.
Body (WorkforcePersonUpdate)TypeAbout
namestring
phonestringA Zimbabwean mobile: 07XXXXXXXX, 2637XXXXXXXX or +2637XXXXXXXX.
payroll_idstring
cost_center_idinteger or stringA numeric id.
monthly_capstring or numberA decimal amount, e.g. "5.00".
notesstring

Returns: 200 WorkforcePersonUpdated. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/workforce/recipients/{recipient_id} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
GET /workforce/recipients/{recipient_id}/statement

A person's statement

workforce.recipient_statement scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
recipient_id requiredpathrcp_… or ext:<external_id>.
fromqueryThe start (inclusive).
toqueryThe end (inclusive).

Returns: 200 WorkforceStatement. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/workforce/recipients/{recipient_id}/statement \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /workforce/schedules

Add a plan

workforce.create_schedule scope workforce:admin

Scope: workforce:admin.

Body (WorkforcePlanCreate)TypeAbout
name requiredstring
frequency required"daily" | "weekly" | "monthly"
hourinteger or string
minuteinteger or string
day_of_weekinteger or stringweekly: 0 (Monday) to 6.
day_of_monthinteger or stringmonthly: 1 to 28.
group_idinteger or stringA numeric id.
product_refstringAn airtime product; plans do not send ZESA.
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
amount_per_recipient requiredstring or numberA decimal amount, e.g. "5.00".
custom_smsstring
cost_center_idinteger or stringThe department it is booked to and sends to.

Returns: 201 WorkforcePlan. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/workforce/schedules \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"name": "Month-end airtime", "frequency": "monthly", "day_of_month": 25, "hour": 9, "amount_per_recipient": "5.00", "cost_center_id": 3}'
PATCH /workforce/schedules/{schedule_id}

Change a plan

workforce.update_schedule scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
schedule_id requiredpathsch_…
Body (WorkforcePlanUpdate)TypeAbout
namestring
frequency"daily" | "weekly" | "monthly"
hourinteger or string
minuteinteger or string
day_of_weekinteger or stringweekly: 0 (Monday) to 6.
day_of_monthinteger or stringmonthly: 1 to 28.
group_idinteger or stringA numeric id.
product_refstringAn airtime product; plans do not send ZESA.
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
amount_per_recipientstring or numberA decimal amount, e.g. "5.00".
custom_smsstring
cost_center_idinteger or stringThe department it is booked to and sends to.
is_activeboolean or stringfalse pauses it.

Returns: 200 WorkforcePlan. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/workforce/schedules/{schedule_id} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
DELETE /workforce/schedules/{schedule_id}

Remove a plan

workforce.delete_schedule scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
schedule_id requiredpathsch_…

Returns: 200 WorkforcePlanStopped. Errors: the envelope.

curl -X DELETE https://jusa.localhost.co.zw/dev/v1/workforce/schedules/{schedule_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /workforce/schedules/{schedule_id}/run-now

Run a plan now

workforce.run_schedule_now scope workforce:admin Idempotency-Key optional

Scope: workforce:admin.

ParameterInAbout
schedule_id requiredpathsch_…
Body (WorkforcePlanRunNow)TypeAbout
idempotency_keystring8 to 64 of A-Z a-z 0-9 _ -. A repeat returns the send it made; the Idempotency-Key header does the same.
againboolean or stringSend again when it already went out today.
acknowledge_over_budgetboolean or stringtrue or false.

Returns: 200 WorkforcePlanRun. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/workforce/schedules/{schedule_id}/run-now \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /workforce/schedules/{schedule_id}/run-now/preview

Preview running a plan

workforce.preview_schedule_run scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
schedule_id requiredpathsch_…

Returns: 200 WorkforcePreview. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/workforce/schedules/{schedule_id}/run-now/preview \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
GET /workforce/summary

The period summary

workforce.summary scope workforce:admin

Scope: workforce:admin.

ParameterInAbout
fromqueryThe start (inclusive).
toqueryThe end (inclusive).

Returns: 200 WorkforceSummary. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/workforce/summary \
  -H "Authorization: Bearer jusa_sk_test_…"

Family

MyFamilyTime (needs OAuth user tokens; not available yet).

GET /family

MyFamilyTime (not available yet)

family.get

Every /family route answers 501 not_yet_available: it needs OAuth user tokens. Use recipients and schedules for your own users.

Returns: an error. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/family \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /family

MyFamilyTime (not available yet)

family.post

Every /family route answers 501 not_yet_available: it needs OAuth user tokens. Use recipients and schedules for your own users.

Returns: an error. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/family \
  -H "Authorization: Bearer jusa_sk_test_…"
PUT /family

MyFamilyTime (not available yet)

family.put

Every /family route answers 501 not_yet_available: it needs OAuth user tokens. Use recipients and schedules for your own users.

Returns: an error. Errors: the envelope.

curl -X PUT https://jusa.localhost.co.zw/dev/v1/family \
  -H "Authorization: Bearer jusa_sk_test_…"
PATCH /family

MyFamilyTime (not available yet)

family.patch

Every /family route answers 501 not_yet_available: it needs OAuth user tokens. Use recipients and schedules for your own users.

Returns: an error. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/family \
  -H "Authorization: Bearer jusa_sk_test_…"
DELETE /family

MyFamilyTime (not available yet)

family.delete

Every /family route answers 501 not_yet_available: it needs OAuth user tokens. Use recipients and schedules for your own users.

Returns: an error. Errors: the envelope.

curl -X DELETE https://jusa.localhost.co.zw/dev/v1/family \
  -H "Authorization: Bearer jusa_sk_test_…"

Reports

Spend, success rate, the GL journal and CSV exports.

GET /exports

List exports

exports.list scope reports:read

Scope: reports:read.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.

Returns: 200 ExportList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/exports \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /exports

Export to CSV

exports.create scope reports:read Idempotency-Key optional

Built in the background; GET it for a signed url (an hour). Tokens are always masked. Scope: reports:read.

Body (ExportCreate)TypeAbout
type required"sends" | "rewards" | "transactions" | "deliveries"
frominteger or stringUnix seconds or an ISO 8601 time.
tointeger or stringUnix seconds or an ISO 8601 time.
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
statusstring

Returns: 202 Export. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/exports \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"type": "sends", "from": "2026-09-01T00:00:00Z"}'
GET /exports/{export_id}

Retrieve an export

exports.retrieve scope reports:read

Scope: reports:read.

ParameterInAbout
export_id requiredpathexp_…

Returns: 200 Export. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/exports/{export_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /exports/{export_id}/download

Download an export

exports.download no key: the URL is the credential

No key: the signed url is the credential.

ParameterInAbout
export_id requiredpathexp_…
tokenqueryThe signed download token from the export's url.

Returns: 200 CSV. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/exports/{export_id}/download
GET /reports/journal

GL journal

reports.journal scope reports:read

Scope: reports:read.

ParameterInAbout
fromqueryThe start (inclusive).
toqueryThe end (inclusive).
cost_centerquerycc_… (Workforce: a department's id, or none for sends booked to none).
formatqueryjson (default); csv for reports and the Workforce files (the ledger also takes csv-json, the file inside JSON); pdf for a receipt.

Returns: 200 Report or CSV. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/reports/journal \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /reports/spend

Spend report

reports.spend scope reports:read

Delivered sends paid from Jusa Credit at face value; group_by day, network, product, program, cost_center or recipient. Scope: reports:read.

ParameterInAbout
fromqueryThe start (inclusive).
toqueryThe end (inclusive).
currencyqueryOnly this currency.
group_byqueryHow rows are grouped.

Returns: 200 Report. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/reports/spend \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /reports/success-rate

Success-rate report

reports.success_rate scope reports:read

delivered ÷ (delivered + failed); group_by network or product. Scope: reports:read.

ParameterInAbout
fromqueryThe start (inclusive).
toqueryThe end (inclusive).
currencyqueryOnly this currency.
group_byqueryHow rows are grouped.

Returns: 200 Report. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/reports/success-rate \
  -H "Authorization: Bearer jusa_sk_test_…"

Webhooks

Endpoints, events and deliveries.

GET /events

List events

events.list scope webhooks:manage

The catch-up feed, 30 days. type takes a family: send.* Scope: webhooks:manage.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
typequeryOnly this type.
objectqueryEvents about this object, e.g. snd_…

Returns: 200 EventList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/events \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /events/{event_id}

Retrieve an event

events.retrieve scope webhooks:manage

Scope: webhooks:manage.

ParameterInAbout
event_id requiredpathevt_…

Returns: 200 Event. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/events/{event_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /send-tokens/{token}

Read a ZESA token from its link

send_tokens.retrieve no key: the URL is the credential

The token_url in a send.delivered event: no key (the url is the credential), once, within 15 minutes.

ParameterInAbout
token requiredpathThe link's token.

Returns: 200 SendToken. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/send-tokens/{token}
GET /webhook-deliveries

List webhook deliveries

webhook_deliveries.list scope webhooks:manage

Scope: webhooks:manage.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
endpointquerywe_…
eventqueryevt_…
typequeryOnly this type.
statusqueryOnly objects in this status.

Returns: 200 WebhookDeliveryList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/webhook-deliveries \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /webhook-deliveries/{delivery_id}

Retrieve a delivery

webhook_deliveries.retrieve scope webhooks:manage

Scope: webhooks:manage.

ParameterInAbout
delivery_id requiredpathwhd_…

Returns: 200 WebhookDelivery. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/webhook-deliveries/{delivery_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /webhook-deliveries/{delivery_id}/redeliver

Redeliver

webhook_deliveries.redeliver scope webhooks:manage Idempotency-Key optional

Scope: webhooks:manage.

ParameterInAbout
delivery_id requiredpathwhd_…

Returns: 202 WebhookDelivery. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/webhook-deliveries/{delivery_id}/redeliver \
  -H "Authorization: Bearer jusa_sk_test_…"
GET /webhook-endpoints

List webhook endpoints

webhook_endpoints.list scope webhooks:manage

Scope: webhooks:manage.

ParameterInAbout
limitqueryRows per page.
starting_afterqueryAn object id: the page after it (next_cursor).
ending_beforequeryAn object id: the page before it.
created[gte]queryUnix seconds or ISO 8601.
created[lte]queryUnix seconds or ISO 8601.
created[gt]queryUnix seconds or ISO 8601.
created[lt]queryUnix seconds or ISO 8601.
statusqueryOnly objects in this status.
metadata[key]querymetadata[<key>]=<value>: an exact match on one key.

Returns: 200 WebhookEndpointList. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/webhook-endpoints \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /webhook-endpoints

Add a webhook endpoint

webhook_endpoints.create scope webhooks:manage Idempotency-Key optional

https only, public addresses only. The signing secret is shown here. Up to 16 per mode. Scope: webhooks:manage.

Body (WebhookEndpointCreate)TypeAbout
url requiredstring
enabled_eventsarray of string"*", a family like "send.*", or types.
descriptionstring
api_versionstring
include_tokensboolean
token_linksbooleanNeeds tokens:read. On by default when the caller has it.
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.
format"jusa" | "legacy_airtime"

Returns: 201 WebhookEndpoint. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/webhook-endpoints \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/jusa/webhooks", "enabled_events": ["send.*", "reward.*"]}'
GET /webhook-endpoints/{endpoint_id}

Retrieve a webhook endpoint

webhook_endpoints.retrieve scope webhooks:manage

Scope: webhooks:manage.

ParameterInAbout
endpoint_id requiredpathwe_…

Returns: 200 WebhookEndpoint. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/webhook-endpoints/{endpoint_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
PATCH /webhook-endpoints/{endpoint_id}

Update a webhook endpoint

webhook_endpoints.update scope webhooks:manage

Scope: webhooks:manage.

ParameterInAbout
endpoint_id requiredpathwe_…
Body (WebhookEndpointUpdate)TypeAbout
urlstring
enabled_eventsarray of string"*", a family like "send.*", or types.
descriptionstring
api_versionstring
include_tokensboolean
token_linksbooleanNeeds tokens:read. On by default when the caller has it.
metadataobjectUp to 20 keys (letters, digits, _ . -, up to 40 characters) with values up to 500 characters.
format"jusa" | "legacy_airtime"
status"enabled" | "disabled"

Returns: 200 WebhookEndpoint. Errors: the envelope.

curl -X PATCH https://jusa.localhost.co.zw/dev/v1/webhook-endpoints/{endpoint_id} \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
DELETE /webhook-endpoints/{endpoint_id}

Delete a webhook endpoint

webhook_endpoints.delete scope webhooks:manage

Scope: webhooks:manage.

ParameterInAbout
endpoint_id requiredpathwe_…

Returns: 200 WebhookEndpoint. Errors: the envelope.

curl -X DELETE https://jusa.localhost.co.zw/dev/v1/webhook-endpoints/{endpoint_id} \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /webhook-endpoints/{endpoint_id}/replay

Replay events

webhook_endpoints.replay scope webhooks:manage Idempotency-Key optional

Every stored event in a time range, again. Scope: webhooks:manage.

ParameterInAbout
endpoint_id requiredpathwe_…
Body (WebhookReplayCreate)TypeAbout
from requiredinteger or stringUnix seconds or an ISO 8601 time.
tointeger or stringUnix seconds or an ISO 8601 time.
typesarray of string

Returns: 202 WebhookReplay. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/webhook-endpoints/{endpoint_id}/replay \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"from": 1}'
POST /webhook-endpoints/{endpoint_id}/roll-secret

Roll the signing secret

webhook_endpoints.roll_secret scope webhooks:manage Idempotency-Key optional

The old secret signs alongside for the grace (two v1= values). Scope: webhooks:manage.

ParameterInAbout
endpoint_id requiredpathwe_…
Body (WebhookRollSecret)TypeAbout
grace"0" | "1h" | "24h"

Returns: 200 WebhookEndpoint. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/webhook-endpoints/{endpoint_id}/roll-secret \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /webhook-endpoints/{endpoint_id}/test

Send a test event

webhook_endpoints.send_test scope webhooks:manage Idempotency-Key optional

Scope: webhooks:manage.

ParameterInAbout
endpoint_id requiredpathwe_…
Body (WebhookTest)TypeAbout
typestring

Returns: 202 WebhookDelivery. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/webhook-endpoints/{endpoint_id}/test \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'

Test mode

Helpers for test keys only.

POST /test/approvals/{approval_id}/approve

Approve (test mode)

test_helpers.approve_approval Idempotency-Key required test mode only

Moves money: an Idempotency-Key is required. Test keys only: a live key gets 403 test_mode_only.

ParameterInAbout
approval_id requiredpathapr_…

Returns: 200 AgentApproval. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/test/approvals/{approval_id}/approve \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Idempotency-Key: $(uuidgen)"
POST /test/approvals/{approval_id}/reject

Reject (test mode)

test_helpers.reject_approval Idempotency-Key optional test mode only

Test keys only: a live key gets 403 test_mode_only.

ParameterInAbout
approval_id requiredpathapr_…

Returns: 200 AgentApproval. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/test/approvals/{approval_id}/reject \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /test/checkout-sessions/{session_id}/pay

Pay a test checkout

test_helpers.pay_checkout scope sends:write test mode only

Scope: sends:write. Test keys only: a live key gets 403 test_mode_only.

ParameterInAbout
session_id requiredpathcs_…
Body (TestCheckoutPay)TypeAbout
outcome"paid" | "failed"

Returns: 200 CheckoutSession. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/test/checkout-sessions/{session_id}/pay \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /test/checkout-sessions/{session_id}/review

Play staff on a held test checkout

test_helpers.review_checkout scope sends:write test mode only

Scope: sends:write. Test keys only: a live key gets 403 test_mode_only.

ParameterInAbout
session_id requiredpathcs_…
Body (TestCheckoutReview)TypeAbout
action required"send" | "credit"
notestring

Returns: 200 CheckoutSession. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/test/checkout-sessions/{session_id}/review \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"action": "send"}'
POST /test/credit

Add test Jusa Credit

test_helpers.add_credit scope credit:topup Idempotency-Key optional test mode only

Scope: credit:topup. Test keys only: a live key gets 403 test_mode_only.

Body (TestCreditCreate)TypeAbout
currency"USD" | "ZWG" | "usd" | "zwg"USD or ZWG (either case).
amount requiredstring or numberA decimal amount, e.g. "5.00".

Returns: 201 TestCredit. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/test/credit \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"currency": "USD", "amount": "500.00"}'
POST /test/events/trigger

Fire a sample event

test_helpers.trigger_event scope webhooks:manage test mode only

Scope: webhooks:manage. Test keys only: a live key gets 403 test_mode_only.

Body (TestTrigger)TypeAbout
type requiredstringAn event type, e.g. send.delivered.

Returns: 201 Event. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/test/events/trigger \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"type": "string"}'
POST /test/reset

Wipe test mode

test_helpers.reset scope account:manage test mode only

Sends, rewards, batches, ledger and events go; keys, endpoints and recipients stay; credit is reset. Scope: account:manage. Test keys only: a live key gets 403 test_mode_only.

Returns: 200 TestReset. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/test/reset \
  -H "Authorization: Bearer jusa_sk_test_…"
POST /test/sends/{send_id}/advance

Move a test send on

test_helpers.advance_send scope sends:write test mode only

Without to: the next scripted step now. A move a live send could not make is 409. Scope: sends:write. Test keys only: a live key gets 403 test_mode_only.

ParameterInAbout
send_id requiredpathsnd_… (or the bare hex of a legacy order).
Body (TestAdvance)TypeAbout
to"delivered" | "failed" | "late_delivered"

Returns: 200 Send. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/test/sends/{send_id}/advance \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /test/zesa/availability

Take ZESA down or up (test mode)

test_helpers.set_zesa_availability scope sends:write test mode only

Scope: sends:write. Test keys only: a live key gets 403 test_mode_only.

Body (TestZesa)TypeAbout
state required"up" | "down"

Returns: 200 ZesaAvailability. Errors: the envelope.

curl -X POST https://jusa.localhost.co.zw/dev/v1/test/zesa/availability \
  -H "Authorization: Bearer jusa_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"state": "up"}'

Utility

Health.

GET /health

Check the API is up

health.retrieve no key: the URL is the credential

No key needed.

Returns: 200 Health. Errors: the envelope.

curl https://jusa.localhost.co.zw/dev/v1/health