xcaicxctl CLI
The operator CLI. Key generation, offline minting, token inspection, and every admin operation against a running authority.
xcaicxctl [--server URL] [--admin-key KEY] <command> [options]| Global flag | Default | Notes |
|---|---|---|
--server | $XCAICX_SERVER, else http://127.0.0.1:8000 | The licensing authority |
--admin-key | $XCAICX_ADMIN_KEY | Required for every server-side command |
Set both in your environment and they disappear from your command lines:
export XCAICX_SERVER=https://licensing.xcaicx.com
export XCAICX_ADMIN_KEY="$(security find-generic-password -a admin-key -s xcaicx-licensing -w)"Commands split into two groups: offline (keygen, mint, inspect, catalogue) which need only the key file, and server (everything else) which need --admin-key.
Offline commands#
keygen#
xcaicxctl keygen --out server/keys [--force]Generates the root Ed25519 signing keypair. Writes the private key mode 600 and refuses to overwrite an existing key without --force.
Do not
Run this once, ever, and back the private key up offline before you issue a single licence against it. It is the root of trust for every licence you will ever issue; if it leaks, anyone can mint licences for your SDK and the only remedy is shipping new firmware with a new compiled-in public key. See Security.
The public half goes into core/src/license/root_key.inc and is compiled into the SDK.
mint#
Signs a licence offline, without contacting a server. This is the air-gapped path.
xcaicxctl mint --sku edge-industrial \
--customer CUST-PLANT7 \
--device 3f2a9c4e8b1d5a6f \
--days 365 \
--key server/keys/root_ed25519.key \
-o unit.token| Flag | Default | Notes |
|---|---|---|
--sku | required | Catalogue code |
--customer | required | Customer id |
--device | * | Fingerprint to bind to. * is unbound. |
--days | SKU default | Term. |
--grace-days | SKU default | Days past expiry that still run |
--api-quota | 0 | Hard on-device inference cap; 0 = unmetered |
--max-streams | SKU default | Override the stream cap |
--modules | SKU set | Comma list, to restrict below the SKU |
--license-id | generated | Supply your own id |
--require-activation | off | Forces the online per-unit exchange |
--key | server/keys/root_ed25519.key | Signing key file |
-o | stdout | Output file |
Careful
mint writes no record to the licensing database. Seat accounting for minted licences is whatever you track yourself — that is the trade the air-gapped path makes. Do not mix mint and batch activation for the same customer without a plan for reconciling counts.
inspect#
Decodes a token and optionally verifies its signature.
xcaicxctl inspect unit.token --pubkey server/keys/root_ed25519.pub{
"lic": "LIC-D3ACF708DB6048AB",
"sku": "oem-industrial",
"dev": "b2cd209a6826bd8b33c0fdaad6304106",
"ent": {"detect": true, "defect": true, "ppe": true, "anpr": false},
"lim": {"max_streams": 8, "max_fps": 30, "api_calls": 0},
"exp": 0, "grace_days": 30, "act": "optional"
}Without --pubkey it decodes but does not verify. That is useful for reading a token a customer emailed you, and it is not a validity check — say so out loud when someone pastes inspect output as evidence that a licence is good.
--pubkey accepts either the key string or a path to a file.
catalogue#
xcaicxctl catalogue [--json]Lists SKUs with modules, caps, terms, allowances and price tiers from the active catalogue, including any XCAICX_PRICING_JSON overrides.
Server commands#
customer#
xcaicxctl customer "CamCo" --kind oem --contact [email protected]--kind is oem or end_user. Prints the new CUST-… id.
batch#
Issues an OEM batch key good for N activations.
xcaicxctl batch --customer CUST-CAMCO --sku oem-industrial --units 5000 -o batch.token| Flag | Notes |
|---|---|
--customer, --sku, --units | Required |
--term-days | Overrides the SKU term |
-o | Write the batch token to a file |
The batch token is what goes onto the factory provisioning line. Treat the file as a credential: anyone holding it can consume units from the batch.
batch-status#
xcaicxctl batch-status B-2026-1C4417D5Purchased vs activated vs remaining. The first thing to check when an OEM says activation is failing — a 402 means the batch is simply spent.
license#
Issues a single licence through the server, recording it in the database.
xcaicxctl license --customer CUST-PLANT7 --sku edge-industrial \
--device 3f2a9c4e8b1d5a6f --days 365| Flag | Default |
|---|---|
--device | * |
--days | SKU term |
--api-quota | 0 |
--require-activation | off |
Prefer this over mint whenever the server is reachable: it leaves a record, so the licence shows up in fleet views and invoices.
revoke#
xcaicxctl revoke LIC-D3ACF708DB6048AB --reason chargebackImmediate for usage reporting; effective on the camera once it fetches /v1/revocations.
fleet#
xcaicxctl fleet [--json]Every activated unit, most recent first. Weakly-bound units are flagged.
Note
Scan the WEAK-BINDING column periodically rather than only when investigating a problem. A cluster of weakly-bound units on one customer means their integrator is deriving fingerprints from MAC addresses, which is a conversation to have before it becomes a cloning case.
usage#
xcaicxctl usage LIC-D3ACF708DB6048ABTotals for one licence: inferences, frames, stream-seconds, events, and the number of records received.
invoice#
xcaicxctl invoice CUST-CAMCO --days 30 [--json]Rates the period. --days 365 additionally includes annual maintenance on perpetual SKUs.
Recipes#
Stand up a new OEM programme
CUST=$(xcaicxctl customer "CamCo" --kind oem | awk '{print $NF}')
xcaicxctl batch --customer "$CUST" --sku oem-industrial --units 5000 -o batch.token
xcaicxctl inspect batch.token --pubkey server/keys/root_ed25519.pubCheck a camera's licence against what you issued
scp camera:/var/lib/xcaicx/unit.token .
xcaicxctl inspect unit.token --pubkey server/keys/root_ed25519.pub
xcaicxctl usage "$(python3 -c 'import json,sys;print(json.load(sys.stdin)["lic"])' < decoded.json)"Reconcile a disputed invoice
xcaicxctl batch-status B-2026-1C4417D5 # are more units activated than expected?
xcaicxctl fleet --json | python3 -m json.tool | grep -c weak_binding
xcaicxctl invoice CUST-CAMCO --days 30 --jsonVerify a deployment did not break signing
curl -s https://licensing.xcaicx.com/healthz | python3 -m json.tool
# public_key must equal the key compiled into the shipped SDKNext#
- Licensing REST API — what each command calls
- Running the authority — deployment and key handling
- Air-gapped deployment — where
mintbelongs