Skip to content

API and resource commands

One grammar for every resource, plus a raw request when you need it.

Most of the ninety one commands are the same five verbs applied to eleven resources. Learn the grammar once and you can drive the whole API without opening the reference.

The shape
wajub <resource> list
wajub <resource> retrieve <id>
wajub <resource> create -d field=value
wajub <resource> update <id> -d field=value
wajub <resource> delete <id>

payments, customers, refunds, transfers, beneficiaries, links, invoices, events, disputes, accounts and webhooks all answer to it, minus the verbs that make no sense for them. There is no refunds update, because a refund cannot be edited. shield, identity and skills have their own shape, covered at the end of this page.

Sending a body

Two ways, and they do not mix. -d is for the common case, --data-raw for everything else.

Repeatable fields, or one JSON object
# Repeatable key=value. Dots build nested objects.
wajub payments create \
  -d amount=25000 \
  -d currency=XAF \
  -d channel=cm.mtn \
  -d customer.email=amina@example.com \
  -d customer.phone=+237670000000

# Anything with arrays, or a body you already have
wajub webhooks create --data-raw '{
  "url": "https://example.com/webhooks/wajub",
  "events": ["payment.succeeded", "payment.failed"]
}'

Arrays need `--data-raw`

-d builds objects through dots, not lists. events[0]=payment.succeeded is sent as a field literally named events[0], which the API rejects. Any body with an array goes through --data-raw.

Listing

Every list command carries the same nine flags.

FlagEffect
--limitMaximum items. With --all, caps the total rather than the page
--pageA specific page
--allFollow pagination until the end, or until --limit
-q, --searchFull text search
-f, --filterkey=value, repeatable, sent as a query parameter
--statusFilter by status
--sinceCreated on or after, YYYY-MM-DD
--untilCreated on or before, YYYY-MM-DD
--expandExpand a nested resource, repeatable
Narrowing a list
wajub payments list --limit 5
wajub payments list --status failed --since 2026-01-01
wajub payments list -q amina@example.com
wajub transfers list --all --limit 500 --json > transfers.json

--since and --until are whole days. The API filters payments on the calendar date, so there is no way to ask for the last two hours from here. For a narrower window, list a day and filter the JSON yourself.

Raw requests

When a route has no dedicated command, or you want to see exactly what the API returns, three commands take any path.

get, post and delete
wajub get /balance
wajub get "/payments?per_page=5&status=succeeded"
wajub get /tax/settings

wajub post /customers -d email=amina@example.com -d name="Amina Nkem"
wajub post /transfers --data-raw '{"amount":100000,"currency":"XAF","beneficiary":"ben_…"}'

wajub delete /payments/trx_CSUGajfv9xh0XQ5wu2lx

The path is relative to the API base, and the key comes from your profile, so there is no header to remember and no key in your history. Quote any path containing &, or your shell eats it.

Two flags that change what the request means

--idempotency-key sets the Idempotency-Key header. The CLI generates a fresh one for every create call anyway, so you only pass it explicitly when you want two invocations to be treated as the same attempt. It is honoured on payments, transfers and refunds.

--sync sets the X-Sync header, making the call act on behalf of a connected account. It is available on every write command, and on nothing that would not make sense.

Retrying safely, and acting for a connected merchant
# Run this twice: the second call returns the first result, it does not charge twice
wajub payments create -d amount=25000 -d currency=XAF --idempotency-key order-4172

# Create the payment on a connected merchant's account
wajub payments create -d amount=25000 -d currency=XAF --sync acc_7Yh2MpL4tRb3nP8sZcXv

Output

Human output is a table. --json prints the API response envelope unchanged, so lists come back under items and the balance under balance. That is what you pipe.

Composing with jq
wajub payments list --status failed --json \
  | jq -r '.items[] | [.id, .amount, .failure_reason] | @tsv'

wajub balance --json | jq '.balance.available'

# Every failed payout of the month, as CSV
wajub transfers list --status failed --since 2026-01-01 --json \
  | jq -r '.items[] | [.id, .amount, .currency, .failure_reason] | @csv'

Commands that ask before acting

Anything that moves money or destroys a record prompts for confirmation. --yes (or -y, or the alias --force) skips the prompt, which is what you want in a script and nowhere else.

CommandWhy it asks
transfers createSends money
refunds createSends money back
payments processCharges the payer
webhooks rotate-secretInvalidates the secret your server is using
accounts tokenRotates a connected account's token
Every deleteRemoves the record

The read only commands

Four commands need no arguments and answer questions you would otherwise look up.

Reference data and your own balance
wajub balance      # your balance, per currency
wajub channels     # every channel slug you may use
wajub countries    # supported countries
wajub currencies   # supported currencies

wajub channels is the one to reach for when a payment fails with an unknown channel: it lists exactly the slugs your account may use, which is not always the full catalogue.

In sandbox, wajub balance returns a simplified object where available equals total and the other figures are zero. The full breakdown exists in live only.

Identity and Shield

Two topics do not follow the CRUD grammar because the underlying products do not.

Name lookup and fraud controls
# Who owns this wallet, before you send money to it
wajub identity resolve -d phone=+237670000000 -d channel=cm.mtn
wajub identity validate -d account=00012345678 -d bank=afriland

# Shield
wajub shield stats
wajub shield settings
wajub shield blocklist
wajub shield block +237670000000 --type phone --reason "chargeback ring"
wajub shield unblock blk_9mWvL2xR7tB5nY4hC6dF

--type accepts email, phone, ip, country and card_bin. Shield's blocklist applies to live traffic, so a sandbox profile is not where you test whether a block works.

What did you think of this content?