DeviZones
Account Desk 8 min read

ZRA Smart Invoice Error 901: "It Is Not a Valid Device"

DZ

Devi Zones

ZRA Smart Invoice Error 901: "It Is Not a Valid Device"

You have your TPIN, your branch ID and your device serial. You press Initialise, and the VSDC answers:

{ "resultCd": "901", "resultMsg": "It is not a valid device" }

Nothing else happens. No device key, no SDC ID, no way forward.

901 means one thing: the ZRA backend your VSDC is talking to has no record of a device matching the serial, TPIN and branch ID you sent. It is not a network fault, and it is almost never a bug in your billing software. Either something in that identity triplet does not match what ZRA has registered — or you are asking the wrong ZRA.

Here are the four causes, in the order we have actually run into them.

1. Your device is registered in one environment, your VSDC points at the other

This is the most common cause and the most confusing one, because everything you typed is correct.

You need the architecture first. There are three layers:

Your billing software → your VSDC → ZRA

The VSDC is a Java application (a .war file) that ZRA supplies and that you deploy on your own server under Tomcat. Your billing software never talks to ZRA directly. It talks to your local VSDC at an address like http://your-server:8080/zraprodvsdc, and the VSDC forwards to ZRA.

Here is the part that catches people: which ZRA the VSDC forwards to is baked into the WAR file — it is not a setting you control. Inside the WAR, at WEB-INF/classes/application.properties, there is a line:

api.external.domain=https://smartinvoiceapi.zra.org.zm

That is the production build. The sandbox build has:

api.external.domain=https://sandboxapi.zra.org.zm

ZRA ships these as separate downloads. They are different files. You cannot turn a production VSDC into a sandbox one by editing that property or flipping a profile flag — we have gone through the builds, and the production WAR contains no sandbox URL anywhere.

So the failure is this: you register a test device on the sandbox portal, then initialise it against a VSDC running the production WAR. The production ZRA API has never heard of that device. 901.

It works in reverse too. A production device initialised against a sandbox VSDC gets the same 901.

How to check

The Tomcat context — the last part of your VSDC URL — is simply the WAR's filename:

| WAR file | URL context | Forwards to | |---|---|---| | zraprodvsdc.war | /zraprodvsdc | Production — smartinvoiceapi.zra.org.zm | | the sandbox build | its own context | Sandbox — sandboxapi.zra.org.zm |

If you are unsure, read the property straight out of the deployed application:

grep api.external.domain \
  /path/to/tomcat/webapps/<context>/WEB-INF/classes/application.properties

Then ask the only question that matters: is that the same portal where I registered this device? If not, you have found your 901.

The fix

Point at the matching build. Registered on the sandbox portal? Deploy the sandbox WAR and initialise against that. When you are ready for real invoices, register the device on the production portal and initialise against the production WAR.

These are two separate device registrations. A sandbox device does not graduate to production.

One useful detail: you can run both WARs on the same Tomcat at the same time. They deploy under different contexts and do not conflict. That is the sane setup — test against sandbox, go live against production, no redeployment in between. It is how we run it.

2. Invisible whitespace in the device serial

This one cost us a day, and it is worth knowing about because nothing on screen will show it to you.

A device would not initialise. The serial in the config looked exactly right. We compared it against the portal character by character. Identical.

It was not. We dumped the stored value as hex:

SELECT encode(convert_to(dvc_srl_no, 'UTF8'), 'hex') FROM zra_branches WHERE id = 1;

The string ended in 09. Hex 09 is a tab character. The serial was 16 characters, not 15 — someone had pressed Tab while the field had focus, and the tab went in with it. Pasting from a spreadsheet cell does the same thing.

ZRA matches the serial byte for byte. A trailing tab makes it a different serial, and a serial ZRA has never seen gets 901.

Spaces, tabs and newlines are all invisible in an input box and all fatal. If your serial looks right and you have ruled out the environment, check its actual bytes.

The permanent fix is to strip whitespace before it is ever sent, and to save the cleaned value back, so a serial that is already corrupted repairs itself on the next attempt. In Account Desk we do this everywhere the serial can enter the system — on typing, on paste, on save, and again immediately before the call:

const dvcSrlNo = branch.dvcSrlNo.replace(/\s+/g, '');

If you are writing your own integration, do the same for dvcSrlNo, bhfId and tpin. It costs nothing and removes an entire category of support ticket.

3. The serial is case-sensitive

1002345678_VSDC and 1002345678_vsdc are two different devices as far as ZRA is concerned, and only one of them exists.

This bites when a serial gets retyped instead of copied, or passes through a system that lower-cases identifiers on the way. If you have several devices with suffixes like _VSDC, _VSDC3, _VSDC4, check the case of every character — not just the digits.

4. The branch ID is wrong

bhfId is the branch identifier, and ZRA's head-office or main branch is 000 — not 001.

If you are a single-location business, or this is your first branch, your device is almost certainly registered under 000. We have seen setups configured with 001 purely because it looks like "the first one". ZRA has no device at 001 for that taxpayer, so: 901.

Check the branch ID against what the Smart Invoice portal shows for the device. Do not guess it.

Diagnose it in one step instead of four

Guessing is slow. Look at the exact bytes you sent and the exact answer ZRA gave.

Whatever software you are using, find the request log — the raw JSON of the initializer/selectInitInfo call and its response. (In Account Desk that is the API inspector: turn capture on, press Verify, and request and response appear side by side.)

You are looking at three fields:

{ "tpin": "1002345678", "bhfId": "000", "dvcSrlNo": "1002345678_VSDC" }

Compare each against the portal, and check the serial for stray whitespace. If all three match and you still get 901, you are in cause #1 — you are asking the wrong ZRA.

The short version

| Symptom | Likely cause | |---|---| | Everything matches the portal exactly | Environment mismatch — sandbox device against production VSDC, or the reverse | | Serial looks right but will not initialise | Invisible tab or space in the serial — check the bytes | | Several devices, only one works | Case mismatch in the serial suffix | | First branch, new setup | bhfId should probably be 000, not 001 |

901 is always an identity mismatch. It never means your invoice data is wrong, and it never means Smart Invoice is down.

Related

If initialisation returns 902 "This device is installed" instead, that is a different situation with one important consequence — see ZRA Smart Invoice error 902. If initialisation looks fine but every other call returns 899, see ZRA VSDC error 899.


If you are setting up ZRA Smart Invoice and would rather not spend a week on device errors, Account Desk connects to Smart Invoice (VSDC) directly — a sale is fiscalised as you bill it, and the ZRA receipt number, SDC ID and QR code print on the invoice. Talk to our team about your Smart Invoice setup, or book a demo.

Frequently asked questions

What does resultCd 901 mean on ZRA Smart Invoice? +

901 is “It is not a valid device”. The ZRA backend your VSDC forwards to has no device registered matching the TPIN, branch ID (bhfId) and device serial you sent. It is an identity mismatch, not a network or data problem.

Why do I get 901 when the device serial is definitely correct? +

The most common cause is an environment mismatch: the device is registered on the sandbox portal but your VSDC runs the production WAR (or the reverse). The ZRA backend is baked into the WAR file, so a sandbox device cannot initialise against a production VSDC. The second most common cause is invisible whitespace — a tab or space pasted into the serial — because ZRA matches the serial byte for byte.

Can I switch my VSDC from production to sandbox in a config file? +

No. ZRA ships separate WAR builds for sandbox and production, and the ZRA API address is baked into each build. Editing the property or changing a profile flag will not repoint it. Deploy the WAR that matches the portal where the device is registered. You can run both WARs on one Tomcat under different contexts.

Should bhfId be 000 or 001? +

ZRA's head-office or main branch is 000. A single-location business or a first branch is almost always registered under 000, not 001. Check the device against the Smart Invoice portal rather than assuming.