A production-ready Python SDK for integrating with the Kenya Revenue Authority (KRA) eTIMS OSCU (Online Sales Control Unit) API. Built to match the official Postman collection specifications with strict header compliance, token management, and comprehensive payload validation.
⚠️ Critical Note: This SDK implements the new OSCU specification (KRA-hosted), not the VSCU eTIMS API. OSCU requires device registration, headers, andcmcKeylifecycle management.
Bartile Emmanuel
📧 ebartile@gmail.com | 📱 +254757807150
Lead Developer, Paybill Kenya
- Introduction to eTIMS OSCU
- OSCU Integration Journey
- Critical Requirements
- Features
- Installation
- Configuration
- Usage Guide
- API Reference
- Field Validation Rules
- Error Handling
- Troubleshooting
- Automated Testing Requirements
- KYC Documentation Checklist
- Go-Live Process
- Support
- License
- Attribution
KRA's Electronic Tax Invoice Management System (eTIMS) uses OSCU (Online Sales Control Unit) – a KRA-hosted software module that validates and signs tax invoices in real-time before issuance. Unlike VSCU, OSCU requires:
- Pre-registered device serial numbers (
dvcSrlNo) - Communication key (
cmcKey) lifecycle management - Strict payload schema compliance per KRA specifications
Each receipt is formed from a combination of receipt type and transaction type:
| RECEIPT TYPE | TRANSACTION TYPE | RECEIPT LABEL | DESCRIPTION |
|---|---|---|---|
| NORMAL | SALES | NS | Standard tax invoice for customers |
| NORMAL | CREDIT NOTE | NC | Refund/return invoice |
| COPY | SALES | CS | Reprint with "Copy" watermark |
| COPY | CREDIT NOTE | CC | Reprint of credit note |
| TRAINING | SALES | TS | Training mode (no tax impact) |
| TRAINING | CREDIT NOTE | TC | Training credit note |
| PROFORMA | SALES | PS | Quote/proforma invoice |
KRA requires tax breakdown across 5 categories in all sales/purchase transactions:
| Code | Description | Standard Rate | Notes |
|---|---|---|---|
| A | VAT Standard | 16% | Most goods/services |
| B | VAT Special | 8% / 14% | Petroleum products, etc. |
| C | Zero-rated | 0% | Exports, humanitarian aid |
| D | Exempt | 0% | Financial services, education |
| E | Non-taxable | 0% | Insurance, residential rent |
💡 Critical: All 15 tax fields required in payloads:
taxblAmtA/B/C/D/E,taxRtA/B/C/D/E,taxAmtA/B/C/D/E
KRA mandates a strict 6-phase integration process before production deployment:
flowchart TD
A[1. Sign Up & Device Registration] --> B[2. Discovery & Simulation]
B --> C[3. Automated App Testing]
C --> D[4. KYC Documentation]
D --> E[5. Verification Meeting]
E --> F[6. Go-Live Deployment]
- Register on eTIMS Taxpayer Sandbox Portal
- Submit Service Request → Select "eTIMS" → Choose "OSCU" type
- Await SMS confirmation of OSCU approval
- Critical: Device serial number (
dvcSrlNo) is provisioned during this phase
- Create application on GavaConnect Developer Portal
- Obtain sandbox credentials:
- Consumer Key/Secret (for token generation)
- Approved device serial number (
dvcSrlNo)
- Run integration tests against sandbox environment
- Critical: Upload test artifacts within 1 hour of test completion:
- Item creation screenshot
- Invoice generation screenshot
- Sample invoice copy (PDF/print)
- Credit note copy (PDF/print)
Third-Party Integrators Must Submit:
- eTIMS Bio Data Form (completed)
- Certificate of Incorporation / Business Registration + CR12
- Valid Business Permit
- National IDs of directors/partners/sole proprietor
- Company Tax Compliance Certificate (valid)
- Proof of 3+ qualified technical staff (CVs + certifications)
- Notarized solvency declaration
- Technology Architecture document (TIS ↔ eTIMS integration diagram)
Self-Integrators Only Need:
- Items 1, 5, 6, and 8 from above list
- Schedule demo via Developer Portal
- Demonstrate:
- Invoice data database structure
- Credit note database structure
- Complete invoice format (with OSCU signatures)
- Item creation workflow
- Stock management integration
- Address KRA feedback within 48 hours if failed
- Execute SLA with KRA (third-party integrators only)
- Receive production keys via Developer Portal
- Deploy to production environment
- Monitor compliance for first 30 days
Before integration, you MUST complete these prerequisites:
- Register OSCU device via eTIMS Taxpayer Production Portal
- Register OSCU device via eTIMS Taxpayer Sandbox Portal
- Obtain approved device serial number (
dvcSrlNo) ⚠️ Dynamic/unregistered device serials fail withresultCd: 901("It is not valid device")
# 1. Initialize FIRST (returns cmcKey)
response = etims.select_init_osdc_info({
"tin": config["oscu"]["tin"],
"bhfId": config["oscu"]["bhf_id"],
"dvcSrlNo": config["oscu"]["device_serial"], # KRA-approved serial
})
# 2. Extract cmcKey
cmc_key = response.get("cmcKey")
# 3. Update config IMMEDIATELY
config["oscu"]["cmc_key"] = cmc_key
# 4. Recreate client with updated config (critical!)
etims = EtimsClient(config, auth)🔔 Note:
cmcKeyis only required for certain write operations (branch/user/insurance), not all endpoints.
- MUST be sequential integers (1, 2, 3...) – NOT strings (
INV001) - Must be unique per branch office (
bhfId) - Cannot be reused even after cancellation
- KRA rejects non-integer invoice numbers with
resultCd: 500
| Field | Format | Example | Validation Rule |
|---|---|---|---|
salesDt, pchsDt, ocrnDt |
YYYYMMDD |
20260131 |
Cannot be future date |
cfmDt, stockRlsDt, rcptPbctDt |
YYYYMMDDHHmmss |
20260131143022 |
Must be current/past |
lastReqDt |
YYYYMMDDHHmmss |
20260130143022 |
Cannot be future date; max 7 days old |
✅ Postman Collection Compliance
- 100% header, path, and payload alignment with official KRA Postman collection
- Correct nested paths (
/insert/stockIO,/save/stockMaster) - All 8 functional categories implemented
✅ Strict Header Management
| Endpoint Type | Required Headers | Initialization Exception |
|---|---|---|
| Initialization | Authorization |
❌ NO tin/bhfId/cmcKey |
| All Other Endpoints | Authorization, tin, bhfId, cmcKey |
✅ Full header set |
✅ Token Lifecycle Management
- Automatic token caching with 60-second buffer
- Transparent token refresh on 401 errors
- File-based cache with configurable location
✅ Comprehensive Validation
- Pydantic v2 schemas matching KRA specifications
- Field-level validation with human-readable errors
- Date format enforcement (
YYYYMMDDHHmmss) - Tax category validation (A/B/C/D/E)
✅ Production Ready
- SSL verification enabled by default
- Timeout configuration (default: 30s)
- Environment-aware (sandbox/production)
- Detailed error diagnostics with KRA fault strings
pip install kra-etims-sdk- Python 3.9+
requests(≥2.31)pydantic(≥2.0)
💡 The SDK uses plain dictionaries for configuration — no custom config class required.
Define your config as a plain Python dictionary:
import os
import tempfile
config = {
'env': 'sbx', # 'sbx' = sandbox, 'prod' = production
'cache_file': os.path.join(tempfile.gettempdir(), 'kra_etims_token.json'),
'auth': {
'sbx': {
'token_url': 'https://sbx.kra.go.ke/v1/token/generate',
'consumer_key': os.getenv('KRA_CONSUMER_KEY'),
'consumer_secret': os.getenv('KRA_CONSUMER_SECRET'),
}
},
'api': {
'sbx': {'base_url': 'https://etims-api-sbx.kra.go.ke/etims-api'}
},
'http': {'timeout': 30},
'oscu': {
'tin': os.getenv('KRA_TIN'),
'bhf_id': os.getenv('KRA_BHF_ID') or '01',
'device_serial': os.getenv('DEVICE_SERIAL'),
'cmc_key': '', # populated after initialization
}
}✅ Required environment variables:
KRA_CONSUMER_KEY,KRA_CONSUMER_SECRET,KRA_TIN,DEVICE_SERIAL
⚠️ Never include trailing spaces in URLs — they cause silent connection failures.
from kra_etims_sdk.auth import AuthClient
from kra_etims_sdk.client import EtimsClient
auth = AuthClient(config)
etims = EtimsClient(config, auth)try:
auth.forget_token() # Clear cached token
token = auth.token(force=True)
print(f"✅ Token OK: {token[:25]}...")
except Exception as e:
print(f"❌ Authentication failed: {e}")
exit(1)Only required for
cmcKey-dependent operations (e.g., saving branch users or insurance).
try:
init_resp = etims.select_init_osdc_info({
'tin': config['oscu']['tin'],
'bhfId': config['oscu']['bhf_id'],
'dvcSrlNo': config['oscu']['device_serial'],
})
cmc_key = init_resp.get('cmcKey')
if not cmc_key:
raise RuntimeError("cmcKey not found in response")
config['oscu']['cmc_key'] = cmc_key
etims = EtimsClient(config, auth) # Reinitialize to inject cmcKey
print(f"✅ OSCU initialized. cmcKey: {cmc_key[:15]}...")
except Exception as e:
print(f"❌ OSCU Init failed: {e}")
exit(1)# Fetch code list
codes = etims.select_code_list({'lastReqDt': '20260101000000'})
# Save an item
item_resp = etims.save_item({
'itemCd': 'KE1NTXU0000006',
'itemClsCd': '5059690800',
'itemNm': 'Test Material',
'pkgUnitCd': 'NT',
'qtyUnitCd': 'U',
'taxTyCd': 'B',
'dftPrc': 3500,
'useYn': 'Y',
'regrId': 'Test', 'regrNm': 'Test',
'modrId': 'Test', 'modrNm': 'Test',
})
# Save purchase transaction (full tax breakdown required)
purchase_resp = etims.save_purchase({
'invcNo': 1,
'spplrTin': 'A123456789Z',
'pchsTyCd': 'N',
'rcptTyCd': 'P',
'pmtTyCd': '01',
'pchsSttsCd': '02',
'cfmDt': '20260206120000',
'pchsDt': '20260206',
'totItemCnt': 1,
'taxblAmtA': 0, 'taxblAmtB': 10500, 'taxblAmtC': 0, 'taxblAmtD': 0, 'taxblAmtE': 0,
'taxRtA': 0, 'taxRtB': 18, 'taxRtC': 0, 'taxRtD': 0, 'taxRtE': 0,
'taxAmtA': 0, 'taxAmtB': 1890, 'taxAmtC': 0, 'taxAmtD': 0, 'taxAmtE': 0,
'totTaxblAmt': 10500,
'totTaxAmt': 1890,
'totAmt': 10500,
'regrId': 'Test', 'regrNm': 'Test',
'modrId': 'Test', 'modrNm': 'Test',
'itemList': [/* ... */],
})✅ All payloads are validated using internal Pydantic schemas before sending.
| Category | Methods |
|---|---|
| Initialization | select_init_osdc_info() |
| Data Management | select_code_list(), select_customer(), select_notice_list(), select_branches(), select_item_classes(), select_items() |
| Branch Management | save_branch_customer(), save_branch_user(), save_branch_insurance() |
| Item Management | save_item(), save_item_composition() |
| Purchase Management | save_purchase(), select_purchases() |
| Sales Management | save_sales_transaction() |
| Stock Management | save_stock_io(), save_stock_master(), select_stock_movement() |
🔍 Each method maps to a KRA endpoint alias defined internally (e.g.,
save_purchase→insertTrnsPurchase).
| Field | Validation Rule | Error if Violated |
|---|---|---|
dvcSrlNo |
Must be pre-registered with KRA | resultCd: 901 "It is not valid device" |
lastReqDt |
Cannot be future date; max 7 days old | resultCd: 500 "Check request body" |
salesDt |
Must be YYYYMMDD format; not future |
resultCd: 500 |
cfmDt |
Must be YYYYMMDDHHmmss format |
resultCd: 500 |
invcNo |
Must be sequential integer (not string) | resultCd: 500 |
taxTyCd |
Must be A/B/C/D/E | resultCd: 500 |
itemCd |
Must exist in KRA system (for transactions) | resultCd: 500 |
pkg |
Must be ≥ 1 | resultCd: 500 |
qty |
Must be > 0.001 | resultCd: 500 |
dcRt |
Cannot be negative | resultCd: 500 |
| Exception | When Thrown | Example |
|---|---|---|
AuthenticationException |
Token generation fails | Invalid consumer key/secret |
ApiException |
KRA business error (resultCd !== '0000') |
resultCd: 500 (invalid payload) |
ValidationException |
Payload fails schema validation | Missing required field |
try:
response = etims.save_purchase(payload)
except ValidationException as e:
print("❌ Validation failed:")
for field, msg in e.details.items():
print(f" • {field}: {msg}")
except ApiException as e:
print(f"❌ KRA API Error ({e.error_code}): {e}")
if e.details and 'resultMsg' in e.details:
print(f"KRA Message: {e.details['resultMsg']}")
except AuthenticationException as e:
print(f"❌ Authentication failed: {e}")| Code | Meaning | Solution |
|---|---|---|
0000 |
Success | ✅ Operation completed |
901 |
"It is not valid device" | Use KRA-approved device serial |
902 |
"Invalid cmcKey" | Re-initialize OSCU to get fresh cmcKey |
500 |
"Check request body" | Validate payload against Postman schema |
501 |
"Mandatory information missing" | Check required fields per endpoint |
502 |
"Invalid format" | Fix date formats / data types |
503 |
"Data not found" | Verify TIN/branch/item exists in KRA system |
504 |
"Duplicate data" | Use unique invoice number |
505 |
"Data relationship error" | Check parent-child relationships |
401 |
"Unauthorized" | Check token validity header |
429 |
"Rate limit exceeded" | Implement request throttling (max 100 req/min) |
Cause: Device serial not registered with KRA sandbox
Solution:
- Email
timsupport@kra.go.kewith subject:
"Request for OSCU Sandbox Test Credentials - [Your Company Name] - PIN: [Your PIN]" - Use static approved serial (e.g.,
dvcv1130) – never generate dynamically - Verify TIN/branch ID match registered device
Cause: cmcKey expired or not set in config
Solution:
config['oscu']['cmc_key'] = extracted_cmc_key
etims = EtimsClient(config, auth) # MUST recreate clientCause: Copy-paste errors from documentation
Solution: Always verify URLs have no trailing whitespace.
Cause: Using string prefix (INV001) instead of integer
Solution: Use sequential integers starting from 1:
"invcNo": 1, # ✅ CorrectKRA mandates successful completion of automated tests before verification:
- Run integration tests against sandbox environment
- System validates:
- Token generation
- OSCU initialization
- Code list retrieval
- Item creation
- Sales transaction with full tax breakdown
- Invoice retrieval
- Upon success, system provides 1-hour window to upload artifacts
| Artifact | Format | Requirements |
|---|---|---|
| Item Creation Screenshot | PNG/JPEG | Must show item code, tax category, price |
| Invoice Generation Screenshot | PNG/JPEG | Must show OSCU signatures, QR code |
| Invoice Copy | Full invoice with all KRA-mandated fields | |
| Credit Note Copy | Must show original invoice reference |
⚠️ Failure to upload within 1 hour = Test invalidated → Must re-run entire test suite
- eTIMS Bio Data Form (completed with director details)
- Certificate of Incorporation + CR12 (or Business Registration Certificate)
- Valid Business Permit (current year)
- National IDs of all directors/partners
- Company Tax Compliance Certificate (valid for current year)
- Proof of 3+ qualified technical staff:
- CVs showing Python/Java/C# experience
- Certifications (e.g., AWS, Azure, KRA eTIMS training)
- Employment contracts showing system administration duties
- Notarized solvency declaration (signed by director + notary public)
- Technology Architecture document:
- System diagram showing TIS ↔ OSCU data flow
- Database schema for invoice storage
- Security measures (encryption, access controls)
- Disaster recovery plan
- eTIMS Bio Data Form
- Company Tax Compliance Certificate
- National ID of sole proprietor/director
- Technology Architecture document (simplified)
- Upon KYC approval, receive SLA template via Developer Portal
- Complete SLA with company details and authorized signatory
- Email signed SLA to
timsupport@kra.go.kewith subject:
"SLA Execution with KRA - [Your Company Name]" - Await KRA approval (5-7 business days)
- Receive production keys via Developer Portal:
- Production consumer key/secret
- Deploy to production environment (
api.developer.go.ke) - Monitor compliance for first 30 days (KRA conducts spot checks)
- Receive interim approval letter after KYC verification
- Receive production keys via Developer Portal
- Deploy directly to production environment
- No SLA execution required
💡 Production URL:
https://etims-api.kra.go.ke/etims-api
⚠️ Never use sandbox credentials in production – KRA monitors environment separation strictly
| Purpose | Contact | Expected Response |
|---|---|---|
| Sandbox credentials & device registration | timsupport@kra.go.ke |
1-3 business days |
| API technical issues & Postman collection | apisupport@kra.go.ke |
24-48 hours |
| Developer Portal access issues | apisupport@kra.go.ke |
24 hours |
| Verification meeting scheduling | Developer Portal UI | Instant (self-service) |
| SLA execution queries | timsupport@kra.go.ke |
3-5 business days |
- GitHub Issues: github.com/paybillke/kra-etims-python-sdk/issues
- Email: ebartile@gmail.com (for integration guidance)
- Emergency Hotline: +254757807150 (business hours only)
ℹ️ Disclaimer: This SDK is independently developed by Paybill Kenya and is not affiliated with or endorsed by the Kenya Revenue Authority (KRA). Always verify integration requirements with KRA before production deployment. KRA may update API specifications without notice – monitor GavaConnect Portal for updates.
MIT License
Copyright © 2024–2026 Bartile Emmanuel / Paybill Kenya
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This SDK was developed by Bartile Emmanuel for Paybill Kenya to simplify KRA eTIMS OSCU integration for Kenyan businesses. Special thanks to KRA for providing comprehensive API documentation and Postman collections.
🇰🇪 Proudly Made in Kenya – Supporting digital tax compliance for East Africa's largest economy.