Concepts
Keys and authentication
Four kinds of key, two modes, scopes and caps.
curl https://jusa.localhost.co.zw/dev/v1/credit -H "Authorization: Bearer jusa_sk_test_…"
Four kinds of key
| Kind | Looks like | For |
|---|---|---|
Secret sk | jusa_sk_live_… | Your server. Every scope. |
Restricted rk | jusa_rk_live_… | A service that needs less: the scopes you choose. A survey relay needs only rewards:write. |
Publishable pk | jusa_pk_live_… | A web page: the catalog, status, and claiming a reward link with its token. Narrow it with allowed_origins. |
Agent ak | jusa_ak_live_… | An AI agent over MCP: sends and direct rewards within a threshold and to known numbers; anything more waits for a person. |
A key is shown once, when it is made; after that it is jusa_sk_live_…a1b2. Keys are
stored hashed, so Jusa cannot show it again either. _test_ or _live_ sets the mode. Legacy
atk_ keys (Authorization: Api-Key atk_…) keep working and show as "legacy secret key".
Test and live mode
The key decides: a test key reaches your sandbox twin, a live key your organisation. They share nothing: an id from
one mode is 404 in the other. See Test mode.
Scopes
A secret key holds all of them. A restricted or agent key holds the ones it was given; a call it cannot make is
403 insufficient_scope. A key can never make a key with more than it has.
| Scope | Allows |
|---|---|
catalog:read | Products, data bundles and networks. |
lookups:read | Phone lookups, ZESA meter confirmation and quotes. |
sends:read | Sends, batches, schedules, checkout sessions and disputes. |
sends:write | Create, cancel and resend sends; checkout sessions; disputes. |
tokens:read | ZESA tokens in the clear; without it they are masked (•••• 1234). Off by default on restricted and agent keys. |
batches:write | Create, approve, cancel and retry batches. |
schedules:write | Create and change schedules and their runs; run one now. |
credit:read | Jusa Credit: balances, transactions, top-ups, reservations, alerts and statements. |
credit:topup | Start a top-up (and, in test mode, add test credit). |
recipients:read | Recipients, groups and cost centres, and their spend. |
recipients:write | Create, change, erase and import recipients; groups and cost centres. |
rewards:read | Programs, rewards, reward links, reviews, rules, activities, referrals and check-ins. |
rewards:write | Reward someone; mint and revoke links; record activities and referrals. |
programs:manage | Create and change programs, fund and defund them, decide reviews, set rules. |
webhooks:manage | Webhook endpoints, the events feed and the delivery log. |
reports:read | Reports and CSV exports. |
workforce:admin | The Workforce tree: departments, plans, allocation, the ledger and journal. |
family:write | Reserved for OAuth user tokens (MyFamilyTime); not available yet. |
account:manage | Keys, the request and audit logs, test-mode reset; members and settings also need an owner or admin behind the key. |
People and roles
On the dashboard your role decides: an owner or admin can do everything; a
developer has keys, webhooks, logs and all of test mode, but spends nothing live; a
viewer reads. Changing members or account settings with a key needs an owner or admin to have made
that key, and to still be one. An invitation (POST /members) waits until the person signs in and accepts
it on their dashboard; until then they are not a member and see nothing. A developer or viewer works through this
API and the dashboard only: the Jusa site and app never act for them on your account.
Controls on a key
allowed_ips: IPs or CIDRs it works from (403 ip_not_allowedelsewhere).expires_at: it stops on its own.spend_capsper currency (per_send,daily,monthly) andper_recipient_daily_cap: checked with the send, under the balance lock (403 key_spend_cap_reached).GET /keys/{id}shows itsusage.allowed_productsandallowed_networks(403 product_not_allowed).- Agent keys:
approval_thresholdover a rolling 24 hours and arecipient_allowlist. Over the threshold, or to a number it does not know, the answer is202with anagent_approval; nothing is written or charged until an owner or admin approves it with their payment PIN.
A secret key with any of these controls is refused on the legacy /api/v1, which cannot enforce them,
and on Workforce writes (403 key_controls_not_enforced): send through /v1/batches and
/v1/schedules instead. A key never loosens its own limits (caps, products, networks, IPs, expiry), and a
key with spend controls makes only publishable keys and changes or rolls only itself
(403 key_controls_escalation): a leaked capped key stays capped. A person on the dashboard, or a key
without controls, manages them.
Rolling and revoking
POST /keys/{id}/roll {"grace_period": "24h"} makes a new key and keeps the old one working for
0, 1h, 24h or 7d; the two share their caps meanwhile.
DELETE /keys/{id} revokes at once. A revoked, expired or unknown key all get the same
401 invalid_api_key. Jusa warns you (api_key.expiring) a week before a key expires.
Browsers
A secret, restricted or agent key sent from a web page (with an Origin header) is refused
(403 key_not_for_browsers): a secret in a browser is a leaked secret. Publishable keys are the only
ones a page may hold; the endpoints they may call answer CORS without cookies.
Versions
The path is /v1; the dated version is the Jusa-Version header. Your account is pinned to
the version current when its first key was made, and every response says which version answered. Within a version
changes only add. A retired version answers with Deprecation and Sunset headers until it is
gone. Supported now: 2026-10-01.
See the changelog.
Rate limits
Per key: live 50 requests a second and 3000 a minute; test
20 a second and 1200 a minute. Every response carries
RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; over the limit is
429 rate_limited with Retry-After. Meter lookups have their own quota (30 a minute per key,
and a daily quota per account tied to what you send), so they cannot be used to harvest names.
Request ids
Every response has a Jusa-Request-Id (req_…). Your request log keeps each request for 30
days, with bodies masked; quote the id to support.