Query Commodity Category / Excise Duty by Issue Date (T146)
Retrieve commodity category or excise duty details as they were effective on a specific historical date. This endpoint supports audit trails and historical tax rate validation by returning the configuration valid at the time of issueDate. Request and response are encrypted.
Endpoint Overviewβ
| Property | Value |
|---|---|
| Interface Code | T146 |
| Request Encrypted | β Yes |
| Response Encrypted | β Yes |
| Request Body | { "categoryCode": "...", "type": "1", "issueDate": "..." } |
| Response Format | JSON Object |
Flow Descriptionβ
- Client submits
categoryCode(commodity or excise code),type(1=Category, 2=Excise), andissueDate. - Server validates the code exists and retrieves the version effective on the specified date.
- Server returns configuration details including tax rates, exemption flags, and validity periods.
- Client uses response to validate historical transactions or reconstruct past tax calculations.
π‘ Tip: Use T146 when auditing past invoices to ensure the tax rates and commodity categories applied match the rules effective on the invoice date, not current rules.
- PHP
- JavaScript / TypeScript
- Python
try {
// Call T146: Query Commodity/Excise by Issue Date
$response = $client->queryCommodityByDate(
categoryCode: '13101501', // Commodity or Excise code
type: '1', // 1=Commodity Category, 2=Excise Duty
issueDate: '2025-02-19 10:02:02' // Historical date
);
$content = $response['data']['content'] ?? $response;
// Check Commodity Category response
if (isset($content['commodityCategory'])) {
$cat = $content['commodityCategory'];
echo "β
Commodity Category: {$cat['commodityCategoryName']}\n";
echo " Code: {$cat['commodityCategoryCode']}\n";
echo " Rate: {$cat['rate']} ({$cat['isZeroRate']} zero-rated, {$cat['isExempt']} exempt)\n";
echo " Excisable: {$cat['excisable']}\n";
}
// Check Excise Duty response
if (isset($content['exciseDuty'])) {
$excise = $content['exciseDuty'];
echo "β
Excise Duty: {$excise['goodService']}\n";
echo " Code: {$excise['exciseDutyCode']}\n";
echo " Rate Text: {$excise['rateText']}\n";
if (!empty($excise['exciseDutyDetailsList'])) {
foreach ($excise['exciseDutyDetailsList'] as $detail) {
echo " Detail: Type {$detail['type']}, Rate {$detail['rate']}\n";
}
}
}
} catch (\UraEfrisSdk\Exceptions\APIException $e) {
echo "β Query failed: " . $e->getMessage() . "\n";
echo " Return Code: " . $e->getReturnCode() . "\n";
}
try {
// Call T146: Query Commodity/Excise by Issue Date
const response = await client.queryCommodityByDate(
'13101501', // categoryCode
'1', // type (1=Category, 2=Excise)
'2025-02-19 10:02:02' // issueDate
);
const content = response?.data?.content ?? response;
// Check Commodity Category response
if (content?.commodityCategory) {
const cat = content.commodityCategory;
console.log('β
Commodity Category:', cat.commodityCategoryName);
console.log(' Code:', cat.commodityCategoryCode);
console.log(' Rate:', cat.rate, `(Zero: ${cat.isZeroRate}, Exempt: ${cat.isExempt})`);
console.log(' Excisable:', cat.excisable);
}
// Check Excise Duty response
if (content?.exciseDuty) {
const excise = content.exciseDuty;
console.log('β
Excise Duty:', excise.goodService);
console.log(' Code:', excise.exciseDutyCode);
console.log(' Rate Text:', excise.rateText);
if (excise.exciseDutyDetailsList?.length) {
excise.exciseDutyDetailsList.forEach((detail: any) => {
console.log(` Detail: Type ${detail.type}, Rate ${detail.rate}`);
});
}
}
return content;
} catch (error: any) {
console.error(`β Query failed: ${error.message}`);
if (error.returnCode) {
console.error(` Return Code: ${error.returnCode}`);
}
throw error;
}
try:
# Call T146: Query Commodity/Excise by Issue Date
response = client.query_commodity_by_date(
category_code='13101501', # Commodity or Excise code
item_type='1', # 1=Commodity Category, 2=Excise Duty
issue_date='2025-02-19 10:02:02' # Historical date
)
content = response.get("data", {}).get("content", response)
# Check Commodity Category response
if content.get("commodityCategory"):
cat = content["commodityCategory"]
print("β
Commodity Category:", cat["commodityCategoryName"])
print(" Code:", cat["commodityCategoryCode"])
print(f" Rate: {cat['rate']} (Zero: {cat['isZeroRate']}, Exempt: {cat['isExempt']})")
print(" Excisable:", cat["excisable"])
# Check Excise Duty response
if content.get("exciseDuty"):
excise = content["exciseDuty"]
print("β
Excise Duty:", excise["goodService"])
print(" Code:", excise["exciseDutyCode"])
print(" Rate Text:", excise["rateText"])
if excise.get("exciseDutyDetailsList"):
for detail in excise["exciseDutyDetailsList"]:
print(f" Detail: Type {detail['type']}, Rate {detail['rate']}")
except Exception as e:
print(f"β Query failed: {e}")
if hasattr(e, "return_code"):
print(f" Return Code: {e.return_code}")
raise
Request Structureβ
{
"data": {
"content": "BASE64_ENCRYPTED_PAYLOAD",
"signature": "JKQWJK34K32JJEK2JQWJ5678",
"dataDescription": {
"codeType": "1",
"encryptCode": "2",
"zipCode": "0"
}
},
"globalInfo": {
"interfaceCode": "T146",
"appId": "AP04",
"version": "1.1.20191201",
"tin": "1000029771",
"deviceNo": "TCS9e0df01728335239",
"taxpayerID": "1"
}
}
Request Fields (Encrypted Payload)β
| Field | Required | Type | Length | Description |
|---|---|---|---|---|
categoryCode | β Yes | String | β€20 | Commodity category code (if type=1) or Excise duty code (if type=2) |
type | β Yes | String (1) | 1 | 1=Commodity Category, 2=Excise Duty |
issueDate | β Yes | String | 20 | Historical date to query (format: yyyy-MM-dd HH:mm:ss) |
π‘ Tip: The
issueDatedetermines which version of the commodity category or excise duty configuration is returned. Use this to validate historical transactions.
Response Structureβ
{
"data": {
"content": {
"commodityCategory": {
"commodityCategoryCode": "100000000",
"parentCode": "0",
"commodityCategoryName": "Standard",
"commodityCategoryLevel": "1",
"rate": "0.18",
"isLeafNode": "101",
"serviceMark": "101",
"isZeroRate": "101",
"zeroRateStartDate": "19/02/2025",
"zeroRateEndDate": "28/02/2025",
"isExempt": "101",
"exemptRateStartDate": "19/02/2025",
"exemptRateEndDate": "28/02/2025",
"enableStatusCode": "1",
"exclusion": "1",
"excisable": "101",
"vatOutScopeCode": "102"
},
"exciseDuty": {
"effectiveDate": "19/02/2025",
"exciseDutyCode": "LED010300",
"goodService": "Un-denatured spirits m",
"id": "320389606969887681",
"isLeafNode": "1",
"parentCode": "LED010000",
"rateText": "60%,UGX2000 per Kg",
"exciseDutyDetailsList": [
{
"currency": "101",
"exciseDutyId": "320389606969887681",
"rate": "2000",
"type": "102",
"unit": "103"
}
]
}
}
},
"globalInfo": {
"interfaceCode": "T146",
"returnStateInfo": {
"returnCode": "00",
"returnMessage": "SUCCESS"
}
}
}
Response Fieldsβ
Commodity Category Object (When type=1)β
| Field | Required | Type | Length | Description |
|---|---|---|---|---|
commodityCategoryCode | β Yes | String | β€18 | Commodity category identifier |
parentCode | β Yes | String | β€18 | Parent category code (0 for root) |
commodityCategoryName | β Yes | String | β€200 | Category name |
commodityCategoryLevel | β Yes | Number | 1 | Hierarchy level (1=root) |
rate | β Yes | String | β€4 | Tax rate (e.g., 0.18 for 18%) |
isLeafNode | β Yes | String (1) | 1 | 101=Yes (leaf), 102=No (has children) |
serviceMark | β Yes | String (1) | 1 | 101=Service, 102=Goods |
isZeroRate | β Yes | String (1) | 1 | 101=Yes (zero-rated), 102=No |
zeroRateStartDate | β No | Date | - | Zero-rate validity start (dd/MM/yyyy) |
zeroRateEndDate | β No | Date | - | Zero-rate validity end (dd/MM/yyyy) |
isExempt | β Yes | String (1) | 1 | 101=Yes (exempt), 102=No |
exemptRateStartDate | β No | Date | - | Exemption validity start (dd/MM/yyyy) |
exemptRateEndDate | β No | Date | - | Exemption validity end (dd/MM/yyyy) |
enableStatusCode | β Yes | String (1) | 1 | 1=Enabled, 0=Disabled |
exclusion | β Yes | String (1) | 1 | 0=Zero, 1=Exempt, 2=No exclusion, 3=Both |
excisable | β Yes | String (3) | 3 | 101=Yes (excisable), 102=No |
vatOutScopeCode | β Yes | String (3) | 3 | 101=Yes (out of scope), 102=No |
Excise Duty Object (When type=2)β
| Field | Required | Type | Length | Description |
|---|---|---|---|---|
id | β Yes | String | β€20 | Excise duty record ID |
exciseDutyCode | β Yes | String | β€20 | Excise duty identifier |
goodService | β Yes | String | β€500 | Description of goods/service |
parentCode | β Yes | String | β€20 | Parent excise category code |
rateText | β Yes | String | β€50 | Human-readable rate description |
isLeafNode | β Yes | String (1) | 1 | 1=Yes, 0=No |
effectiveDate | β Yes | Date | - | Effective date (dd/MM/yyyy) |
exciseDutyDetailsList | β No | Array | - | List of rate calculation details |
Excise Duty Details Array Itemsβ
| Field | Required | Type | Description |
|---|---|---|---|
currency | β Yes | String (3) | Currency code from T115 |
exciseDutyId | β Yes | String (18) | Excise duty ID |
rate | β Yes | Number | Tax rate value |
type | β Yes | String (10) | 101=Percentage, 102=Unit of measurement |
unit | β No | String (3) | Unit code from T115 (required if type=102) |
Return Codesβ
| Code | Message | Description |
|---|---|---|
00 | SUCCESS | Query completed successfully |
99 | Unknown error | Generic server error |
400 | Device does not exist | deviceNo not registered for this TIN |
402 | Device key expired | Device credentials expired; re-run T102 |
403 | Device status is abnormal | Device blocked or suspended |
2860 | type: cannot be empty! | Missing type parameter |
2861 | type: Byte length cannot be greater than 1! | Invalid type length |
2862 | type: Invalid field value! | type must be 1 or 2 |
2863 | categoryCode: cannot be empty! | Missing categoryCode |
2864 | categoryCode: Byte length cannot be greater than 1! | Invalid code length (note: docs say 20, error says 1βvalidate both) |
2865 | issuedDate: cannot be empty! | Missing issueDate |
2866 | issuedDate: The time format must be yyyy-MM-dd HH:mm:ss | Invalid date format |
2867 | categoryCode: does not exist! | Provided code not found in system |
π‘ Tip: If the
categoryCodedid not exist on the specifiedissueDate, the server may return an empty object or error2867. Always validate the response content exists.
Common Use Casesβ
-
Historical Tax Audit
Verify the tax rate and category configuration that was effective on a past invoice date during audits. -
Transaction Reconstruction
Rebuild historical tax calculations using the exact rates and rules active at the time of transaction. -
Rate Change Validation
Confirm when a commodity category changed from zero-rated to standard-rated (or vice versa) by querying multiple dates. -
Excise Duty Verification
Validate excise duty rates and calculation types (percentage vs. unit-based) for historical excisable goods. -
Compliance Reporting
Generate reports showing tax treatment changes over time for specific commodity categories. -
Dispute Resolution
Provide evidence of applicable tax rules on a specific date when disputing tax assessments.
Integration Checklistβ
β
Validate type is either 1 (Category) or 2 (Excise) before calling
β
Ensure issueDate format is yyyy-MM-dd HH:mm:ss
β
Verify categoryCode exists via T123/T125 before querying historical data
β
Handle cases where category/excise did not exist on the specified date
β
Parse commodityCategory vs exciseDuty response objects based on type
β
Check enableStatusCode to confirm category was active on the query date
β
Validate zeroRateStartDate/EndDate and exemptRateStartDate/EndDate for temporal validity
β
Cache historical query results to reduce API calls for repeated audit checks
β
Log issueDate and categoryCode queries for audit trail compliance
β
Cross-reference rate with T115 dictionary for tax rate code validation