XCAICX docs Product Contact

Licensing model

How a licence is represented, how a camera gets one, and what the enforcement actually stops. Two revenue lines run at once: per unit, and per API use.

LineCharged onEnforced by
Per unitEach physical camera activatedBatch seat accounting + device binding
Per API useMetered inference callsOn-device quota + usage spool → invoice

Commercial terms are per programme; this page documents the mechanism, not the numbers.

1. The token#

A licence is a signed, self-contained string:

text
XCAICX1.<base64url(payload_json)>.<base64url(ed25519_signature)>

The signature covers the ASCII bytes XCAICX1.<base64url(payload)> — the encoded payload, not the decoded JSON.

Note

That detail matters more than it looks. Verification never depends on signer and verifier agreeing on JSON key order or whitespace, which is the single most common way licensing systems break in production. The C++ verifier and the Python signer are independent implementations; the test suite runs tokens minted by one through the other precisely to catch drift.

Decoded payload:

json
{
  "lic": "LIC-D3ACF708DB6048AB",
  "sku": "oem-industrial",
  "sub": "CUST-DA6871393C",
  "iss": "xcaicx",
  "dev": "b2cd209a6826bd8b33c0fdaad6304106",
  "ent": {"detect": true, "defect": true, "ppe": true, "anpr": false},
  "lim": {"max_streams": 8, "max_fps": 30, "api_calls": 0},
  "iat": 1786584928, "nbf": 1786584928, "exp": 0,
  "grace_days": 30,
  "act": "optional",
  "batch": {"id": "B-2026-1C4417D5", "units": 5000}
}
ClaimMeaning
licLicence id. What revocation refers to.
skuCatalogue entry this was issued against
subCustomer id
devBound hardware fingerprint, or "*" for an unbound batch key
entEntitlement set. The SDK refuses to start a module whose bit is false.
lim.max_streamsConcurrent stream cap
lim.max_fpsPer-stream FPS cap
lim.api_callsMetered quota; 0 means unmetered
iat / nbf / expIssued-at, not-before, expiry. exp: 0 is perpetual.
grace_daysDays past exp during which everything still runs
actrequired forces the per-unit activation exchange; optional does not
batchBatch id and purchased unit count, on batch keys

Signing is Ed25519. The private key lives only on the licensing server (or an HSM). The public key is compiled into the SDK, so a camera can verify a licence with no network and nothing on the device can mint one.

You can inspect any token locally:

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

2. Per-unit licensing#

OEM path — royalty per manufactured camera#

  1. Sell an OEM a batch of N units:
    bash
    xcaicxctl batch --customer CUST-CAMCO --sku oem-industrial --units 5000
  1. They receive one batch keydev: "*", act: "required", batch.units: 5000 — and install it on the factory provisioning line. It is a licence to activate, not a licence to run: presented to an engine directly, it fails with ACTIVATION_REQUIRED.
  1. Each camera, on first boot, calls POST /v1/activate with its hardware fingerprint. The server:
    • checks the batch has units left (402 Payment Required if exhausted),
    • mints a unit-bound licence — dev = that camera's fingerprint, act: "optional"
    so it never activates again,
    • decrements the batch counter by one.
  1. The camera stores the unit token in state_dir and uses it from then on.

That decrement is the billing event. One activation, one unit.

Careful

Re-activation is free, by design. A device already on file gets its existing token back and consumes no additional unit. A factory reflash, an SD-card swap or a firmware reinstall must not be a billing event — treating one as a billing event is the fastest way to lose an OEM account.

End-user path — subscription per camera#

Sell an edge-* SKU directly. Each camera gets its own licence with a term (exp) and a grace_days window:

bash
xcaicxctl license --customer CUST-PLANT7 --sku edge-industrial \
                  --device 3f2a9c... --days 365

Air-gapped path#

Pre-provision device-bound tokens offline; the camera never touches a network. See Air-gapped deployment.

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

3. The device fingerprint#

This is the most important decision in an integration, because it determines how hard your product is to clone. The SDK derives a fingerprint in this order:

  1. XCAICX_DEVICE_ID environment variable ← use this
  2. /sys/firmware/devicetree/base/serial-number (ARM SoC)
  3. /sys/class/dmi/id/product_uuid (x86)
  4. /etc/machine-id
  5. macOS IOPlatformUUID (dev workstations)
  6. First non-loopback MAC address ← weak, and reported as such

An explicit override wins outright — you know your hardware better than anything the SDK can sniff:

sh
export XCAICX_DEVICE_ID="$(cat /sys/fsl_otp/HW_OCOTP_CFG0)"

Fingerprints derived from a MAC address are flagged weak_binding on the activation record and surface in xcaicxctl fleet, so you can see which of your units are trivially clonable.

Note

The SDK still runs on a weak fingerprint. Refusing to start a customer's production line over a fingerprint technicality is worse than the revenue at risk.

4. Expiry, grace and clock rollback#

  • Past exp but within grace_days: state is XCAICX_LIC_GRACE. Everything still works. Surface it as a warning so the customer renews before the hard stop.
  • Past exp + grace_days: LICENSE_EXPIRED. Analytics stop; the video path must not.
  • The SDK persists a clock high-water mark in state_dir. A clock set behind it yields LICENSE_CLOCK_ROLLBACK, so winding the RTC back does not buy another year.

A camera with a dead RTC battery that boots at epoch will trip rollback detection. That is the correct behaviour, and the fix is NTP or a battery — but it is worth knowing before a technician calls you about it.

5. Revocation#

bash
xcaicxctl revoke LIC-D3ACF708DB6048AB --reason chargeback

Revocation propagates through GET /v1/revocations, which every online SDK pulls on heartbeat and caches in state_dir/crl.json. A revoked licence is refused at the next engine start and blocked from reporting usage immediately.

An offline camera keeps working until it next reaches the network. That is inherent to offline licensing, and if it matters for your deployment the answer is a shorter term rather than a longer revocation list.

6. What enforcement actually buys you#

Each row is covered by the 45 checks in core/tests/test_license.cpp:

AttackResult
Flip a byte in the payloadBadSignature
Re-encode payload with anpr: true, keep the signatureBadSignature
Copy a licence to another cameraWrongDevice
Wind the clock back a year to dodge expiryClockRollback
Use a token past its termExpired, after grace_days
Use a revoked licenceRevoked
Forge a batch key with your own Ed25519 key403 at activation
Present a validly-signed batch key we never sold403 — no seat behind it
Replay a usage record to avoid billingDeduped, duplicates: 1
Reboot to reset the metered counterCounters resume from disk

You can exercise every one of these yourself in the token lab on xcaicx.com, which runs real Ed25519 against a throwaway keypair generated in your browser.

Be honest about the threat model#

Do not

Offline licensing is deterrence, not DRM. An attacker with root on the camera and a disassembler can patch out the check — that is true of every software licensing scheme that runs on hardware the attacker controls.

What this design does buy:

  • Casual copying is dead. Device binding plus signatures stop the realistic threat: an integrator cloning one paid image onto fifty cameras.
  • Entitlements cannot be widened. Buying a detection-only SKU and enabling ANPR requires forging Ed25519.
  • Seat accounting is server-side. Unit counts cannot be inflated on-device.
  • Tamper leaves evidence. Weak fingerprint sources are recorded per activation; a cluster of weakly-bound units on one customer is worth a phone call.

If you need more, the escalation is hardware: bind to a secure element or fused SoC serial via XCAICX_DEVICE_ID and have the bootloader verify SDK integrity. The SDK supports that today — it is a deployment decision, not a code change.

7. Protecting the signing key#

It is the root of trust for every licence you will ever issue.

  • Production: KMS or HSM, never on the API server's disk.
  • server/keys/*.key is gitignored; keygen writes it mode 600 and refuses to overwrite without --force.
  • Rotation: license_pubkey in the engine config overrides the compiled-in key at runtime, so a rotation can be staged through configuration before a firmware release makes it permanent.

More in Security & threat model and Running the authority.

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