Enrolment tokens
What an enrolment token is, how long it lasts, where the installer reads it from, how to tell an install worked, and every failure message you might see.
An enrolment token is the one-time coupon a freshly installed sensor redeems to join your organisation. It is not the credential the sensor uses afterwards. Getting those two things straight is most of what there is to understand here, and confusing them is the most common support ticket this part of the product produces.
This article covers the format and lifetime of a token, where to create one, how the installer receives it, how to confirm an install actually worked, and the exact text of every failure you might see. It also names two error messages that do not exist, because looking for a message that was never written is a bad afternoon.
Two different secrets
| Enrolment token | Sensor token | |
|---|---|---|
| Created by | You, in the console | The platform, as the result of enrolment |
| Looks like | zqe_ followed by 64 hex characters | Bare hex, no prefix |
| Scope | One organisation, an expiry, optionally a product, a platform and a use cap | Exactly one machine |
| May do | Create one sensor, then it is spent or expires | Send telemetry and collect response tickets |
| May never | Authenticate telemetry | Enrol anything |
The zqe_ prefix is not decoration. It is how the sensor works out, from its own config file alone, whether it is holding a coupon it still has to redeem or a credential it can already use. That single character sequence is what makes deferred enrolment possible.
Format, lifetime and caps
- Format:
zqe_plus 64 hexadecimal characters, from 32 random bytes. - Only the hash is stored. The plaintext token exists exactly once, in the response that creates it, and is never recoverable afterwards. The console shows you the first twelve characters —
zqe_and eight more — so you can tell two tokens apart in a list. - Default lifetime: 7 days.
- Maximum lifetime: 90 days.
- Optional use cap. Unset means unlimited. Set it to the number of machines in the batch.
- Optional product,
sensororpam-agent, defaulting tosensor. - Optional platform, one of
any,windows,linuxordarwin, defaulting toany.
Ask for a longer life than 90 days and the request is refused, not quietly shortened:
ttl_seconds may not exceed 7776000 (90 days) — enrolment tokens are short-lived by design
Refusing is the deliberate choice. Someone who asked for a year has made an assumption about how this works; issuing them a 90-day token instead would let them discover the difference three months later, when their fleet stops enrolling and nobody remembers why.
One consequence of how scope is enforced is worth knowing before you narrow a token: the product and platform restrictions are checked after a use is counted. That is on purpose, so nobody can probe a token to learn what it is scoped to without spending one. It does mean a Windows-scoped token typed into a Linux install burns a use and then fails.
Where to get one
In the console: Settings -> Endpoints -> Enrolment tokens. Creating one returns the plaintext token together with a ready-made install command for each platform, with the token, the download URL and your API URL already substituted. That response is the only place the full token ever appears.
The same operations exist on the API for anyone automating a rollout. They are ordinary tenant-scoped routes and they need the endpoint permissions from your role — a role without them gets a 403 and the message insufficient permissions. See Roles and permissions.
| Route | Needs | Purpose |
|---|---|---|
POST /api/v1/organizations/{orgID}/xdr/enrollment-tokens | xdr:endpoints.write | Create a token |
GET /api/v1/organizations/{orgID}/xdr/enrollment-tokens | xdr:endpoints.read | List tokens |
POST /api/v1/organizations/{orgID}/xdr/enrollment-tokens/{tokenID}/revoke | xdr:endpoints.write | Revoke a token |
GET /api/v1/organizations/{orgID}/xdr/enrollment-tokens/{tokenID}/sensors | xdr:endpoints.read | Did the install work? |
POST /api/v1/xdr/sensor/enroll | The zqe_ token itself, as a bearer token | What the installer calls |
That last route carries no organisation in its path, and that is a security property rather than an oversight. The tenant comes from the token. Taking the organisation from the URL would let anyone holding any valid token enrol into any tenant they could name.
How the installer receives the token
Windows uses hidden MSI properties supplied through the installer API, followed by standard input to the native enrollment command. The generated Windows command collects the token at a masked prompt. Linux installers accept environment variables or a protected token file.
| Variable | Read by | Meaning |
|---|---|---|
QX_ENROLL_TOKEN (or ZQ_ENROLL_TOKEN) | Linux .deb / .rpm and macOS package scripts | Enrolment token at install time |
QX_API_URL (or ZQ_API_URL) | the same scripts | API base URL. Use the console origin you were given at enrolment, e.g. https://app.qorionix.com |
SENSOR_ENROLL_TOKEN | the Windows MSI, as a property | Windows equivalent of QX_ENROLL_TOKEN |
SENSOR_API_URL | the Windows MSI, as a property | Windows equivalent of QX_API_URL |
QX_ENROLLMENT_TOKEN (or ZQ_ENROLLMENT_TOKEN) | the running service | Run-time credential override, not an install-time input |
QX_CONFIG_PATH (or ZQ_CONFIG_PATH) | the running service | Overrides the compiled-in config path |
QX_SERVER_URL (or ZQ_SERVER_URL) | the running service | Overrides api_url |
QX_SENSOR_ID (or ZQ_SENSOR_ID) | the running service | Overrides the sensor identity |
On two of the three platforms an environment variable is not a dependable delivery mechanism. apt and dnf do not reliably forward the invoking environment to package maintainer scripts, and a macOS package opened from Finder runs its scripts with no useful environment at all. That is why file-based input exists:
/etc/qorionix/enrollment-token (mode 0600)
/etc/qorionix/api-urlBoth are whitespace-stripped when read, so a trailing newline or a CRLF from an MDM-pushed file is handled rather than becoming a mystifying rejection. The token file is deleted once enrolment reaches a terminal outcome, and kept where a corrected re-run would still need it.
What ends up in the config file
Enrolment writes the machine's config file in full — it is rendered, not merged. The file is written atomically at mode 0600, because the running service reads it and a torn write during a repair install would otherwise leave the agent holding half a token. On Linux the package then relaxes it to root:qorionix-sensor mode 0640.
| Key | What it holds |
|---|---|
api_url | The API base URL. |
enrollment_token | This machine's credential once enrolment succeeds. Treat as a secret. |
sensor_id | The machine's identity in the platform. Present only after enrolment completes. |
organization_id | Echoed back so you can see which tenant the machine joined. |
tls_insecure_skip_verify | Test rigs only. Not for production. |
The shipped default config also contains a channels: block, poll_interval_seconds, cache_path and log_path. No code reads any of them. They look like tuning knobs and they are not, and we would rather say so plainly than have you spend an afternoon adjusting a number nothing consumes.
How enrolment actually works
The installer posts the machine's hostname, platform, architecture, sensor version and a stable host fingerprint to POST /api/v1/xdr/sensor/enroll, carrying the zqe_ token as a bearer token. A successful enrolment answers 201 Created with the sensor id, the machine's own credential, the organisation id, and a reenrolled flag.
The fingerprint is derived from the machine's machine-id where one exists, otherwise its lowest non-loopback MAC address, otherwise just the hostname. It is deliberately stable across reinstalls, because it is the key that decides whether a repair install produces the same endpoint or a duplicate one.
The installer's exit code is a contract, not incidental, and both the Linux package scripts and the MSI branch on it:
| Code | Meaning | What happens next |
|---|---|---|
0 | Enrolled | Nothing to do. |
2 | Token empty or malformed | Enrolment was not attempted. Fix the token and re-run. |
3 | Token rejected | Fatal. A human must issue a new token. |
4 | Network or server problem | Config is stamped; the service will keep retrying. |
5 | Could not write the config file | A local permissions or disk problem. |
A network failure is not fatal and a credential failure is, because no amount of retrying fixes a token that has been spent — and silently installing a sensor that can never enrol is the single worst outcome for an admin with no security team. Note that the package scripts themselves always exit 0 regardless, so a clean apt-get or a green MSI does not prove enrolment happened.
Where the installer could not reach the API, the token is stamped into the config and the service finishes the job on first start, retrying after 5 seconds, 30 seconds, 2 minutes, 10 minutes and 30 minutes, then every 30 minutes indefinitely. Only the first five failures are logged; past that, the endpoint's absence from your console is the signal you will actually act on. A credential rejection stops the loop at once.
Confirming an install worked
Ask the token, not the machine. GET /api/v1/organizations/{orgID}/xdr/enrollment-tokens/{tokenID}/sensors returns the token's use count, its cap, the sensors it created, and two separate counters that are the whole point of the endpoint:
- `awaiting_first_event`
- A sensor row exists — the token was spent — but no telemetry has ever arrived from that machine. The install ran; the agent is not reporting.
- `checked_in`
- Telemetry has arrived. This machine is genuinely alive.
A sensor row proves only that the token was spent. Only a check-in proves the agent is running. Collapsing those two into one number would let a fleet of installed-but-dead agents read as a successful rollout. An unknown token id returns a 404 rather than an empty list, so a mistyped id cannot render a permanently cheerful waiting…. And use_count can legitimately exceed the number of sensors, because a machine that re-enrols spends another use without creating another endpoint.
The failure messages, verbatim
Six token failures come back as 401, one per cause:
enrolment token not recognised
enrolment token has been revoked
enrolment token has expired
enrolment token has reached its maximum number of uses
enrolment token was not issued for this product
enrolment token was not issued for this platformMost of them arrive with the detail attached, which is usually what tells you what to do:
enrolment token has been revoked (revoked 2026-09-01T10:22:14Z)
enrolment token has expired (expired 2026-08-25T09:00:00Z)
enrolment token has reached its maximum number of uses (3 of 3 used)
enrolment token not recognised: token state changed during enrolment, please retry
enrolment token was not issued for this product: token was issued for pam-agent, this is the sensor enrolment endpoint
enrolment token was not issued for this platform: token is for windows, this host is linuxIf no token reached the installer at all, the answer names both variables so you do not have to guess which platform uses which:
no enrolment token supplied — pass one with SENSOR_ENROLL_TOKEN (Windows) or ZQ_ENROLL_TOKEN (Linux/macOS)
On the machine itself, the enroll command prints the same situations in longer form:
enroll: no enrolment token supplied.
Create one in the Qorionix console under Settings -> Endpoints -> Enrolment tokens,
then pass it with --token.
enroll: the Qorionix server rejected this enrolment token.
Reason: <detail>
Fix: issue a fresh token in the console under
Settings -> Endpoints -> Enrolment tokens, then reinstall.
enroll: could not reach <api> right now (<err>).
The agent is installed and will keep trying to enrol in the background.
If this host never appears in the console, check outbound HTTPS to <api>.And on success:
enroll: enrolled as sensor <id>
enroll: re-enrolled (this host was already known) as sensor <id>
enroll: config written to <path>
enroll: this machine should now appear under Endpoints in the Qorionix consoleTwo messages that do not exist
Both of these are things an experienced admin will search for. Neither string is in the product, so searching for them wastes time that this section is meant to save.
- There is no clock-skew error
- A machine whose clock is badly wrong fails TLS certificate validation before it ever reaches our enrolment logic, so the failure surfaces through the generic transient path:
enroll: could not reach <api> right now (<err>), with a Go TLS error inside the brackets, and exit code 4. The sensor will then retry on the usual backoff forever, because as far as it can tell this is a network problem. If a host reports an unreachable API but the network is demonstrably fine, check the system clock and the time source before anything else. - There is no duplicate-host error
- Enrolling a machine that is already known is not an error and produces no warning. The existing endpoint is updated in place — hostname, platform, architecture refreshed, any revocation cleared — and the response is flagged as a re-enrolment, which the installer prints as
re-enrolled (this host was already known). You will not get a duplicate endpoint, and you will not get a message telling you it happened, beyond that one line. The token's use count still goes up.
Revoking a token
Revoking an enrolment token stops it creating any more endpoints. It does not disable the machines that already enrolled with it — each of those holds its own per-machine credential and is unaffected. To stop a specific machine reporting, revoke that sensor rather than the token it came from; a revoked sensor is refused at the door with sensor auth: sensor revoked.
The usual habit is one token per rollout batch, capped at the size of the batch, revoked when the batch is done.
What to do next
- Installing the sensor on Windows — the signed MSI and the exact silent command.
- Installing the sensor on Linux — the deb and rpm forms, including the token-file form for configuration management.
- The endpoint list and endpoint health — where an enrolled machine shows up, and what it means when it stops.