Skip to content

Coming from the Xash vend API ​

This page is for you if you call app.xash.co.zw/api/v1 today with a server token (svt_…). That is the API documented at docs.xash.co.zw. It shows what is different in the Partners API and which new call replaces each old one.

The Partners API is a separate product. It has its own sign-up, keys, balance and base URL: https://partners.xash.co.zw/api/v1. Moving over does not change your server token or your app.xash.co.zw wallet.

What changes ​

A separate partner account and balance ​

You sign up at partners.xash.co.zw as a business. You get a partner account with its own prepaid balance.

  • The balance is in USD only. There is no ZWG wallet. A ZWG product is priced in USD, and the quote tells you the USD amount.
  • It is not your app.xash.co.zw wallet. Money in your old wallet stays there.
  • You fund it with a top-up (mobile money, USDT or bank transfer), or with money you collect from customers.

Keys instead of server tokens ​

You create API keys in the portal, not with POST /auth/server-tokens. There is no login, PIN or password call in the API.

  • xash_test_… keys use the sandbox. They never move real money, and you get one as soon as you sign up.
  • xash_live_… keys sell for real. They work after you finish go-live in the portal.
  • Each key has abilities, such as transactions:write or balance:read. Give each system only what it needs.

The old API had no sandbox, so every test sold real airtime. Build and test against the sandbox first.

Quote, then sell ​

The old API sold in one call. Every sale is now two calls:

  1. POST /quotes checks the recipient and returns the price, the fee and the total_debit. For ZESA and bills it also returns the customer name. A quote lasts 5 minutes.
  2. POST /transactions spends the quote.

This replaces the separate "check account" calls. electricity/check-account and dstv/lookup are now just the quote.

A reference and an Idempotency-Key on every sale ​

Every POST /transactions needs:

  • a reference in the body: your own unique id for the sale, such as your order number;
  • an Idempotency-Key header: a UUID you make for that sale.

If your request times out, send the same request again with the same body and the same key. You get the original sale back with 200. You never pay twice.

The old API had no such protection. Sending airtime/direct again after a timeout could sell twice. With the new API, retrying is the right thing to do.

202 means pending, not failed ​

  • 201 with status completed means it was delivered.
  • 202 with status pending means the outcome is not final yet. The money is reserved. Wait for the webhook, or GET /transactions/{id}. Never buy again.
  • Only status failed means nothing was delivered. The money is then back in your balance.

The API never answers 502 or 504.

One error shape ​

The old API mostly answered 400 with only a message. Now every error looks like this:

json
{
  "success": false,
  "message": "Your balance is too low for this.",
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "hint": "Top up your balance, then make a new quote.",
    "docs_url": "https://docs.partners.xash.co.zw/errors#insufficient_balance"
  }
}

Branch on error.code, never on message. The codes never change. The HTTP status also means something now: 402 is a low balance, 409 is a conflict and 422 is bad input. See Errors for every code.

Webhooks instead of polling ​

The old API made you poll. Now you set a webhook URL in the portal, and we send you an event when a sale, collection or top-up finishes, such as transaction.completed or collection.completed.

  • Each event is signed. Check the X-Xash-Signature header: it is the hex HMAC-SHA256 of {X-Xash-Timestamp}.{raw body}, made with your webhook secret.
  • Reject events with a timestamp older than 5 minutes.
  • Use X-Xash-Event-Id to ignore an event you have already handled.
  • Events are short. GET the record for the full details.

POST /webhooks/test sends you a signed test event.

Collections are spend-only ​

You can still take EcoCash, OMari and InnBucks payments from your customers with POST /collections. The money goes into your partner balance.

That money can only buy Xash products. It is never paid out to you, and there is no refund call. Collections are free.

To top up your own balance, use POST /fundings instead.

Old endpoint to new call ​

Old paths are under https://app.xash.co.zw/api/v1. New paths are under https://partners.xash.co.zw/api/v1.

Product codes are dotted and lowercase, such as econet.airtime or zetdc.prepaid. Get the full list, with your price and the fields each product needs, from GET /products.

Account and keys ​

OldNew
POST /auth/register, set-password, login, PIN calls, change-password, create-businessSign up and manage users in the portal. No API call.
GET/POST/DELETE /auth/server-tokensCreate and revoke API keys in the portal. No API call.
GET /auth/profile, GET /profileGET /me
GET /wallet, GET /wallet/{currency}GET /balances (USD only)
GET /servicesGET /products
GET /pos/branches/{identifier}GET /branches
POST /pos/branchesCreate branches in the portal. Tag a sale with branch_id.

Selling ​

OldNew
GET /airtime/carriersGET /products?category=airtime, or POST /lookups/mobile to find the operator for a number
POST /airtime/directPOST /quotes with e.g. econet.airtime, then POST /transactions
GET /airtime/direct/{carrier}/values, GET /airtime/voucher/{carrier}/valuesGET /products?category=voucher
POST /airtime/direct/voucher/{carrier}, POST /airtime/voucher/{carrier}/buyPOST /quotes, then POST /transactions. The PIN is in fulfilment. One voucher per sale: there is no quantity.
GET /bundlesGET /products?category=data
POST /bundles/buy/{bundle}POST /quotes with the bundle's code, e.g. netone.bundle.whatsapp_1gb, then POST /transactions
POST /bundles/voucher/buy/{bundle}POST /quotes, then POST /transactions. One voucher per sale.
POST /electricity/check-accountPOST /quotes with zetdc.prepaid. The quote returns the customer name.
POST /electricity/buy-tokensPOST /transactions with that quote. The token is in fulfilment.
GET /dstv/packagesGET /products?category=bill
POST /dstv/lookupPOST /quotes
POST /dstv/purchasePOST /transactions
GET /wifi/vouchersGET /products?category=voucher
POST /wifi/vouchers/buyPOST /quotes, then POST /transactions. One voucher per sale.
GET /wifi/vouchers/purchases/{reference}GET /transactions?reference=… or GET /transactions/{id}

Only products in your GET /products list are on sale to you.

Money in ​

OldNew
POST /ecocash/pay, /innbucks/pay, /omari/pay, /payments/receive to take a customer's paymentPOST /collections with method ecocash, innbucks or omari. Send currency: "ZWG" for a ZWG payment.
The same calls, to load your own walletPOST /fundings
POST /crypto/payPOST /fundings (USDT)
POST /omari/confirmPOST /collections/{id}/confirm
GET /{method}/poll/{payment}The collection.completed or funding.credited webhook, or GET /collections/{id} and GET /fundings/{id}
POST /ecocash/direct/charge, GET /ecocash/direct/transactionPOST /collections, GET /collections/{id}
POST /card/payNo equivalent. There are no card payments yet.
POST /ecocash/direct/refundNo equivalent. Collected money is spend-only.
GET /ecocash/lookup, POST /{method}/poll/{payment}/report-debitNo equivalent yet.

Reports ​

OldNew
GET /reports/mini/{currency}/{range}, GET /reports/history/{currency}GET /statement for balance changes. GET /transactions and GET /collections to list or find records.
GET /reports/transaction/{transaction}GET /transactions/{id}
GET /reports/commission-overview/{currency}, GET /reports/commission/{currency}/{range}No equivalent. There is no commission: your discount is already in the price you pay.

Everything else ​

OldNew
POST /transfer, POST /transfer/confirm/{transfer}No equivalent. You cannot move balance to another account.
POST /notifications/smsNo equivalent.
/users/…, /admin/…, /providers-balanceNo equivalent. These were for Xash's own systems.

Example: an airtime sale ​

Both examples use a sandbox number in the 263000000… range. The amounts, ids and messages are examples. Do not run the old example: the old API has no sandbox.

Before ​

One call. A timeout leaves you guessing, and retrying could sell twice.

bash
curl https://app.xash.co.zw/api/v1/airtime/direct \
  -H "Authorization: Bearer $XASH_SERVER_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"mobile_phone": "263000000001", "amount": 1, "currency": "USD"}'
json
{
  "success": true,
  "message": "Recharge to 263000000001 was successful",
  "data": {
    "name": "Econet",
    "amount": 1,
    "currency": "USD",
    "mobile_phone": "263000000001",
    "status": "success",
    "balance": { "balance": 99, "currency": "USD", "profit_on_hold": 0 },
    "commission": "USD0.05"
  }
}

After ​

1. Quote. Amounts are strings with two decimal places.

bash
curl https://partners.xash.co.zw/api/v1/quotes \
  -H "Authorization: Bearer $XASH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"product": "econet.airtime", "amount": "1.00", "recipient": {"phone": "263000000001"}}'
json
{
  "success": true,
  "message": "Quote created.",
  "data": {
    "id": "qt_01JB4Y3KQ2N8F7",
    "product": "econet.airtime",
    "customer": null,
    "face_value": "1.00",
    "face_currency": "USD",
    "fee": "0.00",
    "total_debit": "0.95",
    "rate": null,
    "expires_at": "2026-10-17T14:10:00+02:00"
  }
}

2. Sell. Use your own order number as the reference, and a new UUID as the Idempotency-Key. Save both before you send.

bash
curl https://partners.xash.co.zw/api/v1/transactions \
  -H "Authorization: Bearer $XASH_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f1c9a52-6d0e-4c7b-9a51-2e8d4b7f0c13" \
  -d '{"quote_id": "qt_01JB4Y3KQ2N8F7", "reference": "order-10045"}'
json
{
  "success": true,
  "message": "Sale completed.",
  "data": {
    "id": "tx_01JB4Y5R9M2C6D",
    "reference": "order-10045",
    "status": "completed",
    "product": "econet.airtime",
    "total_debit": "0.95",
    "balance_after": "99.05",
    "created_at": "2026-10-17T14:05:09+02:00",
    "completed_at": "2026-10-17T14:05:11+02:00"
  }
}

The full record has more fields, such as fulfilment (the token or PIN) and receipt.

What to do with each answer:

  • 201, status completed: done. Show your customer the receipt.
  • 202, status pending: wait for the transaction.completed or transaction.failed webhook. Do not sell again.
  • A timeout or a network error: send step 2 again with the same body and the same Idempotency-Key.
  • An error: read error.code and follow error.hint. For example, QUOTE_EXPIRED means make a new quote.

Next steps ​

  1. Sign up at partners.xash.co.zw and copy your xash_test_ key.
  2. Read the endpoints and errors reference. AI agents can read llms.txt.
  3. Move one product at a time, and test each in the sandbox.
  4. Go live in the portal, top up, and switch to your xash_live_ key.

Xash Partners API. Sandbox keys never move real money.