Goods Registration
Before any item can be sold or stocked, it must be registered with EFRIS. Registration tells URA what you sell, how it is measured, how it is taxed, and how it is packaged. Every scenario below uses the same endpoint:
The body wraps one or more goods in a goods array:
{
"goods": [ { "...": "see scenarios below" } ]
}
Fields required for every good
| Field | Description |
|---|---|
operationType |
"101" = add new, "102" = modify existing |
goodsName |
Good/service name (max 200 chars) |
goodsCode |
Your unique SKU (max 50 chars) |
measureUnit |
Unit code from the rateUnit dictionary (see
Data Lookups)
|
currency |
Currency code from the currencyType dictionary (e.g. "101" = UGX) |
commodityCategoryId |
Commodity classification (see commodity categories) |
haveExciseTax |
"101" = Yes, "102" = No |
havePieceUnit |
"101" = Yes, "102" = No |
haveCustomsUnit |
"101" = Yes, "102" = No |
stockPrewarning |
Low-stock alert threshold (can be "0") |
Optional: unitPrice (may be empty for services), description,
goodsTypeCode ("101" Goods (default), "102" Fuel),
haveOtherUnit, exciseDutyCode, piece-unit fields, customs-unit fields.
A. Standard good
The simplest case: no piece unit, no excise duty, no customs unit.
{
"goods": [
{
"operationType": "101",
"goodsName": "Premium Water 500ml",
"goodsCode": "SKU-BOTTLE-01",
"measureUnit": "101",
"unitPrice": "100000",
"currency": "101",
"commodityCategoryId": "22011000",
"haveExciseTax": "102",
"havePieceUnit": "102",
"haveCustomsUnit": "102",
"stockPrewarning": "10"
}
]
}
haveExciseTax = "102", exciseDutyCode must be
empty (EFRIS error 670).Rule: when
havePieceUnit = "102", all four piece fields
(pieceMeasureUnit, pieceUnitPrice, packageScaledValue,
pieceScaledValue) must be empty, and haveOtherUnit must be
"102".
B. Piece (multi-unit) packaging
EFRIS uses scaling to handle bulk vs retail units - for example a crate that contains 24 bottles. When
havePieceUnit = "101", all four piece fields become mandatory:
| Field | Meaning |
|---|---|
packageScaledValue |
The base "outer" unit multiplier (usually "1") |
pieceScaledValue |
How many inner units fit in one outer unit (e.g. "24" = 24 bottles per crate) |
pieceMeasureUnit |
Unit code for the inner unit (from the rateUnit dictionary) |
pieceUnitPrice |
Selling price of one inner unit |
{
"goods": [
{
"operationType": "101",
"goodsName": "Premium Water Crate (24x500ml)",
"goodsCode": "SKU-CRATE-01",
"measureUnit": "104", // Crate (outer unit)
"unitPrice": "2400000", // Price per crate
"currency": "101",
"commodityCategoryId": "22011000",
"haveExciseTax": "102",
"havePieceUnit": "101",
"pieceMeasureUnit": "101", // Bottle (inner unit)
"pieceUnitPrice": "100000", // 2400000 / 24
"packageScaledValue": "1",
"pieceScaledValue": "24", // 24 bottles per crate
"haveCustomsUnit": "102",
"stockPrewarning": "10"
}
]
}
C. Percentage-based excise duty
If an item attracts excise duty, set haveExciseTax = "101" and provide the official URA
exciseDutyCode (query codes via
the excise duty lookup). For
percentage-rated codes (rate type 101), piece units are only needed if the good
naturally uses multi-unit packaging.
{
"goods": [
{
"operationType": "101",
"goodsName": "Bell Lager 500ml",
"goodsCode": "BELL-500",
"measureUnit": "102", // Litre
"unitPrice": "4720",
"currency": "101",
"commodityCategoryId": "22030010",
"haveExciseTax": "101",
"exciseDutyCode": "LED190100", // Beer (percentage-based)
"havePieceUnit": "102",
"haveCustomsUnit": "102",
"stockPrewarning": "10"
}
]
}
D. Unit-based excise duty (EFRIS error 676)
For excise codes rated per unit of measurement (rate type 102, e.g. Mineral Water
at UGX 150 per Litre), different rules apply:
havePieceUnitMUST be"101". If set to"102", registration fails with EFRIS error676: "haveExciseTax is '101', excise duty has unit of measurement, 'havePieceUnit' must be '101'!"pieceMeasureUnitmust match the excise duty unit (e.g."102"Litre,"103"Kilogram,"106"1,000 sticks).- If
measureUnitequals the excise duty's unit,packageScaledValueandpieceScaledValuemust both equal"1". pieceUnitPrice= unit price divided by the scale factor (equalsunitPricefor a single-unit good).
{
"goods": [
{
"operationType": "101",
"goodsName": "Mineral Water 1L",
"goodsCode": "WATER-LITRE",
"measureUnit": "102", // Litre
"unitPrice": "2000",
"currency": "101",
"commodityCategoryId": "50202310",
"haveExciseTax": "101",
"exciseDutyCode": "LED190400", // Mineral Water (UGX 150 per Litre)
"havePieceUnit": "101", // Required because the rate has a unit (per Litre)
"pieceMeasureUnit": "102", // Litre - must match the excise unit
"pieceScaledValue": "1", // Must be 1 when measureUnit = excise unit
"packageScaledValue": "1", // Must be 1 when measureUnit = excise unit
"pieceUnitPrice": "2000", // Same as unitPrice for single-unit scaling
"haveCustomsUnit": "102",
"stockPrewarning": "10"
}
]
}
E. Registering services
For services, unitPrice may be empty (or omitted) and stockPrewarning can be
"0" - services carry no stock.
{
"goods": [
{
"operationType": "101",
"goodsName": "Consulting Services",
"goodsCode": "SVC-CONSULT-01",
"measureUnit": "112", // e.g. per service/hour - see the rateUnit dictionary
"unitPrice": "5000000",
"currency": "101",
"commodityCategoryId": "73110000",
"haveExciseTax": "102",
"havePieceUnit": "102",
"haveCustomsUnit": "102",
"stockPrewarning": "0"
}
]
}
F. Additional selling units
To sell the same good in more than one unit (beyond the piece unit), set haveOtherUnit = "101"
and supply goodsOtherUnits:
{
"haveOtherUnit": "101",
"goodsOtherUnits": [
{
"otherUnit": "AF", // rateUnit code - cannot equal measureUnit or pieceMeasureUnit
"otherPrice": "1000",
"otherScaled": "10",
"packageScaled": "1"
}
]
}
otherUnit cannot equal measureUnit; if
havePieceUnit = "101", otherUnit also cannot equal pieceMeasureUnit. If
havePieceUnit = "102", haveOtherUnit must be "102".
G. Customs units (exporters)
If the good is exported and needs a customs unit of measure, set haveCustomsUnit = "101" and
supply the customs block. Unit codes come from the exportRateUnit dictionary.
{
"haveCustomsUnit": "101",
"commodityGoodsExtendEntity": {
"customsMeasureUnit": "NTT",
"customsUnitPrice": "4",
"packageScaledValueCustoms": "1",
"customsScaledValue": "2.5"
}
}
commodityGoodsExtendEntity and customsUnitList are
mutually exclusive. If your good has more than one customs unit, send only
customsUnitList (same fields, as an array) and remove commodityGoodsExtendEntity.
H. Modifying existing goods
To update a registered good, use "operationType": "102" with the same goodsCode.
Using "101" on an existing code will return an EFRIS error.
| Code | Meaning |
|---|---|
101 |
New registration |
102 |
Update existing |
What comes next
Once a good is registered, load its opening stock before selling it - see
Stock Management. When it appears on an invoice, the
itemCode must match the registered goodsCode exactly - see
Fiscal Invoices.