curl --request POST \
--url https://sync2books-compliance-api.apps.kintro.money/v1/sales \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"traderInvoiceNumber": "INV-2026-0001",
"saleDate": "2026-10-02",
"lines": [
{
"itemId": "itm_example_1",
"quantity": 2,
"unitPrice": 3500
}
],
"customer": {
"name": "Example Customer Ltd",
"pin": "P000000000X"
},
"paymentTypeCode": "01"
}
'import requests
url = "https://sync2books-compliance-api.apps.kintro.money/v1/sales"
payload = {
"traderInvoiceNumber": "INV-2026-0001",
"saleDate": "2026-10-02",
"lines": [
{
"itemId": "itm_example_1",
"quantity": 2,
"unitPrice": 3500
}
],
"customer": {
"name": "Example Customer Ltd",
"pin": "P000000000X"
},
"paymentTypeCode": "01"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
traderInvoiceNumber: 'INV-2026-0001',
saleDate: '2026-10-02',
lines: [{itemId: 'itm_example_1', quantity: 2, unitPrice: 3500}],
customer: {name: 'Example Customer Ltd', pin: 'P000000000X'},
paymentTypeCode: '01'
})
};
fetch('https://sync2books-compliance-api.apps.kintro.money/v1/sales', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sync2books-compliance-api.apps.kintro.money/v1/sales",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'traderInvoiceNumber' => 'INV-2026-0001',
'saleDate' => '2026-10-02',
'lines' => [
[
'itemId' => 'itm_example_1',
'quantity' => 2,
'unitPrice' => 3500
]
],
'customer' => [
'name' => 'Example Customer Ltd',
'pin' => 'P000000000X'
],
'paymentTypeCode' => '01'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://sync2books-compliance-api.apps.kintro.money/v1/sales"
payload := strings.NewReader("{\n \"traderInvoiceNumber\": \"INV-2026-0001\",\n \"saleDate\": \"2026-10-02\",\n \"lines\": [\n {\n \"itemId\": \"itm_example_1\",\n \"quantity\": 2,\n \"unitPrice\": 3500\n }\n ],\n \"customer\": {\n \"name\": \"Example Customer Ltd\",\n \"pin\": \"P000000000X\"\n },\n \"paymentTypeCode\": \"01\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://sync2books-compliance-api.apps.kintro.money/v1/sales")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"traderInvoiceNumber\": \"INV-2026-0001\",\n \"saleDate\": \"2026-10-02\",\n \"lines\": [\n {\n \"itemId\": \"itm_example_1\",\n \"quantity\": 2,\n \"unitPrice\": 3500\n }\n ],\n \"customer\": {\n \"name\": \"Example Customer Ltd\",\n \"pin\": \"P000000000X\"\n },\n \"paymentTypeCode\": \"01\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://sync2books-compliance-api.apps.kintro.money/v1/sales")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"traderInvoiceNumber\": \"INV-2026-0001\",\n \"saleDate\": \"2026-10-02\",\n \"lines\": [\n {\n \"itemId\": \"itm_example_1\",\n \"quantity\": 2,\n \"unitPrice\": 3500\n }\n ],\n \"customer\": {\n \"name\": \"Example Customer Ltd\",\n \"pin\": \"P000000000X\"\n },\n \"paymentTypeCode\": \"01\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"sale": {
"id": "sale_example_1",
"type": "sale",
"status": "completed",
"traderInvoiceNumber": "INV-2026-0001",
"saleDate": "2026-10-02",
"paymentTypeCode": "01",
"customer": {
"name": "Example Customer Ltd",
"pin": "P000000000X",
"phone": null,
"email": null
},
"lines": [
{
"id": "line_example_1",
"itemId": "itm_example_1",
"name": "500ml Cooking Oil",
"description": "500ml Cooking Oil",
"quantity": 2,
"unitPrice": 3500,
"total": 7000,
"taxableAmount": 6034.48,
"taxAmount": 965.52,
"taxRate": 16,
"taxTypeCode": "B"
}
],
"totals": {
"total": 7000,
"taxableAmount": 6034.48,
"taxAmount": 965.52
},
"receipt": {
"cuInvoiceNo": "KRACU0000000000/12 NS",
"receiptNumber": 12,
"receiptLabel": "NS",
"scuId": "KRACU0000000000",
"scuDate": "02/10/2026",
"scuTime": "10:15:30",
"totalReceiptCounter": "12",
"signature": "AAAA-BBBB-CCCC",
"internalData": "DDDD-EEEE-FFFF",
"verificationUrl": "https://etims-verify.example/receipt?id=EXAMPLE",
"originalCuInvoiceNo": null
},
"originalSaleId": null,
"error": null,
"callback": null
}
}
}Issue a sale
Validates the sale, submits it to KRA and returns the signed receipt. Prices are tax inclusive: the VAT split is worked out per line from the item’s tax type, never from the request.
The status code tells you where the sale stands: 201 accepted by KRA, 202 still in flight (poll GET /v1/sales/{id}), 422 rejected or failed. Repeating the request with the same Idempotency-Key returns 200 with Idempotent-Replayed: true and files nothing new.
Required scope: sales:write.
curl --request POST \
--url https://sync2books-compliance-api.apps.kintro.money/v1/sales \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"traderInvoiceNumber": "INV-2026-0001",
"saleDate": "2026-10-02",
"lines": [
{
"itemId": "itm_example_1",
"quantity": 2,
"unitPrice": 3500
}
],
"customer": {
"name": "Example Customer Ltd",
"pin": "P000000000X"
},
"paymentTypeCode": "01"
}
'import requests
url = "https://sync2books-compliance-api.apps.kintro.money/v1/sales"
payload = {
"traderInvoiceNumber": "INV-2026-0001",
"saleDate": "2026-10-02",
"lines": [
{
"itemId": "itm_example_1",
"quantity": 2,
"unitPrice": 3500
}
],
"customer": {
"name": "Example Customer Ltd",
"pin": "P000000000X"
},
"paymentTypeCode": "01"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
traderInvoiceNumber: 'INV-2026-0001',
saleDate: '2026-10-02',
lines: [{itemId: 'itm_example_1', quantity: 2, unitPrice: 3500}],
customer: {name: 'Example Customer Ltd', pin: 'P000000000X'},
paymentTypeCode: '01'
})
};
fetch('https://sync2books-compliance-api.apps.kintro.money/v1/sales', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sync2books-compliance-api.apps.kintro.money/v1/sales",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'traderInvoiceNumber' => 'INV-2026-0001',
'saleDate' => '2026-10-02',
'lines' => [
[
'itemId' => 'itm_example_1',
'quantity' => 2,
'unitPrice' => 3500
]
],
'customer' => [
'name' => 'Example Customer Ltd',
'pin' => 'P000000000X'
],
'paymentTypeCode' => '01'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://sync2books-compliance-api.apps.kintro.money/v1/sales"
payload := strings.NewReader("{\n \"traderInvoiceNumber\": \"INV-2026-0001\",\n \"saleDate\": \"2026-10-02\",\n \"lines\": [\n {\n \"itemId\": \"itm_example_1\",\n \"quantity\": 2,\n \"unitPrice\": 3500\n }\n ],\n \"customer\": {\n \"name\": \"Example Customer Ltd\",\n \"pin\": \"P000000000X\"\n },\n \"paymentTypeCode\": \"01\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://sync2books-compliance-api.apps.kintro.money/v1/sales")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"traderInvoiceNumber\": \"INV-2026-0001\",\n \"saleDate\": \"2026-10-02\",\n \"lines\": [\n {\n \"itemId\": \"itm_example_1\",\n \"quantity\": 2,\n \"unitPrice\": 3500\n }\n ],\n \"customer\": {\n \"name\": \"Example Customer Ltd\",\n \"pin\": \"P000000000X\"\n },\n \"paymentTypeCode\": \"01\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://sync2books-compliance-api.apps.kintro.money/v1/sales")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"traderInvoiceNumber\": \"INV-2026-0001\",\n \"saleDate\": \"2026-10-02\",\n \"lines\": [\n {\n \"itemId\": \"itm_example_1\",\n \"quantity\": 2,\n \"unitPrice\": 3500\n }\n ],\n \"customer\": {\n \"name\": \"Example Customer Ltd\",\n \"pin\": \"P000000000X\"\n },\n \"paymentTypeCode\": \"01\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"sale": {
"id": "sale_example_1",
"type": "sale",
"status": "completed",
"traderInvoiceNumber": "INV-2026-0001",
"saleDate": "2026-10-02",
"paymentTypeCode": "01",
"customer": {
"name": "Example Customer Ltd",
"pin": "P000000000X",
"phone": null,
"email": null
},
"lines": [
{
"id": "line_example_1",
"itemId": "itm_example_1",
"name": "500ml Cooking Oil",
"description": "500ml Cooking Oil",
"quantity": 2,
"unitPrice": 3500,
"total": 7000,
"taxableAmount": 6034.48,
"taxAmount": 965.52,
"taxRate": 16,
"taxTypeCode": "B"
}
],
"totals": {
"total": 7000,
"taxableAmount": 6034.48,
"taxAmount": 965.52
},
"receipt": {
"cuInvoiceNo": "KRACU0000000000/12 NS",
"receiptNumber": 12,
"receiptLabel": "NS",
"scuId": "KRACU0000000000",
"scuDate": "02/10/2026",
"scuTime": "10:15:30",
"totalReceiptCounter": "12",
"signature": "AAAA-BBBB-CCCC",
"internalData": "DDDD-EEEE-FFFF",
"verificationUrl": "https://etims-verify.example/receipt?id=EXAMPLE",
"originalCuInvoiceNo": null
},
"originalSaleId": null,
"error": null,
"callback": null
}
}
}Authorizations
Your API key, for example cmp_sk_test_xxxxxxxxxxxxxxxxxxxxxxxx. Create one on the Integrations page of the compliance dashboard.
Headers
A unique value you choose for this document, 8 to 128 characters: letters, digits and . _ : -. Repeating the request with the same key returns the original result instead of filing a second document. Strongly recommended.
8 - 128^[A-Za-z0-9._:-]+$Body
Your own number for this sale. Unique per business.
50As YYYY-MM-DD.
1 - 200 elementsShow child attributes
Show child attributes
The buyer. Optional.
Show child attributes
Show child attributes
A KRA payment type code. See Codes & Values.
2Defaults to the business's first branch.
Where to send the result. An absolute https URL on a public host, with no credentials in it. See Result callbacks.
2048The business to act for. Required for organisation-wide keys; omit it for a business-bound key. List the ids you can use with GET /v1/businesses.
Response
A repeat of an earlier request. The original sale is returned and nothing is filed again.
Show child attributes
Show child attributes