EFRIS API Integration Hub
Welcome to the developer portal for the EFRIS Multi-Tenant Integration Gateway. Our platform decouples your internal ERP or billing system from the complexities of the Uganda Revenue Authority (URA) EFRIS protocol.
Integrating directly with URA requires custom XML envelopes, digital signatures, AES-128-ECB handshake encryption, and active key expiry tracking. Our middleware wraps these complex steps into standard REST/JSON endpoints, allowing you to achieve compliance in hours instead of months.
Key Features
- Dynamic Key Handshakes: Automatically handles EFRIS handshake protocols (T101, T104, T103) in the background.
- Simple Format Abstraction: Submit invoices with gross, tax-inclusive prices and the middleware calculates net amounts, VAT, and EFRIS discount lines.
- Built-in Digital Signing: Securely signs request packages with your uploaded
.pfxcertificate. - Generic Passthrough Engine: Submit custom payloads directly to any EFRIS T-code interface while the middleware takes care of packaging, signing, and decryption.
📚 Resources & Downloads
Get started quickly with our comprehensive documentation and API collection.
Complete step-by-step guide with examples for all EFRIS endpoints including T109 invoicing, T130 products, T131 stock, and more.
⬇ Download MarkdownReady-to-use Postman collection with all API endpoints pre-configured. Import and start testing immediately with your API key.
⬇ Download JSON🎥 Integration Video Tutorials
Learn through step-by-step video guides from our development team.
Loading videos...
🔌 Authentication
All external API calls must include your company's API key. This key identifies the tenant account, whitelists the request IP, and loads the corresponding EFRIS certificate for signing.
Header Requirement:
X-API-Key: your_company_api_key_here
🧪 Sandbox & Testing
We provide a fully configured test environment so you can begin testing endpoints immediately without needing to set up a database company or configure certificates.
Use these credentials directly in Swagger UI or your requests:
- API Key (`X-API-Key`):
efris_mTGKiJaI8uCEi-NpT-_DuwM257cJeUDgA5YFPkhk6ew - Sandbox TIN:
1014409555(Use this TIN for seller/buyer fields during testing) - Mock Device Number:
1014409555_02
To test inside the interactive console, click the Authorize button in Swagger UI (/docs), paste the key efris_mTGKiJaI8uCEi-NpT-_DuwM257cJeUDgA5YFPkhk6ew, and click authorize.
📝 Submit Invoice (Simple Format)
The recommended approach for ERPs is the Simple Format. Under this mode (triggered by sending "format": "simple" in the payload), you only send the gross, tax-inclusive price, and the gateway automatically calculates EFRIS tax, net amounts, and required separate discount lines.
Choose your programming language below to view the request sample:
import requests
import json
url = "https://www.efrisintegration.nafacademy.com/api/external/efris/submit-invoice"
headers = {
"X-API-Key": "efris_mTGKiJaI8uCEi-NpT-_DuwM257cJeUDgA5YFPkhk6ew",
"Content-Type": "application/json"
}
payload = {
"format": "simple",
"invoiceNumber": "INV-2026-001",
"invoiceDate": "2026-05-29",
"customerName": "Excise Buyer Ltd",
"customerTin": "1000123456",
"buyerType": "0",
"paymentMethod": "102",
"items": [
{
"item_name": "Premium Beer",
"item_code": "BEER-01",
"quantity": 10,
"unit_price": 5000, # Gross price (inclusive of VAT & Excise)
"tax_rate": 18,
"excise_flag": "1",
"excise_rule": "1",
"excise_rate": "30%",
"excise_tax": 9000
}
]
}
response = requests.post(url, headers=headers, json=payload)
print(json.dumps(response.json(), indent=2))
use Illuminate\Support\Facades\Http;
$response = Http::withHeaders([
'X-API-Key' => 'efris_mTGKiJaI8uCEi-NpT-_DuwM257cJeUDgA5YFPkhk6ew',
'Content-Type' => 'application/json'
])->post('https://www.efrisintegration.nafacademy.com/api/external/efris/submit-invoice', [
'format' => 'simple',
'invoiceNumber' => 'INV-2026-001',
'invoiceDate' => '2026-05-29',
'customerName' => 'Excise Buyer Ltd',
'customerTin' => '1000123456',
'buyerType' => '0',
'paymentMethod' => '102',
'items' => [
[
'item_name' => 'Premium Beer',
'item_code' => 'BEER-01',
'quantity' => 10,
'unit_price' => 5000,
'tax_rate' => 18,
'excise_flag' => '1',
'excise_rule' => '1',
'excise_rate' => '30%',
'excise_tax' => 9000
]
]
]);
print_r($response->json());
const axios = require('axios');
const url = 'https://www.efrisintegration.nafacademy.com/api/external/efris/submit-invoice';
const headers = {
'X-API-Key': 'efris_mTGKiJaI8uCEi-NpT-_DuwM257cJeUDgA5YFPkhk6ew',
'Content-Type': 'application/json'
};
const payload = {
format: 'simple',
invoiceNumber: 'INV-2026-001',
invoiceDate: '2026-05-29',
customerName: 'Excise Buyer Ltd',
customerTin: '1000123456',
buyerType: '0',
paymentMethod: '102',
items: [
{
item_name: 'Premium Beer',
item_code: 'BEER-01',
quantity: 10,
unit_price: 5000,
tax_rate: 18,
excise_flag: '1',
excise_rule: '1',
excise_rate: '30%',
excise_tax: 9000
}
]
};
axios.post(url, payload, { headers })
.then(response => console.log(response.data))
.catch(error => console.error(error));
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "efris_mTGKiJaI8uCEi-NpT-_DuwM257cJeUDgA5YFPkhk6ew");
var json = @"{
""format"": ""simple"",
""invoiceNumber"": ""INV-2026-001"",
""invoiceDate"": ""2026-05-29"",
""customerName"": ""Excise Buyer Ltd"",
""customerTin"": ""1000123456"",
""buyerType"": ""0"",
""paymentMethod"": ""102"",
""items"": [
{
""item_name"": ""Premium Beer"",
""item_code"": ""BEER-01"",
""quantity"": 10,
""unit_price"": 5000,
""tax_rate"": 18,
""excise_flag"": ""1"",
""excise_rule"": ""1"",
""excise_rate"": ""30%"",
""excise_tax"": 9000
}
]
}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://www.efrisintegration.nafacademy.com/api/external/efris/submit-invoice", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
}
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
url := "https://www.efrisintegration.nafacademy.com/api/external/efris/submit-invoice"
jsonStr := []byte(`{
"format": "simple",
"invoiceNumber": "INV-2026-001",
"invoiceDate": "2026-05-29",
"customerName": "Excise Buyer Ltd",
"customerTin": "1000123456",
"buyerType": "0",
"paymentMethod": "102",
"items": [
{
"item_name": "Premium Beer",
"item_code": "BEER-01",
"quantity": 10,
"unit_price": 5000,
"tax_rate": 18,
"excise_flag": "1",
"excise_rule": "1",
"excise_rate": "30%",
"excise_tax": 9000
}
]
}`)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
req.Header.Set("X-API-Key", "efris_mTGKiJaI8uCEi-NpT-_DuwM257cJeUDgA5YFPkhk6ew")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
import okhttp3.*;
import java.io.IOException;
public class EfrisClient {
public static void main(String[] args) throws IOException {
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\n \"format\": \"simple\",\n \"invoiceNumber\": \"INV-2026-001\",\n \"invoiceDate\": \"2026-05-29\",\n \"customerName\": \"Excise Buyer Ltd\",\n \"customerTin\": \"1000123456\",\n \"buyerType\": \"0\",\n \"paymentMethod\": \"102\",\n \"items\": [\n {\n \"item_name\": \"Premium Beer\",\n \"item_code\": \"BEER-01\",\n \"quantity\": 10,\n \"unit_price\": 5000,\n \"tax_rate\": 18,\n \"excise_flag\": \"1\",\n \"excise_rule\": \"1\",\n \"excise_rate\": \"30%\",\n \"excise_tax\": 9000\n }\n ]\n}");
Request request = new Request.Builder()
.url("https://www.efrisintegration.nafacademy.com/api/external/efris/submit-invoice")
.post(body)
.addHeader("X-API-Key", "efris_mTGKiJaI8uCEi-NpT-_DuwM257cJeUDgA5YFPkhk6ew")
.addHeader("Content-Type", "application/json")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
}
}
require 'net/http'
require 'uri'
require 'json'
uri = URI.parse("https://www.efrisintegration.nafacademy.com/api/external/efris/submit-invoice")
request = Net::HTTP::Post.new(uri)
request["X-API-Key"] = "efris_mTGKiJaI8uCEi-NpT-_DuwM257cJeUDgA5YFPkhk6ew"
request["Content-Type"] = "application/json"
request.body = JSON.dump({
"format" => "simple",
"invoiceNumber" => "INV-2026-001",
"invoiceDate" => "2026-05-29",
"customerName" => "Excise Buyer Ltd",
"customerTin" => "1000123456",
"buyerType" => "0",
"paymentMethod" => "102",
"items" => [
{
"item_name" => "Premium Beer",
"item_code" => "BEER-01",
"quantity" => 10,
"unit_price" => 5000,
"tax_rate" => 18,
"excise_flag" => "1",
"excise_rule" => "1",
"excise_rate" => "30%",
"excise_tax" => 9000
}
]
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts response.body
import 'package:dio/dio.dart';
void main() async {
var dio = Dio();
dio.options.headers["X-API-Key"] = "efris_mTGKiJaI8uCEi-NpT-_DuwM257cJeUDgA5YFPkhk6ew";
dio.options.headers["Content-Type"] = "application/json";
var payload = {
"format": "simple",
"invoiceNumber": "INV-2026-001",
"invoiceDate": "2026-05-29",
"customerName": "Excise Buyer Ltd",
"customerTin": "1000123456",
"buyerType": "0",
"paymentMethod": "102",
"items": [
{
"item_name": "Premium Beer",
"item_code": "BEER-01",
"quantity": 10,
"unit_price": 5000,
"tax_rate": 18,
"excise_flag": "1",
"excise_rule": "1",
"excise_rate": "30%",
"excise_tax": 9000
}
]
};
try {
var response = await dio.post(
"https://www.efrisintegration.nafacademy.com/api/external/efris/submit-invoice",
data: payload,
);
print(response.data);
} catch (e) {
print(e);
}
}
Expected Successful Response Schema
The endpoint returns both the nested fullEfrisResponse (as per our integration guide) and flat convenience fields at the top level for backward compatibility:
{
"success": true,
"message": "Invoice successfully submitted to EFRIS",
"fdn": "FDN-123456",
"verificationCode": "VER-1234",
"qrCode": "https://ura.go.ug/qr/123",
"fiscalized_at": "2026-05-29T21:00:00Z",
"invoiceNumber": "INV-2026-001",
"fullEfrisResponse": {
"fdn": "FDN-123456",
"verification_code": "VER-1234",
"qr_code": "https://ura.go.ug/qr/123",
...
}
}
🧮 Excise Duty Calculation Math
Uganda EFRIS regulations dictate specific mathematical structures for tax details when Excise Duty (Category 05) is applied to items alongside Standard VAT (Category 01).
- VAT Base (VAT netAmount) = Total Gross / 1.18
- VAT Tax = Total Gross - VAT Base
- VAT Gross = Total Gross
- Excise netAmount = VAT Base - Total Excise Tax
- Excise taxAmount = Total Excise Tax
- Excise grossAmount = VAT Base
The gateway automatically handles this breakdown. If your ERP passes an item priced at 50,000 gross with 9,000 excise tax, the gateway computes the tax details as follows:
| Category | Code | Net Amount | Tax Amount | Gross Amount |
|---|---|---|---|---|
| Standard VAT | 01 |
42,372.88 |
7,627.12 |
50,000.00 |
| Excise Duty | 05 |
33,372.88 |
9,000.00 |
42,372.88 |
⚡ Generic Passthrough Engine
If you need to call specialized EFRIS T-code interfaces (e.g. daily reports, branch listings, negative stock settings) that don't have dedicated REST endpoints, you can use the **Generic Passthrough Engine**.
Endpoint: POST /api/external/efris/passthrough/{interface_code}
The gateway automatically loads keys, handles handshake keys (T104), encrypts/decrypts the body via AES-128, and wraps/signs the request using your digital certificate. You only pass the raw content JSON.
Example Passthrough Request (T125 Query Excise Duty)
POST /api/external/efris/passthrough/T125?encrypt_code=0
Header: X-API-Key: efris_mTGKiJaI8uCEi-NpT-_DuwM257cJeUDgA5YFPkhk6ew
{
"exciseDutyCode": "",
"exciseDutyName": "beer"
}
📦 Products & Inventory
Before an item can be fiscalized, it must be registered with EFRIS. We provide endpoints to register products, adjust stock quantities (stock increases/decreases), and query commodity categories.
Multi-Unit / Packaging Magic
If you sell bulk packages (e.g. Boxes, Crates) but manage inventory in sub-units (e.g. Sticks, Bottles), you can specify packaging scales during registration and invoice submissions. Use package_scaled, piece_scaled, and piece_qty fields in the item lists as described in the API schemas.
🗂️ Endpoint Directory
Below is a directory of the EFRIS integration endpoints. Click any route to explore it interactively in the Swagger Console.
| Method | Path | EFRIS Code | Description |
|---|---|---|---|
| POST | /api/external/efris/submit-invoice |
T109 |
Submit & fiscalize invoice (Simple/EFRIS formats) |
| POST | /api/external/efris/submit-credit-note |
T110 |
Apply for credit notes / debit notes reversals |
| POST | /api/external/efris/passthrough/{interface_code} |
T101-T187 |
Run any arbitrary EFRIS request dynamically |
| POST | /api/external/efris/register-product |
T130 |
Register goods/services in URA EFRIS catalog |
| POST | /api/external/efris/stock-increase |
T131 |
Increment EFRIS stock balance (maintain records) |
| POST | /api/external/efris/stock-decrease |
T131 |
Decrement EFRIS stock balance (maintain records) |
| GET | /api/external/efris/server-time |
T101 |
Check URA clock sync & connection health |
| GET | /api/external/efris/registration-details |
T102 |
Get company's profile registration details |
| GET | /api/external/efris/taxpayer/{tin} |
T119 |
Look up taxpayer legal info by TIN from URA |
| GET | /api/external/efris/commodity-categories |
T124 |
List valid EFRIS category codes (paginated) |
| GET | /api/external/efris/credit-notes |
T112 |
Query applied credit notes list and approval status |
| GET | /api/external/efris/excise-duty |
T125 |
List valid excise duty codes and rate types |
| GET | /api/external/efris/goods |
T127 |
List company's registered goods catalog |
| GET | /api/external/efris/invoice/{invoice_number} |
T108 |
Retrieve fiscal FDN / QR code details for invoice |
🚀 Explore the Interactive Documentation Console
Authorize with your key and try requesting EFRIS servers live using our Swagger console explorer.
Open Swagger Console (/docs)