jami_create_checkout
Create checkout
Start a checkout and get back how the buyer pays — either for one of the organization's published products, or for ANY amount with no product ("charge this buyer 1,237 ETB for order #1042").
Buyers pay in ETB via Telebirr, M-Pesa or CBE Birr; nothing is added on top. In LIVE mode a "direct" checkout pushes a real payment prompt to the buyer's phone, so only call this when the user actually wants to charge someone.
What to charge:
- Product: product_id (p_… token or 24-char id, must be published). amount_minor only for flexible-price (pwyw) products.
- Custom amount: omit product_id, pass amount_minor (≥ 100 = 1.00 ETB) and description (what the buyer sees, ≤ 120 chars).
How to collect it:
- Direct push: pass gateway + customer_email + customer_phone. The prompt goes straight to the buyer's phone (kind 'direct').
- Payment link: omit gateway. You get a Jami link in checkout_url (kind 'redirect') to send the buyer by SMS/Telegram/email; they pick the method and enter their details there, and can retry a failed attempt until the link expires (30 min). customer_* is optional.
Args:
- customer_email / customer_phone: required for a direct push; optional for a link. Phone like "251911223344" or "0911223344". Sandbox magic numbers: 251900000001 = instant success, 251900000002 = instant failure.
- gateway: 'telebirr' | 'mpesa' | 'cbe'. Omit for a payment link.
- amount_minor: integer santim. 123700 = 1,237.00 ETB.
- metadata: optional string map (e.g. { "orderId": "1042" }) stored on the order and sent back on order webhooks.
Returns: { session_id, kind: 'free' | 'redirect' | 'direct', checkout_url, order_id }.
- free: completed instantly; order_id is set.
- redirect: send the buyer to checkout_url.
- direct: the buyer confirms on their phone; call jami_wait_for_checkout next.
Parametersgatewaymetadataproduct_iddescriptionsuccess_urlamount_minorcheckout_linkcustomer_namecustomer_emailcustomer_phonecollected_fieldscustomer_external_id
jami_get_checkout_status
Get checkout status
Check a checkout session once. The server reconciles with the payment provider on every call, so this resolves even when webhooks are late.
Args:
- session_id: from jami_create_checkout.
Returns: { session_id, status: 'open' | 'completed' | 'expired', order_id, order_status, amount_minor, amount, expires_at, failure }.
When status is 'expired', failure.code explains why: payment_rejected · payment_failed · payment_canceled · payment_timed_out · initiate_failed · session_expired.
Use jami_wait_for_checkout instead if you want to block until the buyer finishes.
Parameterssession_idrequiredresponse_format
jami_wait_for_checkout
Wait for checkout
Poll a checkout session until it's paid or ends, up to timeout_seconds. Reports progress while it waits.
If the buyer hasn't finished when the timeout hits, this returns status 'open' (not an error) — call it again to keep waiting. Sessions expire 30 minutes after creation.
Args:
- session_id: from jami_create_checkout.
- timeout_seconds: 5–300 (default 90).
- interval_seconds: 2–30 between polls (default 3).
Returns the same shape as jami_get_checkout_status.
Parameterssession_idrequiredresponse_formattimeout_secondsinterval_seconds
jami_create_product
Create product
Create a product the organization sells, and get its public Jami page (url) and p_… token. Published by default, so the page and checkouts work immediately.
Pricing (can't be changed after creation; the amounts can):
- fixed: amount_minor is the price, e.g. 250000 = 2,500.00 ETB.
- pwyw (flexible): minimum_amount_minor; each checkout then sends its own amount.
- free: no price.
Subscription plans: product_type 'recurring' + pricing_model 'fixed' + billing_interval ('week' | 'month' | 'year') and optional interval_count (default 1; ≤ 52 weeks, ≤ 12 months, 1 year). amount_minor is charged from the payer's Jami wallet every period, 100–1,000,000 santim (1–10,000 ETB). The interval is locked after creation; a new price only applies to new subscribers.
Args: title (1–120 chars), pricing_model, amount_minor / minimum_amount_minor, product_type? ('one_time' default | 'recurring'), billing_interval?, interval_count?, description?, status? ('published' | 'draft').
Returns: { product_id, token, url, title, status, pricing_model, product_type, billing_interval, interval_count, price }. To charge for a one-time product, call jami_create_checkout with product_id = token (omit gateway for a payment link). For a recurring plan, call jami_create_subscription_link instead.
Parameterstitlerequiredstatusdescriptionamount_minorproduct_typepricing_modelrequiredinterval_countresponse_formatbilling_intervalminimum_amount_minor
jami_update_product
Update product
Change a product's title, description, status or price amount. The pricing model itself is locked.
Args: product_id (p_… token or id), and any of: title, description, status ('published' | 'draft' | 'archived' — archived stops new sales), amount_minor (fixed price), minimum_amount_minor (flexible floor).
Parameterstitlestatusproduct_idrequireddescriptionamount_minorresponse_formatminimum_amount_minor
jami_get_product
Get product
Fetch one product by its p_… token or id: price, status, its public page url, and for a subscription plan its billing interval.
Args:
- product_id: p_… token or 24-character id.
Returns: { product_id, token, url, title, status, pricing_model, product_type, billing_interval, interval_count, price }.
Parametersproduct_idrequiredresponse_format
jami_list_products
List products
List the organization's products, newest first.
Args: status ('published' | 'draft' | 'archived', optional), page (default 1), limit 1–100 (default 20).
Returns: { products: [{ product_id, token, url, title, status, pricing_model, price }], total, page, limit, count, has_more, next_page }.
Parameterspagelimitstatusresponse_format
jami_create_subscription_link
Create subscription link
Create a hosted subscribe link for a recurring product (a plan made with jami_create_product product_type 'recurring'). Send the link to the payer.
Nothing is charged by this call. An API key can only PROPOSE a subscription: the payer opens the link, signs in to Jami, reviews the plan, amount and cadence, and approves with their Jami wallet PIN. Only then is the first period charged from their Jami wallet; Jami renews it every period after that until either side cancels. The link is single use and valid 24 hours.
Args:
- product_id: the recurring product's p_… token or id (must be published).
- customer_email: optional, prefills / tags the payer.
- customer_external_id: optional, your own user id (≤ 200 chars); it comes back on the subscription and its webhooks, and jami_list_subscriptions can filter by it.
- success_url / cancel_url: optional absolute http(s) URLs. success_url gets ?subscription_id=sub_… (and {SUBSCRIPTION_ID} is filled).
- metadata: optional string map (≤ 20 keys) copied onto the subscription.
Returns: { checkout_id: 'sc_…', url, expires_at, livemode }. Watch for the subscription.created webhook, or call jami_list_subscriptions, to see the approval.
Parametersmetadatacancel_urlproduct_idrequiredsuccess_urlcustomer_emailresponse_formatcustomer_external_id
jami_list_subscriptions
List subscriptions
List the organization's wallet subscriptions, newest first.
Args:
- status: 'active' | 'past_due' | 'canceled' (optional). past_due = a renewal failed and is being retried.
- product_id: optional p_… token or id of the plan.
- customer_external_id: optional, your own user id passed when the link was created.
- page (default 1), limit 1–100 (default 20).
Returns: { subscriptions: [{ subscription_id, status, product_title, amount_minor, cadence, next_charge_at, customer_email, … }], total, page, limit, count, has_more, next_page }.
Parameterspagelimitstatusproduct_idresponse_formatcustomer_external_id
jami_get_subscription
Get subscription
Fetch one wallet subscription with its newest 24 charges (one per billing period) — use it to see why a renewal failed or when the next one runs.
Args:
- subscription_id: the sub_… id.
Returns: { subscription: { subscription_id, status, amount_minor, cadence, current_period_end, next_charge_at, cancel_at_period_end, cancellation_reason, last_failure, … }, charges: [{ charge_id, sequence, status, amount_minor, failure, order_id, paid_at }] }. Failure codes: insufficient_funds, kyc_required, payer_unavailable, simulated_failure, internal_error.
Parametersresponse_formatsubscription_idrequired
jami_cancel_subscription
Cancel subscription
Cancel a wallet subscription so the payer is not charged again. Confirm with the user first — a cancel the payer didn't ask for loses a customer, and there are no refunds.
Args:
- subscription_id: the sub_… id.
- at_period_end: default true — no more charges, and it ends at current_period_end (the payer keeps what they paid for). You can undo this kind of cancel before then with jami_resume_subscription. false ends it immediately (a past_due subscription always ends immediately).
Returns the updated subscription: { subscription_id, status, cancel_at_period_end, ended_at, cancellation_reason, … }. Idempotent: cancelling twice is harmless.
Parametersat_period_endresponse_formatsubscription_idrequired
jami_resume_subscription
Resume subscription
Undo a cancel that YOU scheduled (jami_cancel_subscription with at_period_end true), so the subscription keeps renewing at next_charge_at.
Only works on a cancel the organization scheduled and while the subscription hasn't ended yet. A cancel the customer made can't be undone by the organization (the payer has to subscribe again), and an ended subscription can't be resumed — both return 409 not_allowed.
Args:
- subscription_id: the sub_… id.
Returns the updated subscription: { subscription_id, status, cancel_at_period_end: false, next_charge_at, … }. Idempotent: resuming an already-renewing subscription changes nothing.
Parametersresponse_formatsubscription_idrequired
jami_simulate_subscription_renewal
Simulate subscription renewal
Sandbox only: run a subscription's next renewal right now, succeeding or failing, to test subscription.renewed / subscription.payment_failed / subscription.canceled handling without waiting a billing period. No money moves.
Refused with a live (jamidev_live_) token — live renewals only run on schedule.
Args:
- subscription_id: the sub_… id of a sandbox subscription.
- outcome: 'success' (default) or 'failure'. Failures follow the real dunning schedule: retries at 1, 3 and 7 days, then the subscription is canceled with cancellation_reason payment_failed.
Returns: { result: 'renewed' | 'retry_scheduled' | 'canceled' | 'skipped' | 'deferred', subscription }.
Parametersoutcomeresponse_formatsubscription_idrequired
jami_list_orders
List orders
List the organization's orders, newest first, one page at a time.
Args:
- status: 'paid' | 'pending' | 'failed' (optional filter).
- page: 1-based page (default 1).
- limit: 1–100 per page (default 20).
Returns: { orders: [{ order_id, status, amount_minor, amount, product: { id, title }, customer: { id, email, name }, paid_at, created_at, metadata }], total, page, limit, count, has_more, next_page }.
To total revenue or find a specific buyer, page through with next_page until has_more is false. Amounts are integer santim (100 = 1.00 ETB); this is what buyers paid, before any withdrawal fee.
Parameterspagelimitstatusresponse_format
jami_get_balance
Get balance
Get the organization's withdrawable JamiDev earnings — the ceiling on what jami_create_withdrawal can pay out.
Returns: { balance_minor, balance, eligible, currency }.
eligible is false (and balance 0) when the org can't withdraw yet: it's a sandbox org, Developer Mode is off, or KYC isn't approved.
The money itself sits in the owner's unified Jami wallet, so a withdrawal can still fail with 402 if that wallet was spent elsewhere.
Parametersresponse_format
jami_quote_withdrawal
Quote withdrawal
Preview what reaches the destination for a withdrawal — offline, no API call, works without a token.
The JamiDev usage fee (7% unless Jami set a custom rate on the account) and any government tax are cut FROM the amount: you withdraw amount, the account receives net. Rounding is round-half-up per component. The server is authoritative; the real breakdown comes back on the withdrawal.
Args:
- amount_minor: gross santim (min 10000 = 100.00 ETB).
- fee_rate / tax_rate: optional overrides (0–1) if you know the account's real rates.
Returns the breakdown plus whether the amount auto-pays (≤ 10,000 ETB) or goes to manual review, and the per-org 100,000 ETB rolling-24h cap.
Parametersfee_ratetax_rateamount_minorrequiredresponse_format
jami_get_withdrawal
Get withdrawal
Fetch one withdrawal by id to track it from pending → processing → completed (or rejected).
Args:
- withdrawal_id: from jami_create_withdrawal or jami_list_withdrawals.
Parameterswithdrawal_idrequiredresponse_format
jami_list_withdrawals
List withdrawals
List the organization's withdrawals, newest first.
Args:
- status: 'pending' | 'reviewing' | 'approved' | 'rejected' | 'processing' | 'completed' (optional).
- page (default 1), limit 1–100 (default 20).
Returns: { withdrawals: [...], total, page, limit, count, has_more, next_page }.
Parameterspagelimitstatusresponse_format
jami_verify_webhook
Verify webhook
Check whether a webhook delivery is genuine — the same timing-safe HMAC check and replay guard the SDK runs. Use it to debug "signature mismatch" errors in a handler.
Args:
- payload: the RAW request body exactly as received (not re-serialized JSON — whitespace matters).
- signature: the X-JamiDev-Signature header, "t=<unix>,v1=<hex>".
- secret: the subscription's webhook secret (defaults to JAMI_WEBHOOK_SECRET).
- tolerance_seconds: max age of the timestamp (default 300). Raise it to check an old captured delivery.
Returns: { valid, reason, event_type, event_id, livemode }. An invalid signature is a normal result (valid: false), not an error.
Parameterssecretpayloadrequiredsignaturerequiredtolerance_seconds
jami_create_test_webhook
Create test webhook
Build a correctly signed JamiDev webhook delivery for testing a handler locally — no real event needed.
Args:
- type: 'order.created' | 'order.completed' | 'benefit.granted' | 'checkout.session.expired' | 'withdrawal.paid' | 'withdrawal.failed' | 'subscription.created' | 'subscription.renewed' | 'subscription.payment_failed' | 'subscription.updated' | 'subscription.canceled'.
- secret: signing secret (defaults to JAMI_WEBHOOK_SECRET; any string works for a local test).
- data: optional fields to merge over the realistic sample payload.
- livemode: default false (sandbox).
- target_url: optional. When set, the delivery is POSTed there (e.g. "http://localhost:3000/webhooks/jami") and the response status is reported.
Returns: { payload, signature, headers, curl } — the exact body and X-JamiDev-Signature to send, plus delivery_status when target_url is set. Handlers must verify the RAW body and reply 2xx within 10 seconds.
Parametersdatatyperequiredsecretlivemodetarget_url
jami_search_docs
Search Jami docs
Search the official JamiDev developer docs (https://dev.jami.bio/docs) — the source of truth for integrating Jami payments. Works without an API token.
Use this before writing integration code: SDK usage, REST endpoints, webhooks and signature checks, sandbox magic numbers, Sign in with Jami (OAuth/OIDC), the wallet API, the browser storefront, fees and limits.
Args:
- query: what you need, e.g. "verify webhook signature express", "pay what you want amount", "withdrawal fee".
- limit: 1–10 results (default 5).
Returns: { results: [{ title, path, url, snippet }] }. Read a full page with jami_read_doc(path).
Parameterslimitqueryrequired
jami_read_doc
Read Jami doc page
Read one JamiDev docs page as markdown, with every code sample intact.
Args:
- path: a docs path from jami_search_docs, e.g. "/docs/webhooks", "api/checkout", or a full URL. Use "/docs" for the overview.
- offset: character offset for long pages (default 0); the result says where to continue.
Returns: { title, url, markdown, offset, next_offset? }.
Parameterspathrequiredoffset