Verify and track
Declarants, importers and roadside officers do not have ITS accounts. Two public routes serve them, with no credential at all. You can build them into a national single window, a mobile app or a verification page.
What each route needs
The public routes are tiered by what the caller can prove, not by which application is calling:
| The caller proves | Who that is | Route | What they get |
|---|---|---|---|
| The printed verification code | Anyone who has seen the document, or a photo of it | GET /api/public/verify/{code} | Whether the document is genuine, and its public state |
| The serial number and a party's TIN | The declarant or the importer/consignee | GET /api/public/track/{serial_no} and /pdf | The corridor, the current holder, the guarantee verdict, tracking, and the printed leaflet |
| A bearer token | An administration | /etd under the gateway | Everything in its scope, every version |
Nothing public answers on a serial number alone. A serial encodes country, office and date, so an open route on it would let anyone walk the numbering.
Verify a printed document
GET /api/public/verify/{code}
The verification code is printed on the Transit Document and encoded in its QR code. It is 26 characters of Crockford base32, one per declaration, the same on every version. It is not guessable.
{
"status": "VALID",
"document_type": "ETD",
"issued_at": "2026-08-11T11:02:26.453195Z",
"document": {
"serial_no": "KEMBATD26081138",
"departure": { "country_code": "KE", "country_name": "Kenya" },
"destination": { "country_code": "UG", "country_name": "Uganda" },
"issued_at": "2026-08-11",
"state": "REGISTERED",
"version_no": 1,
"signature_status": "UNSIGNED",
"stamp_count": 1
}
}
status | HTTP | Meaning |
|---|---|---|
VALID | 200 | The document is genuine. Here is its public state. |
REVOKED | 410 | The document was withdrawn. Do not rely on the paper. |
UNKNOWN | 404 | No such code. Usually a typing mistake. |
- The code is case-insensitive, and the confusable characters
I,L,OandUare accepted for1and0. - The answer never carries names, values, HS codes, commodity descriptions, the guarantee, the corridor or where the goods are. A scan reveals no more than the paper already shows.
- Responses carry
Cache-Control: no-store.
Scanning the QR code
The QR code encodes a URL to a verification page, not a bare code. Take the last path segment as the code. Also accept a bare code, because older printed leaflets encode a different path.
Track a consignment
GET /api/public/track/{serial_no}
X-Party-Tin: <the declarant's or the consignee's TIN>
Only two TINs open a declaration:
- the declarant's TIN (TD field 34), the person who lodged the declaration;
- the importer/consignee's TIN (TD field 4).
The exporter's and the transporter's TINs are printed on the same document and are not accepted. Label the input "Declarant or consignee TIN".
Send the TIN in the X-Party-Tin header, never in the URL. Access logs record the request line, so a TIN in the path or the query would be written into every log between the caller and ITS.
{
"data": {
"serial_no": "UGEBBTD2608091",
"departure": { "country_code": "UG", "country_name": "Uganda" },
"destination": { "country_code": "KE", "country_name": "Kenya" },
"current_state": "IN_TRANSIT",
"stops": [
{
"stamp_type": "DEPARTURE",
"country_code": "UG",
"station_country": "UG",
"station_code": "EBB",
"next_country_code": "KE",
"received_at": "2026-08-11T14:02:26.45+03:00"
}
],
"current_holder": "UG",
"declared_next_country": "KE",
"versions": [
{
"version_no": 1,
"change_type": "ISSUANCE",
"created_at": "2026-08-11T14:02:26.45+03:00"
}
],
"guarantee_status": "VERIFIED",
"tracking": { "tracking_status": "NOT_TRACKED" }
}
}
The payload carries no names, no TINs, no commodity, no weight, no declared value and no guarantee amount or surety. guarantee_status is the verdict alone. tracking follows the same rules as for an administration: read tracking_status before position. See Reading and searching.
Every failure is the same 404
An unknown serial, a wrong TIN and the exporter's TIN instead of the consignee's all return one indistinguishable 404 (RES-ETD-043), in the same time. Do not guess which one happened. Say "We could not find a declaration matching those details."
A request with no X-Party-Tin header is 400 VAL-ETD-044.
Download the leaflet
GET /api/public/track/{serial_no}/pdf?leaflet=BOTH
X-Party-Tin: <the declarant's or the consignee's TIN>
- It is always the current version.
as_ofand version numbers are not accepted here. leaflet(I,IIorBOTH),langanddispositionwork as on the authenticated route. An unknown value is a400.- Because
X-Party-Tinis a custom header, a browser sends a CORS preflight, and the download cannot be a plain link. Fetch it and turn the response into a file. - Responses carry
Cache-Control: no-store. Do not cache the bytes and do not sendIf-None-Match.
Serial number input
For an input placeholder, use the real format: UGEBBTD2608091. Accept 14 to 18 characters, and also the 23-character form issued before 14 August 2026, such as UGEBBTD0000000120260809.
Limits
A route with no credential is a target for automated guessing. Expect the public routes to be rate-limited, and back off when refused.