Fees & limits
Every number in JamiDev on one page — money units, fees, payout limits, timeouts, rate limits, sandbox numbers and error codes.
The numbers other pages mention, in one place. If something here disagrees with another
page, this page and the API are right; tell us at support@jami.bio.
| Rule | Value |
|---|
| Currency | ETB only |
| Unit in the API | Integer santim (ETB minor units). 10000 = 100.00 ETB. |
| Charged to the buyer | Exactly the product's price, or the custom amount you sent. Nothing is added at checkout. |
| Custom amount (no product) | Integer santim, at least 100 (1.00 ETB) |
pwyw product amount | Integer santim, at or above the product's minimum |
| Fee | Value |
|---|
| Monthly or setup fee | None |
| At checkout | None. The buyer pays exactly the price or amount. |
| JamiDev fee on withdrawal | 7% of the amount withdrawn, unless Jami set a custom rate on your account |
| Government tax on withdrawal | The rate Jami's platform settings apply (shown as taxAmount) |
| How fees are taken | Cut from the amount withdrawn: netAmount = amount − feeAmount − taxAmount |
| Rounding | Each part rounded to the nearest santim |
The server is authoritative. Read feeAmount, taxAmount and netAmount on the
withdrawal; Jami.computeWithdrawalQuote is an offline estimate using the defaults.
| Limit | Value |
|---|
| Minimum withdrawal | 100.00 ETB (10000) |
| Paid out automatically | Up to 10,000.00 ETB (1000000). Above that, manual review. |
| Per organization, rolling 24 hours | 100,000.00 ETB (10000000). Past it: 429. |
| Per withdrawal maximum | The organization's withdrawable earnings (GET /balance) |
| Requirements | Production organization, Developer Mode on, approved KYC |
| Methods | Telebirr, M-Pesa, CBE Birr |
For wallet:send on behalf of a signed-in user:
| Limit | Value |
|---|
| Per transfer | 1.00 to 10,000.00 ETB |
| Per user, per app, rolling 24 hours | 50,000.00 ETB. Past it: 429 daily_cap_exceeded. |
| Idempotency key | Required, at least 8 characters |
| Sender | Must have approved KYC |
| Rule | Value |
|---|
| Session lifetime | 30 minutes |
productId | The product's p_… token or its id. Must be published and in your organization. Omit it for a custom amount. |
description (custom amount) | Required, 1 to 120 characters |
metadata | Up to 20 keys of 1 to 40 characters; string, number or boolean values, stored as strings of up to 500 characters. API token only. |
| Payment methods | telebirr, mpesa, cbe |
| Orders per session | At most one |
Payment link (no gateway) | Lasts the session's 30 minutes. The buyer can retry failed attempts on it; a new attempt is refused for 150 seconds while a prompt is out. API token only. |
SDK waitForCheckout | Polls every 2 s, gives up after 5 minutes (poll_timeout) |
| Rule | Value |
|---|
title | 1 to 120 characters |
description | Up to 5,000 characters |
| Pricing model | fixed, pwyw or free. Locked after creation (422 on change). |
| Product types | One-time only |
| Created from | The Products API, the SDK, the MCP server or the dashboard |
| Endpoint | Limit |
|---|
POST /checkout with a token | A bucket per token: bursts of up to 200, refilling at 20 per second |
POST /checkout without a token (hosted pages) | 40 per 5 minutes per IP |
Payment link attempts (POST /pay/{payToken}) | 30 per 5 minutes per IP |
| OAuth endpoints | See Sign in with Jami |
A 429 from POST /withdrawals means the daily payout cap, not a request rate.
| Rule | Value |
|---|
| Signature header | X-JamiDev-Signature: t=<unix seconds>,v1=<hex> |
| Algorithm | HMAC-SHA256 over "{t}.{raw body}" with the subscription secret |
| SDK replay window | 300 seconds (toleranceSec) |
| Response timeout | 10 seconds |
| Retries | 1 min, 5 min, 30 min, 2 h, 12 h (5 attempts in total) |
| Phone | Result |
|---|
251900000001 | Instant success |
251900000002 | Instant failure (payment_failed) |
| Anything else | Simulator page with Approve and Fail buttons |
Tokens: jamidev_test_… in sandbox, jamidev_live_… in production. A token stops working
when its organization switches environment.
Set on expired sessions as failure.code.
| Code | Meaning |
|---|
payment_rejected | The provider refused it within seconds, before a prompt reached the phone |
payment_failed | Failed after the prompt (wrong PIN, canceled on the phone, low balance), or no detail given |
payment_canceled | The buyer canceled |
payment_timed_out | The buyer didn't answer the prompt in time |
initiate_failed | The payment couldn't be started; message has the provider's reason |
session_expired | 30 minutes passed without a payment |
New codes may be added. Treat unknown ones like payment_failed.
| HTTP | Body | SDK error | Typical cause |
|---|
400 | { "error": "…" } | JamiValidationError | A field failed validation; the message names it |
401 | { "message": "…" } | JamiAuthError | Missing, malformed, revoked or wrong-environment token (.hint set for the last) |
402 | { "error": "…" } | JamiError | Withdrawal: the owner's wallet can't cover it right now |
404 | { "error": "…" } | JamiError | Not found, or not yours (the API doesn't tell them apart) |
429 | { "error": "…" } | JamiRateLimitError | Checkout rate limit, or the daily payout cap |
502 | { "error": "…" } | JamiError | The payment provider rejected the checkout |
| — | — | JamiSignatureError | A webhook failed verification |
| — | — | JamiWalletError | A wallet call failed; code holds the server's reason |
Every SDK error has status, code and, when the response had one, requestId. Include
requestId when you contact support.