Escrow Safe Trade is live: protect your next high-value deal Learn more
ProductsPayment links & checkoutM-Pesa, cards, invoices, split payoutsEscrow Safe TradeMilestone and release-based escrowWallets & P2PPersonal and business walletsChama, Lengo & DeniGroup savings, goals and IOUsBusiness toolkitAccounting, payroll, POS, ticketingBulk payments & payrollPay many people in one uploadSubscriptions & billingRecurring plans and renewalsRent, loans & donationsCollections and campaignsAll products
SolutionsMerchantsBuyersChama groupsBusiness solutionsIndustries
Pricing
DevelopersDeveloper overviewQuickstart, auth and sandboxREST APIsCheckout, invoices and requestsWebhooksSigned, real-time eventsWallet-as-a-ServiceVirtual sub-accountsDocumentationGuides and API reference
Security
CompanyAbout usCareersPartnersBlogHelp centreContact us
Log in Get started Talk to sales
Commerce & compliance

eTIMS (KRA fiscalization)

Optional Kenya Revenue Authority fiscalisation. Paysasa stores each fiscal document and submits it asynchronously to KRA.

Base URLhttps://api.paysasa.com/api/v1Authx-api-keyFormatJSON

Optional Kenya KRA eTIMS module. Paysasa stores fiscal documents and submits them asynchronously to KRA (OSCU/VSCU). Your checkout or POS must never wait on KRA — always treat fiscalization as a background job.

Regulatory note: Path A (Paysasa as approved third-party integrator) requires KRA certification. Until then use Path B — self-integrator: the merchant registers with KRA, stores credentials on their Paysasa business, and Paysasa proxies submissions. Platform integrator mode is unavailable until KRA certification is complete.

Prerequisites#

  1. eTIMS must be enabled for your account before you can configure it. Contact support if the module is not available.
  2. Merchant opens /dashboard/etims, enters KRA PIN + Path B credentials (or Path A when allowed), then Activate.
  3. Create a business API key (apk_live_… / apk_test_…) for that same business.
  4. Subscribe webhooks to etims.document.accepted (and optionally etims.document.rejected), or poll GET /etims/invoices/:id.

Xduka / Airsoko POS / external ERP: If the merchant already uses this Paysasa business for payments and has activated eTIMS on it, your app can fiscalize with that business's API key after each sale. Call POST /etims/invoices with an idempotencyKey (e.g. your sale id) and line items including KRA itemClassificationCode. Paysasa POS Lite auto-enqueues from its own sales; external systems must call this API themselves — that is the supported SaaS path today.

Endpoints#

MethodPathPurpose
POST/etims/invoicesCreate fiscal invoice (QUEUED or PENDING_MANUAL)
GET/etims/invoices/:idFetch status + KRA CU / QR when accepted
POST/etims/invoices/:id/confirm-manual-filingMark PENDING_MANUAL as filed (CU #)
GET/etims/invoicesList recent documents (?status=&limit=)
POST/etims/credit-notesCreate credit note (needs originalDocumentId)
GET/etims/reports/manual-exportCSV export for KRA portal re-entry
GET/etims/itemsList item ↔ KRA classification mappings
POST/etims/itemsUpsert item mapping
GET/etims/tax-codesTax types (A–E)
GET/POST/etimsAccount setup / activate (dashboard + API)

Manual mode#

Default filing_method is manual_portal. Invoices are stored as PENDING_MANUAL with no KRA submission queue. Use GET /etims/reports/manual-export for a CSV, file on the KRA portal, then POST /etims/invoices/:id/confirm-manual-filing with the CU invoice number. Switch to automated_api when credentials are ready (optional per-invoice override via filingMethod). Manual/buyer docs require an 8–10 digit itemClassificationCode; buyer PIN is required when total ≥ KES 10,000 (except buyer_initiated).

Create invoice (external POS / ERP)#

cURL
curl -X POST "https://api.paysasa.com/api/v1/etims/invoices" \
  -H "x-api-key: apk_live_<prefix>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotencyKey": "xduka-sale-9981247",
    "externalSysId": "XD-INV-2026-0821-009",
    "buyer": {
      "pin": "A001234567Z",
      "name": "Acme Kenya Ltd",
      "phone": "+254711000000"
    },
    "currency": "KES",
    "lines": [
      {
        "itemSeq": 1,
        "itemClassificationCode": "50121500",
        "itemCode": "SKU-100",
        "itemName": "Enterprise Software Subscription",
        "quantity": 1,
        "unitPrice": 100000.00,
        "discountAmount": 0,
        "taxType": "B_STANDARD"
      }
    ]
  }'

# → HTTP 202
# { "id": "…", "status": "QUEUED", "message": "Invoice queued for KRA transmission.", … }

Poll status#

cURL
curl "https://api.paysasa.com/api/v1/etims/invoices/<document_id>?business_id=biz_xxxxxxxxxxxxxxxx" \
  -H "x-api-key: apk_live_<prefix>.<secret>"

# When ACCEPTED:
# {
#   "id": "…",
#   "status": "ACCEPTED",
#   "kraDetails": {
#     "cuInvoiceNumber": "KRA…",
#     "cuSerialNumber": "…",
#     "qrCodeUrl": "https://etims.kra.go.ke/…",
#     "receiptSignature": "…"
#   },
#   "financials": { "taxableAmount": 100000, "taxAmount": 16000, "totalAmount": 116000 }
# }

Credit note#

cURL
curl -X POST "https://api.paysasa.com/api/v1/etims/credit-notes" \
  -H "x-api-key: apk_live_<prefix>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotencyKey": "xduka-cn-9981247",
    "originalDocumentId": "<etims_document_id>",
    "buyer": { "name": "Acme Kenya Ltd", "pin": "A001234567Z" },
    "lines": [
      {
        "itemClassificationCode": "50121500",
        "itemName": "Refund — Software Subscription",
        "quantity": 1,
        "unitPrice": 100000.00,
        "taxType": "B_STANDARD"
      }
    ]
  }'

Item mappings & tax codes#

cURL
# List / upsert KRA item classification mappings
curl "https://api.paysasa.com/api/v1/etims/items?business_id=biz_xxxxxxxxxxxxxxxx" \
  -H "x-api-key: apk_live_<prefix>.<secret>"

curl -X POST "https://api.paysasa.com/api/v1/etims/items" \
  -H "x-api-key: apk_live_<prefix>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "business_id": "biz_xxxxxxxxxxxxxxxx",
    "externalItemCode": "SKU-100",
    "itemClassificationCode": "50121500",
    "itemName": "Enterprise Software",
    "unitOfMeasure": "PCE",
    "taxType": "B_STANDARD"
  }'

curl "https://api.paysasa.com/api/v1/etims/tax-codes?business_id=biz_xxxxxxxxxxxxxxxx" \
  -H "x-api-key: apk_live_<prefix>.<secret>"
JavaScript
// 1) Complete sale in your POS (source of truth)
// 2) Fire-and-forget fiscalize — do not block the receipt UI
async function fiscalizeSale(sale) {
  const res = await fetch("https://api.paysasa.com/api/v1/etims/invoices", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": process.env.PAYSASA_API_KEY, // apk_* for the Paysasa business
    },
    body: JSON.stringify({
      idempotencyKey: `xduka:${sale.id}`, // prevents double fiscalization
      externalSysId: sale.number,
      buyer: {
        name: sale.customer?.name || "Walk-in Customer",
        pin: sale.customer?.kraPin || null,
        phone: sale.customer?.phone || null,
      },
      lines: sale.items.map((item, i) => ({
        itemSeq: i + 1,
        itemClassificationCode: item.kraClassCode || "99000000",
        itemCode: item.sku,
        itemName: item.name,
        quantity: item.qty,
        unitPrice: item.unitPrice, // major units (KES)
        taxType: item.taxType || "B_STANDARD",
      })),
    }),
  });
  // 202 → queued. Poll GET /etims/invoices/:id or wait for webhook.
  return res.json();
}

// 3) On webhook etims.document.accepted → print QR / CU invoice number on reprint

Tax types: A_EXEMPT, B_STANDARD (16%), C_ZERO_RATED, D_NON_VAT, E_EIGHT_PERCENT. Failed submissions are retried automatically. In sandbox, mock CU numbers may be returned when KRA credentials are not configured.