A comprehensive Node.js SDK for integrating with the Kenya Revenue Authority (KRA) Electronic Tax Invoice Management System (eTims).
- Complete KRA eTims API integration
- Express.js server mode for easy API endpoint exposure
- CORS support with domain whitelisting
- Comprehensive validation for all API requests
- Detailed logging and error handling
- Environment-based configuration
- TypeScript support
Shadrack Matata
Email: kisamba.debug@gmail.com
Tel: +254722854082 / +254733854082
npm install kra-etims-sdk
const KRAeTimsSDK = require('kra-etims-sdk');
// Initialize the SDK
const sdk = new KRAeTimsSDK();
// Authenticate with KRA eTims API
async function authenticate() {
try {
const token = await sdk.authenticate('your_username', 'your_password');
console.log('Authentication successful:', token);
return token;
} catch (error) {
console.error('Authentication failed:', error);
}
}
// Send a sales transaction
async function sendSalesTransaction() {
try {
// Authenticate first
await authenticate();
// Prepare sales transaction data
const salesData = {
tin: 'P000000045R',
bhfId: '00',
invcNo: 'INV001',
salesTrnsItems: [
{
itemCd: 'ITEM001',
itemNm: 'Test Item',
qty: 1,
prc: 100,
splyAmt: 100,
dcRt: 0,
dcAmt: 0,
taxTyCd: 'V',
taxAmt: 16
}
]
};
// Send sales transaction
const result = await sdk.sendSalesTransaction(salesData);
console.log('Sales transaction sent:', result);
} catch (error) {
console.error('Failed to send sales transaction:', error);
}
}
Below is a comprehensive example of integrating the KRA eTims SDK with an Express application as middleware:
const express = require('express');
const bodyParser = require('body-parser');
const morgan = require('morgan'); // Optional for logging
const KRAeTimsSDK = require('kra-etims-sdk');
// Initialize Express app
const app = express();
const port = process.env.PORT || 3000;
// Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(morgan('dev')); // Optional for logging
// Initialize the KRA eTims SDK
const etimsSDK = new KRAeTimsSDK({
// Optional configuration
logLevel: 'info', // 'debug', 'info', 'warn', 'error'
timeout: 30000, // API request timeout in ms
});
// Mount the SDK as middleware
app.use('/etims-api', etimsSDK.middleware());
// Custom routes that use the SDK directly
// Authentication example
app.post('/api/login', async (req, res) => {
try {
const { username, password } = req.body;
const token = await etimsSDK.authenticate(username, password);
res.json({ success: true, token });
} catch (error) {
console.error('Authentication error:', error);
res.status(401).json({ success: false, error: error.message });
}
});
// Sales transaction example
app.post('/api/sales', async (req, res) => {
try {
// Authenticate first (or use middleware to handle this)
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
// Send sales transaction
const result = await etimsSDK.sendSalesTransaction(req.body);
res.json({ success: true, data: result });
} catch (error) {
console.error('Sales transaction error:', error);
res.status(400).json({ success: false, error: error.message });
}
});
// Get stock information example
app.post('/api/stock/info', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const result = await etimsSDK.getStockMoveList(req.body);
res.json({ success: true, data: result });
} catch (error) {
console.error('Stock info error:', error);
res.status(400).json({ success: false, error: error.message });
}
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error('Application error:', err);
res.status(500).json({ success: false, error: 'Internal server error' });
});
// Start the server
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Create a reusable authentication middleware for your Express routes:
// auth.middleware.js
const KRAeTimsSDK = require('kra-etims-sdk');
const sdk = new KRAeTimsSDK();
const authMiddleware = async (req, res, next) => {
try {
// You could also check for an existing token and validate it
await sdk.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
req.etimsSDK = sdk; // Attach the authenticated SDK to the request
next();
} catch (error) {
console.error('KRA eTims authentication error:', error);
res.status(401).json({ success: false, error: 'Authentication failed' });
}
};
module.exports = authMiddleware;
Then use it in your routes:
const express = require('express');
const authMiddleware = require('./auth.middleware');
const router = express.Router();
// Apply authentication middleware to all routes
router.use(authMiddleware);
// Now all routes have access to the authenticated SDK via req.etimsSDK
router.post('/sales/send', async (req, res) => {
try {
const result = await req.etimsSDK.sendSalesTransaction(req.body);
res.json({ success: true, data: result });
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
});
module.exports = router;
For comparison, here's how to set up the SDK as a standalone server:
const KRAeTimsSDK = require('kra-etims-sdk');
// Initialize the SDK with server option
const sdk = new KRAeTimsSDK({ server: true });
// Start the server
const port = process.env.PORT || 5000;
sdk.start(port).then(() => {
console.log(`KRA eTims server started on port ${port}`);
}).catch(err => {
console.error('Failed to start server:', err);
});
Create a .env
file in your project root:
# API Base URLs
DEV_API_BASE_URL=https://etims-api-sbx.kra.go.ke
PROD_API_BASE_URL=https://etims-api.kra.go.ke/etims-api
# Authentication
API_USERNAME=your_username
API_PASSWORD=your_password
# Environment
NODE_ENV=development
PORT=5000
# CORS Configuration
CORS_ALLOWED_ORIGINS=http://localhost:3000,https://yourdomain.com
The SDK automatically selects the appropriate API base URL based on the NODE_ENV
environment variable:
- When
NODE_ENV=development
(or not set), the SDK usesDEV_API_BASE_URL
(sandbox environment) - When
NODE_ENV=production
, the SDK usesPROD_API_BASE_URL
(production environment)
# For development (sandbox)
NODE_ENV=development npm start
# For production
NODE_ENV=production npm start
// Before initializing the SDK
process.env.NODE_ENV = 'production'; // or 'development'
const KRAeTimsSDK = require('kra-etims-sdk');
const sdk = new KRAeTimsSDK();
You can also override the automatic selection by explicitly setting the API base URL when initializing the SDK:
const KRAeTimsSDK = require('kra-etims-sdk');
// For development (sandbox)
const devSdk = new KRAeTimsSDK({
apiBaseUrl: 'https://etims-api-sbx.kra.go.ke'
});
// For production
const prodSdk = new KRAeTimsSDK({
apiBaseUrl: 'https://etims-api.kra.go.ke/etims-api'
});
The SDK includes built-in CORS support with domain whitelisting to secure your API endpoints.
You can configure allowed origins in your .env
file:
CORS_ALLOWED_ORIGINS=https://yourdomain.com,https://app.yourdomain.com
By default, the SDK allows requests from http://localhost:3000
and http://localhost:5000
in development.
This section provides comprehensive examples of how to use each endpoint in your Express application.
Request Body:
{
"username": "your_username",
"password": "your_password"
}
Express Integration Example:
const express = require('express');
const KRAeTimsSDK = require('kra-etims-sdk');
const router = express.Router();
// Initialize the SDK
const etimsSDK = new KRAeTimsSDK();
router.post('/auth', async (req, res) => {
try {
const { username, password } = req.body;
// Call the SDK's authentication method
const token = await etimsSDK.authenticate(username, password);
// Store token in session or return to client
req.session.etimsToken = token;
res.json({
success: true,
message: 'Authentication successful',
expiresAt: etimsSDK.getTokenExpiry()
});
} catch (error) {
console.error('Authentication error:', error);
res.status(401).json({
success: false,
message: 'Authentication failed',
error: error.message
});
}
});
module.exports = router;
Request Body:
{
"tin": "P000000045R",
"bhfId": "00",
"dvcSrlNo": "MOVA22"
}
Express Integration Example:
router.post('/initialize', async (req, res) => {
try {
// Ensure authentication
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const initData = req.body;
const result = await etimsSDK.initializeOsdcInfo(initData);
res.json({
success: true,
message: 'OSDC initialization successful',
data: result
});
} catch (error) {
console.error('Initialization error:', error);
res.status(400).json({
success: false,
message: 'Initialization failed',
error: error.message
});
}
});
Request Body:
{
"tin": "P000000045R",
"bhfId": "00",
"lastReqDt": "20220101010101"
}
Express Integration Example:
router.post('/code-list', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const result = await etimsSDK.getCodeList(req.body);
res.json({
success: true,
data: result
});
} catch (error) {
console.error('Code list error:', error);
res.status(400).json({
success: false,
error: error.message
});
}
});
Request Body:
{
"tin": "P000000045R",
"bhfId": "00",
"lastReqDt": "20220101010101"
}
Express Integration Example:
router.post('/item-classifications', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const result = await etimsSDK.getItemClassificationList(req.body);
// Cache the results for future use
req.app.locals.itemClassifications = result;
res.json({
success: true,
count: result.length,
data: result
});
} catch (error) {
console.error('Item classification error:', error);
res.status(400).json({
success: false,
error: error.message
});
}
});
Request Body:
{
"lastReqDt": "20220101010101"
}
Express Integration Example:
router.post('/branches', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const result = await etimsSDK.getBranchList(req.body);
res.json({
success: true,
count: result.length,
data: result
});
} catch (error) {
console.error('Branch list error:', error);
res.status(400).json({
success: false,
error: error.message
});
}
});
Request Body:
{
"tin": "P000000045R",
"bhfId": "00",
"lastReqDt": "20220101010101"
}
Express Integration Example:
router.post('/notices', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const result = await etimsSDK.getNoticeList(req.body);
res.json({
success: true,
data: result
});
} catch (error) {
console.error('Notice list error:', error);
res.status(400).json({
success: false,
error: error.message
});
}
});
Request Body:
{
"tin": "P000000045R",
"bhfId": "00",
"lastReqDt": "20220101010101"
}
Express Integration Example:
router.post('/taxpayer', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const result = await etimsSDK.getTaxpayerInfo(req.body);
res.json({
success: true,
data: result
});
} catch (error) {
console.error('Taxpayer info error:', error);
res.status(400).json({
success: false,
error: error.message
});
}
});
Request Body:
{
"tin": "P000000045R",
"bhfId": "00",
"lastReqDt": "20220101010101"
}
Express Integration Example:
router.post('/customers', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const result = await etimsSDK.getCustomerList(req.body);
res.json({
success: true,
count: result.length,
data: result
});
} catch (error) {
console.error('Customer list error:', error);
res.status(400).json({
success: false,
error: error.message
});
}
});
Request Body:
{
"tin": "P000000045R",
"bhfId": "00",
"invcNo": "INV001",
"salesTrnsItems": [
{
"itemCd": "ITEM001",
"itemNm": "Test Item",
"qty": 1,
"prc": 100,
"splyAmt": 100,
"dcRt": 0,
"dcAmt": 0,
"taxTyCd": "V",
"taxAmt": 16
}
]
}
Express Integration Example:
router.post('/sales/create', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
// You might want to validate the sales data before sending
const salesData = req.body;
// Calculate totals if not provided
if (!salesData.totals) {
const items = salesData.salesTrnsItems;
const totalAmount = items.reduce((sum, item) => sum + item.splyAmt, 0);
const totalTax = items.reduce((sum, item) => sum + item.taxAmt, 0);
salesData.totals = {
totalAmount,
totalTax,
grandTotal: totalAmount + totalTax
};
}
const result = await etimsSDK.sendSalesTransaction(salesData);
// You might want to store the result in your database
res.json({
success: true,
message: 'Sales transaction recorded successfully',
data: result,
receiptNo: result.receiptNo,
timestamp: result.timestamp
});
} catch (error) {
console.error('Sales transaction error:', error);
res.status(400).json({
success: false,
message: 'Failed to record sales transaction',
error: error.message
});
}
});
Request Body:
{
"tin": "P000000045R",
"bhfId": "00",
"lastReqDt": "20220101010101",
"invcNo": "INV001" // Optional
}
Express Integration Example:
router.post('/sales/history', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const result = await etimsSDK.getSalesTransactions(req.body);
res.json({
success: true,
count: result.length,
data: result
});
} catch (error) {
console.error('Sales history error:', error);
res.status(400).json({
success: false,
error: error.message
});
}
});
// Get a specific invoice
router.get('/sales/invoice/:invoiceNo', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const params = {
tin: process.env.KRA_TIN,
bhfId: process.env.KRA_BHF_ID,
lastReqDt: '20220101010101',
invcNo: req.params.invoiceNo
};
const result = await etimsSDK.getSalesTransactions(params);
if (result.length === 0) {
return res.status(404).json({
success: false,
message: 'Invoice not found'
});
}
res.json({
success: true,
data: result[0]
});
} catch (error) {
console.error('Invoice retrieval error:', error);
res.status(400).json({
success: false,
error: error.message
});
}
});
Request Body:
{
"tin": "P000000045R",
"bhfId": "00",
"lastReqDt": "20220101010101"
}
Express Integration Example:
router.post('/stock/movements', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const result = await etimsSDK.getStockMoveList(req.body);
res.json({
success: true,
count: result.length,
data: result
});
} catch (error) {
console.error('Stock movement error:', error);
res.status(400).json({
success: false,
error: error.message
});
}
});
Request Body:
{
"tin": "P000000045R",
"bhfId": "00",
"itemCd": "ITEM001",
"itemClsCd": "FOOD",
"itemNm": "Test Item",
"pkgUnitCd": "EA",
"qtyUnitCd": "EA",
"splyAmt": 100,
"vatTyCd": "V"
}
Express Integration Example:
router.post('/stock/create', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const stockData = req.body;
const result = await etimsSDK.saveStockMaster(stockData);
res.json({
success: true,
message: 'Stock item created successfully',
data: result
});
} catch (error) {
console.error('Stock creation error:', error);
res.status(400).json({
success: false,
message: 'Failed to create stock item',
error: error.message
});
}
});
Request Body:
{
"tin": "P000000045R",
"bhfId": "00",
"lastReqDt": "20220101010101"
}
Express Integration Example:
router.post('/purchases', async (req, res) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
const result = await etimsSDK.getPurchaseTransactions(req.body);
res.json({
success: true,
count: result.length,
data: result
});
} catch (error) {
console.error('Purchase history error:', error);
res.status(400).json({
success: false,
error: error.message
});
}
});
Here's how to organize all these endpoints into a complete Express router:
// routes/etims.routes.js
const express = require('express');
const KRAeTimsSDK = require('kra-etims-sdk');
const router = express.Router();
// Initialize the SDK
const etimsSDK = new KRAeTimsSDK();
// Authentication middleware
const authMiddleware = async (req, res, next) => {
try {
await etimsSDK.authenticate(process.env.API_USERNAME, process.env.API_PASSWORD);
req.etimsSDK = etimsSDK;
next();
} catch (error) {
res.status(401).json({
success: false,
message: 'KRA eTims authentication failed',
error: error.message
});
}
};
// Apply authentication middleware to all routes except login
router.use((req, res, next) => {
if (req.path === '/auth') {
return next();
}
authMiddleware(req, res, next);
});
// Authentication route
router.post('/auth', async (req, res) => {
try {
const { username, password } = req.body;
const token = await etimsSDK.authenticate(username, password);
res.json({ success: true, token });
} catch (error) {
res.status(401).json({ success: false, error: error.message });
}
});
// Basic data routes
router.post('/code-list', async (req, res) => {
try {
const result = await req.etimsSDK.getCodeList(req.body);
res.json({ success: true, data: result });
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
});
// Add other routes here...
module.exports = router;
// In your main app.js:
// const etimsRoutes = require('./routes/etims.routes');
// app.use('/api/etims', etimsRoutes);
const { AuthenticationError, ValidationError } = require('kra-etims-sdk/errors');
try {
// SDK operations
} catch (error) {
if (error instanceof AuthenticationError) {
// Handle authentication errors
console.error('Authentication failed:', error.message);
} else if (error instanceof ValidationError) {
// Handle validation errors
console.error('Validation failed:', error.details);
} else {
// Handle other errors
console.error('Operation failed:', error);
}
}
import KRAeTimsSDK from 'kra-etims-sdk';
import { SalesTransaction, ApiResponse } from 'kra-etims-sdk/types';
const sdk = new KRAeTimsSDK();
async function processSale(sale: SalesTransaction): Promise<ApiResponse> {
try {
await sdk.authenticate('username', 'password');
return await sdk.sendSalesTransaction(sale);
} catch (error) {
console.error('Failed to process sale:', error);
throw error;
}
}
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the ISC License - see the LICENSE file for details.