XCAICX docs Product Contact

Air-gapped deployment

Cameras that will never reach the internet. A large share of the industrial market, and a first-class path rather than a workaround.

The shape of it#

text
── your side (online) ──────────┐         ┌── customer site (air-gapped) ──
                                │         │
read fingerprint  ──────────────┼────────►│  camera, offline = 1
xcaicxctl mint --device …       │  USB /  │  reads unit.token from disk
unit.token  ────────────────────┼─ image ►│  verifies with compiled-in pubkey
                                │         │  never opens a socket
reconcile usage-spool.jsonl ◄───┼─────────┤  usage accrues locally
                                │         │

No activation call, no heartbeat, no revocation fetch. The camera verifies its licence with the public key compiled into the SDK and runs.

1. Get the fingerprint#

The token must be bound to the fingerprint the camera will actually compute. Read it on the unit itself, during factory test:

sh
# On the target, with your chosen source wired up
export XCAICX_DEVICE_ID="$(cat /sys/fsl_otp/HW_OCOTP_CFG0)"
echo "$XCAICX_DEVICE_ID"

Do not

If you do not set XCAICX_DEVICE_ID, the SDK falls back through devicetree serial, DMI UUID, /etc/machine-id, and finally the MAC address. /etc/machine-id is regenerated by some image build processes, and a MAC is trivially cloned. For air-gapped units — which cannot self-correct by re-activating — bind to fused silicon.

2. Mint the token offline#

bash
xcaicxctl mint \
  --sku edge-industrial \
  --customer CUST-PLANT7 \
  --device 3f2a9c4e8b1d5a6f0c2e7b9d4a1f8e3c \
  --days 365 \
  --grace-days 30 \
  --key server/keys/root_ed25519.key \
  -o unit.token

mint signs locally with the key file and never contacts a server. Useful flags:

FlagEffect
--deviceThe fingerprint to bind to. Required for air-gapped.
--daysTerm. Omit for the SKU default; 0 where the SKU is perpetual.
--grace-daysDays past expiry during which everything still runs
--api-quotaHard on-device inference cap. 0 = unmetered.
--max-streamsOverride the SKU's stream cap downwards
--modulesRestrict the entitlement set below the SKU's
--require-activationDo not use for air-gapped — forces the online exchange

Verify before shipping it:

bash
xcaicxctl inspect unit.token --pubkey server/keys/root_ed25519.pub

Check dev matches the unit, act is optional, and exp/grace_days are what the contract says.

3. Configure the camera#

c
cfg.license_path   = "/etc/xcaicx/unit.token";
cfg.state_dir      = "/var/lib/xcaicx";
cfg.cloud_endpoint = NULL;
cfg.offline        = 1;         /* never touch the network */
python
eng = xcaicx.Engine(license_path="/etc/xcaicx/unit.token",
                    state_dir="/var/lib/xcaicx",
                    offline=True)

With offline = 1 the SDK never opens a socket. xcaicx_engine_heartbeat() becomes a no-op returning XCAICX_OK, so you can leave the call in your scheduler.

Build with -DXCAICX_WITH_TLS=OFF to drop libssl from the image entirely.

4. Reconcile usage#

Usage still accrues to state_dir/usage-spool.jsonl, sealed by period. Collect it with whatever mechanism the site already permits — a maintenance USB stick, an existing one-way data diode, a quarterly service visit.

bash
cat /var/lib/xcaicx/usage-spool.jsonl
json
{"device_id":"3f2a9c…","license_id":"LIC-…","period_start":1786500000,
 "period_end":1786586400,"inferences":2592000,"frames":2592000,
 "stream_seconds":86400,"events":214,
 "modules":{"detect":2592000,"defect":0,"ppe":0,"anpr":0}}

Counts only — no frames, no crops, no imagery. When a security officer at an industrial site asks what the file contains, that is the complete answer, and being able to show them the file is usually the end of the conversation.

Feed collected spools back to the authority when you are next online:

bash
xcaicxctl usage LIC-D3ACF708DB6048AB     # what the server has recorded

Records dedupe on (device_id, period_start, period_end), so re-submitting a spool you already delivered is safe.

5. Renewal#

An air-gapped camera cannot renew itself. Plan for it:

  • Mint the replacement token before the current one expires.
  • grace_days is the buffer for a service visit that slips. Set it to match the site's realistic maintenance interval, not to a default.
  • Deliver the new token to license_path and restart the engine. There is no online step.

Careful

Set the term against the site's maintenance schedule, not the calendar. A 365-day term on a plant that is only accessible during an annual shutdown gives you a zero-day margin — the licence and the only opportunity to replace it expire in the same week.

6. What you give up#

Being explicit, because these are the questions that come back later:

CapabilityOnlineAir-gapped
Seat accounting enforced server-sideYesNo — you mint against a count you track yourself
RevocationPropagates on heartbeatNot possible until the next visit
Usage reaching the invoice automaticallyYesManual collection
Fleet visibility (xcaicxctl fleet)LiveOnly what you have collected
Weak-binding detectionRecorded at activationNot recorded

Air-gapped licensing therefore trusts your provisioning process rather than the server. If a customer's site is air-gapped and you need hard seat enforcement, the answer is a network path to the licensing authority for provisioning only — not for inference — which most security teams will accept when they understand no imagery crosses it.

Next#

Commercial software. Use requires a valid XCAICX licence token. Questions an integrator cannot answer from this page belong in an email to [email protected] — and, usually, in a fix to this page.

© 2026 AZMX AI · xcaicx.com