DEVELOPERS · API V1

Sharet API docs

Collect from customers, pay anyone, split a payment and settle in batches, in Kenyan shillings, through one API. Every account has a fixed Sharet ID and every settlement is tied to it.

Base URL

https://sharet.africa/api/v1 · JSON in, JSON out · amounts are strings like "1500.00" · currency KES · fiat only.

Quickstart

  1. Create a sandbox key (sk_test_…) in Developers › API keys and give it the scopes you need.
  2. Check it works:
# who am I? curl https://sharet.africa/api/v1/accounts/me \ -H "Authorization: Bearer sk_test_…"
  1. Pay someone in the sandbox (no real money moves):
curl https://sharet.africa/api/v1/payments \ -H "Authorization: Bearer sk_test_…" \ -H "Idempotency-Key: order-1042" \ -H "Content-Type: application/json" \ -d '{"amount": "1500.00", "to": {"type": "mpesa", "phone": "0722 000 904"}}'

When you're ready, verify your business and create a live key (sk_live_…) with your servers' IP addresses.

Sharet IDs and @names

Every person and business has a Sharet ID, like SH-4K7Q2M. It never changes and is never reused, and every settlement, webhook and statement is tied to it. An @name (like @mwangihardware) is the alias people see and type; you can send it, and we always resolve it to the Sharet ID and return that. Renaming an @name never redirects money.

GET /accounts/resolve?handle=@akinyi { "sharet_id": "SH-9TRX3A", "handle": "@akinyi", "name": "Akinyi W.", "verified": true, "can_receive": true }

Names come back shortened (Akinyi W.) so lookups can't be used to collect people's full names. GET /accounts/me returns your own Sharet ID, @name, verification level, balance and the key's scopes.

Keys, scopes and IPs

Send your key as Authorization: Bearer sk_…. A business's keys act for the business's own Sharet wallet; we keep only a fingerprint, so copy a key when it's shown.

ScopeLets the key
readLook things up (every key has it)
collectCreate and cancel payment requests
payoutSend payments and batches, retry splits
connectAsk people to connect their accounts
  • Sandbox and live are separate. sk_test_ keys never move real money; sk_live_ keys need a verified business.
  • Live payouts need an IP allow-list: list your servers' addresses or ranges on the key. Calls from anywhere else get ip_not_allowed.
  • Large batches need a second person: live batches over KES 500,000 wait in Dashboard › API approvals for someone else from your business.

Requests and retries

  • Idempotency: every call that moves money (and creating a payment request) takes an Idempotency-Key header, 8–80 characters, one per payment. Retrying with the same key returns the first answer (with Idempotent-Replay: true) and never pays twice. Refusals aren't remembered, so you can fix the problem and retry with the same key.
  • Amounts are strings with two decimals and no separators, in whole shillings: "35000.00".
  • Errors are application/problem+json with a stable type; see Errors.
  • Sandbox and live data are separate: a key only sees objects from its own environment.
  • Pauses: if Sharet pauses payments for safety, money calls answer 423; nothing moves and you can retry later.

Quotes

See the exact fee and total before paying. Nothing moves.

POST /quotes { "amount": "5000.00", "to": { "type": "bank", "bank": "equity", "account": "0123456789" } } { "amount": "5000.00", "fee": "50.00", "excise": "7.50", "rounding": "0.50", "total": "5058.00", "tariff": { "version": 1, "volume_discount_percent": 0, "month_to_date": "0.00" } }

Destinations (to): {"sharet_id": "SH-…"} or {"handle": "@name"} (free and instant), {"type": "mpesa"|"airtel"|"tkash", "phone": "0722 000 904"}, {"type": "till", "till": "512334"}, {"type": "paybill", "paybill": "247247", "account": "…"}, {"type": "bank", "bank": "equity", "account": "…"} (PesaLink; banks: equity, kcb, ncba, imbank, coop, absa, stanbic, family).

Payments out

POST /payments (scope payout, Idempotency-Key) pays from your account's wallet. The response includes the fee, total and status: paid straight away for Sharet accounts, paying while M-PESA or the bank works, then paid, failed or refunded (a failed payout's money comes back to your wallet). Follow it with GET /payments/{id} or a webhook.

POST /payments { "amount": "31500.00", "to": { "sharet_id": "SH-4K7Q2M" }, "reference": "Rent Nov · 4B" } # 201 { "id": "pay_…", "status": "paid", "amount": "31500.00", "fee": "0.00", "total": "31500.00", "to": { "type": "sharet", "sharet_id": "SH-4K7Q2M" } }

Payment requests

POST /payment-requests (scope collect, Idempotency-Key) asks someone to pay you and returns a pay link (pay_url). They pay from their Sharet wallet (signed in, with their PIN, free) or by an M-PESA prompt to any phone. The money lands in your wallet. No call can take money from someone's wallet without them approving it.

POST /payment-requests { "amount": "35000.00", "reference": "Rent Nov 2026 · Unit 4B", "payer": { "handle": "@akinyi" }, // optional: or {"sharet_id"} or {"phone"} "channels": ["sharet", "mpesa"], "expires_in_minutes": 10080, "metadata": { "lease": "L-2041" } } # 201 { "id": "pr_…", "status": "open", "pay_url": "https://sharet.africa/r/…", "expires_at": "…" }

If you name a payer, only that Sharet account can pay from a wallet. GET /payment-requests/{id} shows the status (open, paid, expired, cancelled); POST /payment-requests/{id}/cancel closes it.

Split settlement

Add splits to a payment request and, once it's paid, every leg is paid from your wallet to the recipients' Sharet IDs together or not at all. Legs are percentages or fixed amounts in whole shillings; what isn't in a leg (your "self" share and rounding) stays with you.

"splits": [ { "to": { "sharet_id": "SH-4K7Q2M" }, "percent": 90 }, // landlord { "to": { "handle": "@kevin" }, "percent": 8 }, // agent { "to": "self", "percent": 2 } // you ]

Legs go to Sharet accounts only (up to 10, each account once). If a leg can't be paid, none are, the money stays in your wallet and settlement.failed says why; fix it and retry with POST /payment-requests/{id}/settle.

Batch settlements

POST /settlements (scope payout, Idempotency-Key) pays up to 500 items in one call. Every item is checked first: if any is wrong, nothing is paid and errors lists them by index. Each item is then paid like POST /payments; a refused item is recorded as failed without stopping the others.

POST /settlements { "reference": "Landlords · Nov", "items": [ { "amount": "31500.00", "to": { "sharet_id": "SH-4K7Q2M" }, "reference": "4B" }, { "amount": "22000.00", "to": { "type": "bank", "bank": "kcb", "account": "1102345678" } } ] } # 201 completed / partially_failed / failed, or 202 awaiting_approval

Live batches over KES 500,000 return 202 awaiting_approval and run once a different person from your business approves them in Dashboard › API approvals.

Connected accounts

Platforms (like a property manager) can collect and settle for their users, in their users' own Sharet accounts, with their consent.

  1. POST /connect/links (scope connect) with {"scopes": ["collect", "payout"], "reference": "landlord-17", "return_url": "https://…"} returns a link, valid 7 days.
  2. The person opens it, signs in, reads exactly what you may do, and approves with their PIN (or declines). They come back to your return_url with ?connection=…&status=active.
  3. Act for them by adding Sharet-Account: SH-… to your calls, only within what they approved; their own limits apply. Sharet ↔ Sharet is free; sending outside Sharet is charged.
  4. List them with GET /connect/accounts; either side can disconnect at any time (POST /connect/accounts/{id}/disconnect, or Account › Connected apps).

Webhooks

Register an endpoint with POST /webhooks {"url": "https://…", "events": [...]}; the response includes a whsec_… secret, shown once. Events: payment.paid, payment.failed, payment.refunded, payment_request.paid, payment_request.expired, settlement.completed, settlement.failed, settlement.awaiting_approval, account.connected, account.disconnected.

Every delivery is signed. Check the signature before trusting it, ignore repeats by id, answer 2xx quickly, and confirm with a GET when it matters. We retry for 24 hours.

# Header Sharet-Signature: t=1794060000,v1=5f2b… # HMAC-SHA256 of "t.body" with your secret # PHP [$t, $v1] = [/* parse t= and v1= */]; $ok = hash_equals(hash_hmac('sha256', $t.'.'.$rawBody, $secret), $v1) && abs(time() - $t) < 300; # Node const ok = crypto.timingSafeEqual(Buffer.from(crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex')), Buffer.from(v1));

Errors

Errors are application/problem+json: {"type": "https://sharet.africa/docs/errors#…", "title": "…", "status": 422, "detail": "…"}, sometimes with errors by field. Use type in your code; show detail to people.

typeStatusMeaning
invalid_api_key401The key is missing, wrong or switched off.
missing_scope403The key doesn't have the scope this call needs. Add it under Access in Developers › API keys.
ip_allow_list_required403A live key needs its server addresses listed before it can pay out.
ip_not_allowed403This live key pays out only from its listed addresses. The response says which IP we saw.
business_not_verified403Live money needs a verified business. Use a sk_test_ key meanwhile.
not_connected403The Sharet-Account you sent hasn't connected to your business, or has disconnected.
not_allowed_by_account403The connected person didn't allow this (e.g. you asked to pay out, they allowed only collect).
not_an_approver403Batches must be approved by a different person from the same business.
account_not_found404No Sharet account has that @name or Sharet ID.
payment_not_found404No payment with that id for this key's account and environment.
payment_request_not_found404No payment request with that id for this key's account and environment.
settlement_not_found404No settlement with that id for this key's account and environment.
webhook_not_found404No webhook endpoint with that id.
connection_not_found404No connection with that id.
idempotency_key_reused409That Idempotency-Key was used for a different request.
in_progress409The first request with this Idempotency-Key is still running. Try again shortly.
idempotency_key_required428Money calls need an Idempotency-Key header (8–80 characters).
invalid_amount422Amounts are strings in whole shillings, at least KES 10, e.g. "1500.00".
destination_required422Send "to" with a sharet_id, a handle, or a type.
invalid_destination422The phone, till, paybill or bank details aren't valid. "errors" says which field.
own_account422You can't pay or request from your own account.
payment_refused422The payment engine refused it: not enough money, over a limit, or payouts paused. "detail" explains.
invalid_items422Some batch items need fixing; nothing was paid. "errors" lists them by index.
invalid_splits422The split legs don't add up, repeat an account, or are out of range.
outside_split_not_available422Split legs go to Sharet accounts only; recipients can send outside themselves.
invalid_payer422The payer's phone isn't a valid Kenyan mobile number.
invalid_channels422channels must be "sharet", "mpesa" or both.
invalid_scopes422Connect links ask for "collect", "payout" or both.
invalid_return_url422return_url must be a public https:// address.
invalid_webhook_url422Webhook URLs must be public https:// addresses.

Sandbox

  • Use sk_test_ keys: same endpoints, simulated money, nothing touches real balances.
  • The M-PESA number 0700 000 002 always fails; every other destination succeeds.
  • Sandbox pay links show Simulate paid and Simulate cancel buttons.
  • Webhooks fire in the sandbox too, to endpoints registered with a test key.

Pricing

Sharet ↔ Sharet is always free, including splits and connected accounts. Outside payouts are priced from two tables: a fee by movement type and amount, then a discount by your outside payouts this month. Every quote and payment shows the fee, the tariff version and your discount before money moves. Sharet's own companies are priced one tier up.

Limits

  • 60 requests a minute per key to start; batches up to 500 items; splits up to 10 legs.
  • Payment limits follow the paying account's verification: verified businesses have business limits; connected people keep their own.
  • Kenyan shillings only for now; Tanzania is next after the Kenya pilot.

Questions? Talk to us, or request sandbox access.