EBM HubDeveloper documentationMenu

Issue RRA receipts from your own system

EBM Hub signs every sale through the RRA VSDC and returns the receipt data your POS or ERP prints. Work in the browser portal, or connect over the API with a company key.

POST /trnsSales/saveSales

Invoice1001

Branch00

Total RWF12,250.00


SDC INFORMATION

SDC IDSDC010000001

Receipt1/1 NS

Internal dataTEST-INTE-RNAL

SignatureTEST-SIGN-ATUR

Sandbox example. A sale is printable only after its SDC signature comes back.

For customers who prefer a browser, see the customer portal guide. API access is optional.

For a runnable backend integration example, use the Python client with the managed CIS guide. It uses Python's standard library and is included in the downloadable client package.

The Python client's EbmError retains status and response, plus the optional top-level code, raw retry_after header and parsed retry_after_seconds. Integer delay values from 0 through 2,147,483,647 are parsed; absent, malformed, larger or HTTP-date values leave retry_after_seconds as None and retain the raw header. These fields never trigger a retry. For a managed CIS HTTP 409 with code == "BRANCH_BUSY", your application can wait at least the supplied delay and retry the unchanged persisted request/key within its own attempt/time limit. A delay header alone does not make another conflict or uncertain outcome safe to retry. Transport failures still propagate for reconciliation.

This guide is for companies connecting a POS, ERP, billing system or other backend to our EBM service. Your onboarding contact supplies the service base URL, company API keys, enabled permissions and registered branch details. Examples use synthetic identifiers; replace them with your assigned values. No production URL or credentials are embedded in this document.

The machine-readable OpenAPI specification lists the 24 Java-compatible routes, request schemas and receipt-delivery endpoints and can be imported into API tooling. Set your assigned base URL in that tool before making requests.

1. Onboarding

Supply your company TIN, branch codes, technical contact and intended integration operations. We provision the company and its authorized branches and arrange device initialization for each environment. Confirm that a branch is initialized before submitting transactions.

You receive a sandbox key for integration testing and a separate production key for live use. The same company can use both at once. Each key identifies one company and one environment; it can serve multiple authorized company branches. Every fiscal request must use that company's TIN and the intended branch's bhfId.

Setting Development Production
API environment name sandbox production
Newly issued key prefix ek_test_ ek_live_
Fiscal state Separate sandbox state Separate live state
Branch initialization Sandbox device credentials Production device credentials

The key determines the environment. Sending an environment header does not change an API key's environment. Existing migrated keys may retain an older prefix: use the environment supplied at onboarding as authoritative. Sandbox receipts and invoice history are not promoted into live state.

Keep keys in your backend or a protected device credential store. Do not embed them in a public website, mobile source code, logs or shared support attachments. A signed-in company owner can set optional expiry when creating a key and revoke it in API access. Expired keys receive HTTP 401. For rotation, create a replacement with the intended permissions, update the integration and verify it works, then revoke the old key. Contact your onboarding operator if you cannot access the account. API keys are not RRA signing/device keys; we manage the latter as part of branch setup.

2. Authentication and connectivity

Every compatibility request requires X-API-Key. POST requests use UTF-8 JSON and Content-Type: application/json. The paths below are relative to the base URL; an installation may include an existing deployment context in that base URL.

export EBM_BASE_URL='https://YOUR-ASSIGNED-HOST/YOUR-OPTIONAL-CONTEXT'
# Set EBM_API_KEY from your secret store; do not commit it to a script.
curl --request POST "$EBM_BASE_URL/test/echoTest" \
  --header "X-API-Key: $EBM_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{"tin":"999555888","bhfId":"00","testStr":"integration-check"}'

Permission examples: receipts:write for sales, items:write for item writes, stock:write for stock operations and codes:read for code lookups. A signed-in company owner can select permissions when creating a key in API access; the key list shows the permissions granted. Unchecking all permissions creates a read-only key for permitted GET operations. Registration and stock/receiving workflows also require receipts:write, as detailed in the managed CIS guide. A key never grants access to another company's TIN.

3. Choose your invoicing integration

For a company using our managed CIS catalogue, stock, refund and print controls, use POST/api/portal/fiscal/invoices. See managed CIS API for the request schema and examples. Company API keys work on that endpoint with receipts:write.

The raw /trnsSales/saveSales route below is for an agreed external-CIS integration. Once a CIS profile exists for the company in the selected environment, raw sales and selected stock/purchase/import routes return HTTP 409 to prevent bypassing CIS controls. Confirm the integration mode during onboarding.

External CIS: submit a VSDC sale

Download sale-request.json as a complete local example. Replace its TIN, branch, invoice number, dates, product identifiers, amounts and operator fields with your actual transaction. The sample product code is a placeholder and does not establish RRA product registration.

curl --request POST "$EBM_BASE_URL/trnsSales/saveSales" \
  --header "X-API-Key: $EBM_API_KEY" \
  --header 'Content-Type: application/json' \
  --header 'X-Idempotency-Key: pos-order-20260912-0042' \
  --data-binary @sale-request.json

Important fields:

Field Meaning
tin Your company TIN, as a string
bhfId Registered two-digit branch code, for example 00
invcNo Your invoice number; unique within company, environment and branch
orgInvcNo Original invoice reference where applicable
salesTyCd N normal, T training, P proforma, C copy
rcptTyCd S sale or R refund
pmtTyCd Payment code from the EBM code catalogue
salesSttsCd Sales status code from the catalogue
salesDt Sale date in yyyyMMdd, without separators
cfmDt, other datetime fields When supplied, yyyyMMddHHmmss
totItemCnt Number of entries in itemList
itemList Product, quantity, package, tax and amount details
receipt Receipt/customer print details; include its prchrAcptcYn field
regrId, regrNm, modrId, modrNm Identifiers/names of the registering and modifying operators

Send invoice identifiers as JSON integers. JavaScript applications should avoid numbers above Number.MAX_SAFE_INTEGER; use a serializer that preserves 64-bit integer values if larger identifiers are needed. Do not send formatted numbers containing currency symbols or thousands separators.

The supported sale/receipt combinations are NS, NR, TS, TR, PS, CS, and CR. Refund requests require the applicable rfdRsnCd; coordinate the refund workflow and original invoice reference during onboarding. Do not change an existing invoice into a refund by resending altered content under its original idempotency key.

The supplied Java-compatible validation includes tax-rate, tax-computation and field constraints. Use the amounts produced by your agreed EBM mapping and validate that mapping in sandbox. This example is an API fixture, not a substitute for your accounting configuration.

4. Understand the response

Sales use an envelope containing resultCd, resultMsg, resultDt and, on success, data. The successful receipt data includes:

  • rcptNo: the receipt counter for the sale type.
  • totRcptNo: the branch's total receipt counter.
  • vsdcRcptPbctDate: the receipt publication timestamp.
  • intrlData and rcptSign: fiscal receipt values to retain and use as supplied.
  • sdcId and mrcNo: device identifiers when returned.

Training and proforma receipts have empty intrlData and rcptSign, matching the supplied Java applications. Do not manufacture signatures when they are empty.

For a new sale, resultCd: "000" confirms receipt issuance through the configured VSDC and storage by the service. It does not by itself confirm RRA delivery. Retain the complete response alongside your order and invoice. Use the receipt delivery endpoints below: ACCEPTED confirms recorded RRA acceptance; VSDC_ISSUED requires reconciliation with the configured VSDC. Webhooks are not implemented.

5. Retries and duplicate protection

Persist an idempotency key with the order before the first submission. Use that same key and identical transaction content if the connection times out or the response is lost. A completed retry returns the existing local receipt without allocating another invoice or receipt counter. Idempotency is scoped to company, environment and branch.

Use a different idempotency key for a different transaction. Reusing a completed key with changed transaction content returns HTTP 409; it is not an update operation. Local Rust compares the typed, sanitized VSDC request; remote VSDC and managed CIS retain their own original-input comparison. Preserve all original fields, including timestamps, for every retry. Legacy local idempotency records created before request binding was added have no provable input: their replay returns HTTP 409 and requires reconciliation with the existing receipt. Without the original key, resubmitting an already used invoice can return 924. Investigate that result before issuing a replacement invoice.

For local Rust signing, the service maintains its own durable RRA submission queue. With a remote VSDC, that VSDC owns downstream delivery; the service retains the issuance result and uncertain requests. Client retries are for obtaining the local outcome after uncertain HTTP delivery. They are not a way to force a second RRA submission.

6. Errors

Check both the HTTP status and resultCd. Compatibility errors can arrive with HTTP 200. HTTP authentication, authorization and rate-limit responses may use a different envelope. Treat messages as diagnostic text; branch your code on status/result codes.

Signal Client action
HTTP 401 Check the supplied key, expiry and revocation status
HTTP 403 Check the key's company and permitted operation
HTTP 409 Preserve the original request; reconcile conflicting input, uncertain issuance or a required managed CIS workflow. Do not generate a replacement invoice automatically
HTTP 429 Back off; respect Retry-After when supplied
910 Correct invalid or missing request fields; resending unchanged is ineffective
913 Correct a code value using the agreed catalogue
834 Correct the sale/receipt type combination
836 Ask support to investigate branch initialization or sequence state
881, 882, 883 Purchase-code validation failed, where that compatibility policy is enabled
901 Device setup is missing or invalid; contact support
924 Invoice already exists; reconcile it with the original transaction
894 Upstream connection failed; retry read operations with backoff and preserve sale idempotency
Other non-000 codes Retain the response and investigate; do not assume every error is retryable

For support, provide environment, company TIN, branch, invoice number, idempotency key, approximate request time and the response. Redact API keys and unnecessary customer details.

7. Endpoint catalogue

These are the Java-compatible routes. Access and required fields depend on your enabled operations. Initialization and reporting should be coordinated during onboarding.

Method Path Operation
POST /initializer/selectInitInfo Initialize a registered branch device
POST /code/selectCodes Retrieve code catalogue
POST /itemClass/selectItemsClass Retrieve item classifications
POST /branches/selectBranches Retrieve branch information
POST /branches/saveBrancheUsers Register branch user information
POST /branches/saveBrancheInsurances Register branch insurance information
POST /branches/saveBrancheCustomers Register branch customer information
POST /customers/selectCustomer Look up a customer
POST /items/selectItems Retrieve items
POST /items/saveItems Register/update items
POST /items/saveItemComposition Register item composition
POST /imports/selectImportItems Retrieve imports
POST /imports/updateImportItems Update an import item
POST /stock/selectStockItems Retrieve stock movements
POST /stock/saveStockItems Record stock movement
POST /stockMaster/saveStockMaster Update stock master
POST /trnsPurchase/selectTrnsPurchaseSales Retrieve supplier purchase transactions
POST /trnsPurchase/savePurchases Record a purchase
POST /trnsSales/saveSales Issue a local fiscal receipt
POST /reports/checkZReport Check a Z report
POST /reports/saveZReports Submit a Z report
POST /notices/selectNotices Retrieve notices
POST /test/echoTest Connectivity echo
GET /main/selectServerTime Retrieve upstream server time

For raw stock writes, supply the movement number (sarNo), original reference (orgSarNo), registration type, line count and totals explicitly. The VSDC compatibility endpoint forwards those values; it does not allocate a replacement movement number or recalculate your totals. Managed CIS stock workflows construct these fields on your behalf.

Catalogue lookup example:

{"tin":"999555888","bhfId":"00","lastReqDt":"20180520000000"}

Send that body to /code/selectCodes with a key permitted to read codes. Keep lastReqDt for incremental synchronization according to your integration workflow.

Reporting note for Java migrations

Z-reports include normal, copy, training and proforma groups. Compatibility reports reproduce the supplied WAR’s refund aggregation, including its known defect: a sale of 100 followed by refunds of 30 and 20 produces a reported refund amount of 120 (actual refunds are 50). Rust forwards the same WAR-compatible result. The supplied _9 WAR also has a report endpoint defect: an 8-digit report date returns 910; the required 14-digit date overflows its integer parser and returns 899. The compatibility endpoint reproduces those outcomes. A failed response does not submit a report or close the day.

8. Before live use

Complete sandbox exercises for each branch: normal sale, lost-response retry, duplicate invoice, refund, validation failure and upstream unavailability. Confirm printed receipt fields and reconcile transactions with support. Obtain a production key and separately initialized production branch; change credentials/configuration deliberately. Agreed service limits, retention, support contacts and availability commitments come from your service agreement, not hard-coded assumptions in a client.

Receipt delivery status

Use GET/api/portal/receipts with your X-API-Key to list up to 50 receipts in that key’s company and environment. Pass ?offset=50 for the next page. GET/api/portal/receipts/{id} returns a receipt and its delivery state; another company’s identifier returns 404. ACCEPTED confirms successful RRA delivery. Pending or failed states do not. These endpoints return a { "data": ... } envelope rather than the legacy resultCd envelope.

Development and Production

Your CIS uses one integration contract in both environments. Select the service URL and environment-pinned API key supplied for Development or Production. Development's API name remains sandbox; Production uses production. Browser users use the Environment selector. No request field selects Rust or Tomcat.

VSDC destinations and credentials are deployment settings managed by the administrator. See deployment setup and recovery. Changing an environment uses its separate company/branch fiscal state; it does not transfer receipts, counters or device assignments.

A receipt status of VSDC_ISSUED confirms issuance by the configured VSDC; final RRA delivery must be reconciled with that VSDC. Identical completed retries return the stored result. Changed input under an existing invoice/key returns HTTP 409. For an unknown signing outcome, retain the original request and contact the operator for reconciliation before issuing again.

Raw VSDC response contracts

OpenAPI 1.8.0 includes all 24 compatibility operations. Typed response fields come from Jackson serialization models in the supplied _9 WAR, including nested lists and nullable values. Successful sales describe fiscal receipt counters and signatures. Preserve decimal and 64-bit integer precision in client parsers.

Always inspect resultCd; HTTP 200 can contain a business error, with data or resultDt null or absent where documented. Standalone framework errors use timestamp, status, error and path. Hosted authentication, gateways and proxies can return other envelopes or plain text.

For standalone WAR-compatible deployments, send Accept: application/json. The supplied WAR and standalone Rust adapter can persist an invoice and then return HTTP 406 when the response cannot satisfy the requested media type. Treat that outcome as requiring reconciliation of the original invoice; do not issue a replacement under a new number. See standalone response negotiation. The shipped Python client already requests JSON.

Write routes and server-time passthrough do not enforce a typed upstream data model, so their schemas deliberately keep it flexible. These contracts describe the supplied compatibility target and recorded fixtures; they do not prove full parity, all remote-backend variants or RRA approval.

Monthly receipt activity

Use GET/api/portal/usage?month=2026-03 to read your company's recorded receipt counts in the API key's environment. Omit month for the current Kigali month. The response includes month, timezone, inclusive from, exclusive to_exclusive, environment, basis, totals and by_branch_and_mode inside data. Each group identifies branch and mode (NS, NR, CS, CR, TS, TR, PS or UNKNOWN). Counts are total, accepted, pending and vsdc_issued.

Month selection uses the receipt record's creation time in Africa/Kigali, not its submitted sales date. A completed idempotent replay adds no receipt record. Missing/unrecognized historical modes are counted as UNKNOWN. Accepted/pending apply to the embedded Rust backend; external VSDC records remain vsdc_issued, even if other delivery metadata exists. Delivery status is current when queried, so past-month status counts can change after later delivery. These are activity counts, not API request totals, subscription charges, quota enforcement or immutable billing statements. Use saved CIS reports for their documented reporting purpose.

Questions about onboarding, keys or branches? Contact your EBM Hub onboarding contact.