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:

POST/register-good-or-service

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"
        }
    ]
}
Rule: when 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:

  • havePieceUnit MUST be "101". If set to "102", registration fails with EFRIS error 676: "haveExciseTax is '101', excise duty has unit of measurement, 'havePieceUnit' must be '101'!"
  • pieceMeasureUnit must match the excise duty unit (e.g. "102" Litre, "103" Kilogram, "106" 1,000 sticks).
  • If measureUnit equals the excise duty's unit, packageScaledValue and pieceScaledValue must both equal "1".
  • pieceUnitPrice = unit price divided by the scale factor (equals unitPrice for 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"
        }
    ]
}
Rules: 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"
    }
}
Note: 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.

Stuck on an integration step? Our engineers reply fast.