Docs

AffixIO on your host. The record never leaves.

The public Node SDK. Install it, prove from stores you already run, sign every proof with ML-DSA-65, and keep AffixIO to a licence heartbeat unless you opt in to remote sync.

What ships

Package name affixio. Version 1.3.2. Licence Apache-2.0. Engine Node.js 20 or newer. Entry dist/index.js, types dist/index.d.ts, ESM only. CLI bins: affixio and affix-sdk (same file). Source: github.com/AffixIO/SDK. npm: npmjs.com/package/affixio.

The tarball includes dist/, bundled Noir circuits simple_yesno and yesno, demo-data/, examples/sql/, and TLS material under certs/. SQL drivers are not bundled. Install pg, mysql2, or odbc yourself if you use those recipes.

Runtime dependencies in the package: @aztec/bb.js (UltraHonk / Barretenberg), @noir-lang/noir_js, @noble/post-quantum (ML-DSA-65), qrcode, bwip-js.

Canonical Node install: npm install affixio (1.3.2). Light edge package: npm install @affix-io/sdk-light (1.1.3). Do not cite the unpublished @affix-io/sdk scope. Older method maps remain on /sdk/ and /sdk-light/. Product shape: /sdks/. API hub: /developers/. Path: /how-it-works/.

Install

npm install affixio

Issue a key in Hub. Store it on disk, mode 0600, not in config.json:

npx affixio set-key --api-key aio_your_key

Or pass apiKey into the constructor, or set AFFIX_API_KEY. Guided setup: npx affixio setup then npx affixio menu.

Quick start

CLI:

npx affixio prove --claim approved --offline
npx affixio verify-local <proof-hex>

verify-local exits 0 when valid, 2 when invalid. It does not throw a parser error at the caller.

In code:

import { AffixSDK } from "affixio";

const sdk = new AffixSDK({ apiKey: process.env.AFFIX_API_KEY });

const proof = await sdk.prove({
  mode: "offline",
  circuitId: "simple_yesno",
  fields: { claim: "approved", required: "approved" },
});

const check = await sdk.verifyLocal("simple_yesno", proof.proof, {
  envelope: proof.envelope,
});
// { valid: true, decision: "yes", proof_mode: "hmac", envelope_ok: true }

Claim and context values are plain text. They are reduced to canonical field elements for you, so gate/north-2 is as valid as 0x1f.

What runs where

Host versus AffixIO in licence-only mode
On your host At AffixIO (licenceOnly default true)
HMAC and UltraHonk proving GET /v1/auth/check licence heartbeat
Local proof verification Nothing else. Remote prove/verify/flush throw LicenceOnlyError
ML-DSA-65 signing of every proof
Tamper-evident spend journal
QR and barcode issue and scan
Lookups against your databases and APIs

Set licenceOnly: false only when this deployment is meant to push proofs to api.affix-io.com for remote verify, AffixIO ML-DSA attestation, and Merkle anchoring. That path is documented under Remote opt-in.

Constructor and env

new AffixSDK({ apiKey, ... }). apiKey is required on the object (the stored key from set-key also works for the CLI). Useful fields from AffixSdkConfig:

SDK configuration and environment
Option / env Default
AFFIX_API_KEY / apiKey Required
AFFIX_API_BASE / apiBase https://api.affix-io.com
AFFIX_PROOF_MODE / proofMode hmac
AFFIX_LICENCE_ONLY / licenceOnly true
AFFIX_LOCAL_HMAC_SECRET / hmacSecret Generated on first run
AFFIX_PRESENTMENT_BASE / presentmentBase Unset. QR links point at your host only. There is no AffixIO URL default.
allowOfflineProve true
brickWithoutLicence true
enforceProofQuota true (POST /v1/quota/consume when networking is allowed)
queueUnsyncedProofs / autoFlush false in licence-only mode
autoFlushIntervalMs 5000 when auto-flush is on
maxMerkleLeaves 50000
flushChunkSize 25 (API max 25 proofs per aggregate call)
flushConcurrency 4
merkleAuditBatchSize 1000
licenceMinIntervalMs / licenceMaxIntervalMs About 36 hours / 60 hours between heartbeats

operatorConfig() and npx affixio config report presence flags only. They never print key material. Call sdk.dispose() when tearing down a long-lived process so the auto-flush timer stops.

.affix layout

Operator state lives under .affix/. Keep it out of git and out of container images. Proof and queue blobs can move to Redis, SQL, memory, or any get/set backend via documentStore. Secrets and the spend journal stay on local disk.

Operator files under .affix
Path Contents Mode
.affix/config.json Operator configuration, no secrets 0600
.affix/secrets/api.key AffixIO API key 0600
.affix/secrets/hmac.secret Local HMAC secret 0600
.affix/secrets/mldsa65.json Deployment ML-DSA-65 key pair 0600
.affix/spend/spend-journal.jsonl Signed hash chain 0600
.affix/proofs.json, stats.json Stored proofs and counters 0600

prove()

Always local. Never AffixIO prove. Works in offline, auto (falls back when the network is down), and online (live licence required; prove still runs on the host).

const proof = await sdk.prove({
  mode: "offline",
  circuitId: "simple_yesno",
  proofMode: "hmac",
  fields: { claim: "active", required: "active" },
  origin: "manual",
});

You get proof_id, proof (hex), decision yes or no, proof_digest, proof_mode, offline, pending_sync, an envelope (canonical payload plus local attestation), and signature (algorithm: "ML-DSA-65", key_id, signature_b64, public_key_b64). Signing failure is an error, not a warning. Proof creation fails if verification of that signature fails.

decision on the input forces HMAC outcomes. UltraHonk ignores it; the circuit decides. request_id is auto-generated if omitted. One host request maps to one proof.

HMAC scheme name: affix-light-v1. Algorithm: HMAC-SHA256. Helpers: lightProve, lightVerify, isLightProof, packLightProof, unpackLightProof.

Proof digest matches the API contract: SHA-256 over {circuitId}:{valid}:{proofHex}, not a hash of the hex alone.

HMAC and UltraHonk

Proof modes
hmac (default) ultrahonk
What it is Authenticated, signed yes/no. Not zero-knowledge. Zero-knowledge over bundled Noir circuits via Barretenberg
Circuits Labelled with the circuit id you pass, typically simple_yesno Bundled simple_yesno and yesno only
Speed (README) Sub-millisecond Seconds
Licence entitlement HMAC on paid tiers ultrahonk_zkp (Enterprise on the pricing page)
const zk = await sdk.prove({
  mode: "offline",
  circuitId: "simple_yesno",
  proofMode: "ultrahonk",
  fields: { claim: "approved", required: "approved" },
});

Do not describe every AffixIO proof as ZK. CLI: npx affixio prove --claim approved --proof-mode ultrahonk --offline.

verifyLocal

Handles HMAC and UltraHonk, with an optional ML-DSA-65 envelope check. Tampered, truncated, or forged carriers return valid: false plus a reason. Fail closed. Source is always "local".

const result = await sdk.verifyLocal(circuitId, proofHex, {
  proofId,
  envelope: proof.envelope,
});

Lower-level: localVerify, unpackProof, proofDigest, wrapProofEnvelope, verifyProofEnvelope.

Witness and fields

Most hosts never build a witness by hand. Pass fields: { claim, required }. For a full credential:

import { buildYesNoWitness } from "affixio";

const now = Math.floor(Date.now() / 1000);
const witness = buildYesNoWitness("simple_yesno", {
  schema_id: "gate_v1",
  issuer_id: "site-a",
  issuer_pubkey_hash: "0x1",
  credential_id: "0x99",
  claim_value: "approved",
  valid_from: now - 60,
  valid_until: now + 86_400,
}, {
  secret: "0xsec",
  context_id: "0xctx",
  required_claim_hash: "approved",
});

await sdk.prove({ mode: "offline", witness });

Also exported: defaultContext, isAffixCircuit, normaliseWitnessInputs, normaliseWitnessPackage.

Data check then prove

AffixIO never queries your databases. Your host reads a store, then proves from the exact field strings it found. Pass is exact string equality: fields.claim === fields.required. Decision is yes only when those strings match.

const found = await sdk.check(store, {
  id: "REC-1001",
  claimField: "status",
  required: "active",
});
const proved = await sdk.proveFromCheck({ check: found, mode: "offline" });
// proved.field_aligned is true when decision === "yes" ⇔ check.pass

Shortcuts: checkAndProve(store, query, opts), proveFromCheckAndVerify (needs licenceOnly: false and a live line), generateCodeFromCheck for a QR after the lookup. Each call gets a fresh request_id and proof_id. Unique credential_id / context_id so nullifiers differ per request.

Open anything in the catalogue with openDataStore({ kind, path }) or a live executor. Kinds: sql, postgres, mysql, mariadb, sqlite, mssql, sql_script, json, json_document, mongo, mongo_document, ndjson, key_value, redis, redis_export, oracle, db2, odbc, csv, tsv, pipe, pipe_delimited, semicolon, delimited, fixed_width, dbase, dbf, xml, ldif, ini.

Demo fixtures ship in demo-data/ (modern JSON/Mongo/Redis export, plus legacy CSV, TSV, pipe, semicolon, fixed-width, dBASE, XML, LDIF, INI). Entitlement for live DB adapters is db_adapters (Business+ on the pricing page). Missing entitlements on legacy cloud keys stay open.

SQL

Parameterise. Never concatenate an id into SQL. Read-only by default on live executors. null from lookup means no row. SqlLookupError means infrastructure: codes timeout, pool, connection, query, readonly, unknown. error.isDbDown is true when the engine is unreachable, not when the row is missing. lookupOutcome({ catchInfra: true }) returns row | no_row | db_error.

import {
  AffixSDK,
  SqlStore,
  createPgExecutor,
  createPrimaryReplicaExecutor,
} from "affixio";
import pg from "pg";

const primary = new pg.Pool({ connectionString: process.env.AFFIX_SQL_PRIMARY_URL });
const executor = createPrimaryReplicaExecutor({
  primary: createPgExecutor(primary, { readOnly: true, timeoutMs: 10_000, role: "primary" }),
  preferReplica: true,
  failoverToPrimary: true,
  dialect: "postgres",
  readOnly: true,
  timeoutMs: 10_000,
});

const store = SqlStore.fromExecutor(executor, {
  table: "records",
  dialect: "postgres",
  lookupSql: "SELECT id, status, site FROM records WHERE id = :id LIMIT 1",
  readOnly: true,
  timeoutMs: 10_000,
});

const sdk = new AffixSDK({ apiKey: process.env.AFFIX_API_KEY });
const proved = await sdk.checkAndProve(store, {
  id: "REC-1001",
  claimField: "status",
  required: "active",
}, { mode: "offline" });

Also: createMysqlExecutor, createOdbcExecutor, resolveSqlConnectionConfig, InMemorySqlDatabase for tests, prepareLookupSql, wrapSqlExecutor, withQueryTimeout. Copyable recipes live in the package at examples/sql/postgres.mjs, mysql.mjs, odbc.mjs. Those files still show an older import path in comments; import the helpers from affixio.

Env the recipes expect: AFFIX_SQL_PRIMARY_URL, optional AFFIX_SQL_REPLICA_URL, AFFIX_SQL_TIMEOUT_MS (default 10000), AFFIX_API_KEY.

File and document stores

import { openDataStore, JsonDocumentStore } from "affixio";

const json = openDataStore({
  kind: "json",
  path: new URL("../node_modules/affixio/demo-data/modern/patients.json", import.meta.url).pathname,
});

const csv = openDataStore({ kind: "csv", path: "./extract.csv" });
const dbf = openDataStore({ kind: "dbf", path: "./legacy.dbf" });

Classes if you want them explicit: JsonDocumentStore, KeyValueStore, CsvTableStore, FixedWidthStore, PipeDelimitedStore, MongoDocumentStore, XmlDocumentStore, DbaseStore, LdifStore, DelimitedTableStore, IniSectionStore, RedisExportStore, HttpDataStore / createHttpDataStore / testHttpProfile. HTTP profiles store the names of env vars for auth, not the secrets. sdk.testConnections() probes licence, internal/external HTTP profiles, and HSM when configured.

QR and barcodes

Codes carry a PII-free proof any standard scanner can read. Carrier prefix AFX.ZK1.. A presentment link, if you set presentmentBase, points at your host. There is no AffixIO URL default. When unset, the image encodes the raw carrier only.

const qr = await sdk.generateCodeFromProve({
  kind: "qr",
  maxUses: 1,
  format: "both",
  mode: "offline",
  save: { path: "./codes", sidecar: true },
  fields: { claim: "day-pass", required: "day-pass" },
});
// qr.carrier → AFX.ZK1.…
// qr.pii_free === true

const result = await sdk.readCode({
  scanned: qr.content,
  sidecarPath: qr.files.sidecar,
  gateId: "north-door",
  consume: true,
  mode: "offline",
});
Code options
Option Meaning
maxUses: 1..255 Cap admissions per gate
maxUses: "unlimited" or 0 Reusable, no cap
sidecar: true Keep the full proof beside the code; the carrier stays PII-free
kind qr or barcode
format svg, png, or both
readCode.consume Increment local use count on admit (default false for verify-only)
Capacity constants QR 2953 bytes, PDF417 1800, Data Matrix 2335 (conservative)

Licence entitlement: qr_carriers (Growth+). Helpers: packCarrier, unpackCarrier, isZkCarrier, extractCarrierFromScan (raw carrier or a host /v/… URL), renderQrSvg, renderBarcodeSvg, CodeUseStore. Circuit ids on the carrier header are simple_yesno or yesno.

Spend journal

Code uses are written to an append-only, hash-chained journal. Each entry carries the previous hash and an ML-DSA-65 signature. Writes take an exclusive lock. A broken chain fails closed so nothing is admitted. There is no reset in the SDK. Rotating the journal is a deliberate operator action against the files on disk.

sdk.spendStatus();        // { integrity, head, journal_path }
sdk.verifySpendJournal(); // { ok, seq, last_hash, count }

SpendJournal is exported if you need the class. CLI: npx affixio spend-status.

Local ML-DSA-65

Every proof is signed with ML-DSA-65 (FIPS 204) as it is created. The signing key belongs to your deployment and is separate from any AffixIO key. No private key ships in the package. Envelope algorithm string on local payloads is HMAC-SHA256+ML-DSA-65 (canonical JSON plus a domain-separated HMAC-SHA256 key alongside ML-DSA-65).

proof.signature;
// { signed: true, algorithm: "ML-DSA-65", key_id, signature_b64, public_key_b64 }

sdk.exportLocalPublicKey();
// { algorithm: "ML-DSA-65", key_id, public_key_b64 }

import {
  createLocalSigningKeyPair,
  signLocalPayload,
  verifyLocalPayload,
} from "affixio";

CLI: npx affixio pubkey. AffixIO-side attestation (different key, public at /.well-known/affix-mldsa65.json) only happens when you opt in to remote verify. See NIST. Licence entitlement for local ML-DSA-65 signing: mldsa65 (Business+).

Licence

Heartbeat: GET /v1/auth/check with the API key. Recheck is randomised roughly every couple of days (licenceMinIntervalMs / licenceMaxIntervalMs). Definitive API denials expire the local lease immediately. Network errors may use grace. brickWithoutLicence (default true) gates prove/verify when the lease is dead.

await sdk.checkLicence(true);
sdk.licenceState();
await sdk.isOnline();

LicenceState includes ok, status (unchecked | active | expired | unreachable), message, key_hint, plan_tier, grace, entitlements, and quota. Entitlement flags: qr_carriers, mldsa65, db_adapters, ultrahonk_zkp, hsm_integration. LicenceError has code affix_sdk_licence_invalid.

CLI: npx affixio licence. Pricing and quota numbers: Pricing.

Remote opt-in

Turn licenceOnly off for a host that is supposed to push proofs to AffixIO for circuit verify, AffixIO ML-DSA-65 attestation, and Merkle anchoring. Client batches cap at 50,000 leaves.

const sdk = new AffixSDK({
  apiKey: process.env.AFFIX_API_KEY,
  licenceOnly: false,
  autoFlush: true,
});

await sdk.proveAndVerify({
  mode: "online",
  circuitId: "simple_yesno",
  fields: { claim: "approved", required: "approved" },
});

await sdk.flushOfflineQueue();
sdk.stopAutoFlush();
sdk.dispose();

flushOfflineQueue builds a client Merkle batch, verifies each proof on AffixIO with attestation (parallel workers, default 4), and anchors admitted digests. Manual and auto flushes share one lock so batches never overlap. The timer is unref so short CLI runs still exit. digestsOnly: true skips ZK verify and is not for production admits.

sdk.verify(circuitId, proof, requestAttestation, opts) is online only. On failure with queueOnFailure, the proof is enqueued. queueProofForAffix exists for an offline scan that still needs AffixIO signing later. buildMerkleBatch / anchorPendingLeaves pack digests. Algorithm: sha256-sorted-pairs. At 50,000 leaves omit proof bytes (includeProofBytes: false).

The HTTP client (sdk.client) exposes health, listCircuits, verify, merkleRoot, merkleAudit, merkleVerifyProof, aggregateVerify (1..25, no ML-DSA; prefer circuit verify for signing), attest (POST /api/attest), merkleAuditBatch. Auth on the API is Bearer or X-API-Key. Default rate limit on the API is 10 requests per second per key. OpenAPI: /v1/openapi.json.

CLI

JSON on stdout, diagnostics on stderr, so output pipes into jq.

npx affixio menu
npx affixio setup
npx affixio set-key --api-key aio_...
npx affixio licence
npx affixio prove --claim approved --offline
npx affixio prove --claim approved --proof-mode ultrahonk --offline
npx affixio verify-local <proof-hex>
npx affixio qr --claim approved --scans 1 --out ./codes
npx affixio barcode --claim approved --scans 5 --out ./codes
npx affixio read <payload> --sidecar ./codes --consume
npx affixio spend-status
npx affixio stats
npx affixio hsm status
npx affixio hsm test
npx affixio config
npx affixio pubkey

HSM and KMS

Connect PKCS#11 hardware (Thales, Utimaco, nCipher, YubiHSM, SoftHSM) or a cloud service (AWS KMS and CloudHSM, Azure Key Vault and Managed HSM, GCP KMS, Fortanix, or any HTTP endpoint). The profile stores the provider, labels, and the names of the environment variables that hold credentials. PINs, tokens, and keys are never written to config.

await sdk.setHsmProfile({ provider: "pkcs11", library_path: "/usr/lib/softhsm/libsofthsm2.so", pin_env: "HSM_PIN" });
sdk.hsmStatus();
await sdk.testHsm();

Providers on the type: pkcs11, softhsm, aws_cloudhsm, aws_kms, azure_key_vault, azure_managed_hsm, gcp_kms, google_cloud_hsm, thales, utimaco, ncipher, yubihsm, fortanix, generic_http, custom. Entitlement: hsm_integration (Enterprise). Interactive menu key H connects one.

Storage backends and stats

Implement get / set / optional delete and pass documentStore (or per-domain storage.proofs, queue, codeUses, licence). Keys: proofs, offline-queue, code-uses, licence. Built-ins: createMemoryDocumentStore, createDefaultJsonDocumentStore, JsonFileDocumentStore, PathMappedJsonStore.

import { AffixSDK, createMemoryDocumentStore } from "affixio";

const sdk = new AffixSDK({
  apiKey: process.env.AFFIX_API_KEY,
  documentStore: createMemoryDocumentStore(),
});

Counters are local. No telemetry leaves the host.

const stats = await sdk.statsSnapshot();
// stats.proofs.{hmac, ultrahonk, yes, no, mldsa65_signed, mldsa65_sign_failures}
// stats.verify.{local_ok, local_fail}
// stats.codes.{issued, admitted, denied}
// stats.spend.{consumed, double_spend_blocked, journal_errors}

CI action

The SDK repo ships a composite GitHub Action that posts a proof to api.affix-io.com before deploy. It bundles the Sectigo intermediate CA so TLS works when the API host omits the full chain.

- uses: AffixIO/SDK/.github/actions/verify-proof@main
  with:
    api_key: ${{ secrets.AFFIX_API_KEY }}
    circuit_id: yesno
    proof: ${{ env.AFFIX_PROOF }}

Inputs: api_key (required), circuit_id (default yesno), proof (hex or path), api_base (default https://api.affix-io.com).

What to build

Same SDK. Different hosts. The store and the gate change. The prove call does not.

JSON allow-list

Membership, staff roster, or device register as a JSON file. JsonDocumentStore or openDataStore({ kind: "json", path }), then checkAndProve with claimField: "status" and required: "active". Works on a laptop, a Pi, or a VM.

Postgres gate

PAS, CRM, or tenancy table. Replica for lookups, failover to primary. Parameterised WHERE id = :id. Treat no_row as deny, isDbDown as retry. Same pattern for MySQL and ODBC.

Legacy extract

Nightly CSV, dBASE, fixed-width, LDIF, or INI drop. Point openDataStore at the file. Prove from the exact strings the adapter found. No migration of the source system required to get a yes or no out.

Door, till, or turnstile

generateCodeFromProve with maxUses: 1 and a sidecar. Scanner posts the raw text to readCode({ consume: true, gateId }). Presentment URL on your host if you want a link instead of the raw AFX.ZK1. carrier.

Season pass

maxUses: 255 or "unlimited". Same carrier. Spend journal still hash-chains each consume. Gate id separates doors.

Offline kiosk

mode: "offline", allowOfflineProve: true. Admit locally, enqueue. When the line returns and you have opted in to remote sync, flushOfflineQueue (or auto-flush every 5s) pushes attestations.

HTTP handler

Any Node server. POST an id, run checkAndProve, return decision, proof_id, and signature.key_id. Keep the API key on the server. There is no browser or WASM prover in this package.

Internal HTTP API

createHttpDataStore against a service you already run. Auth via env var name on the profile. Then prove from the fields that came back. AffixIO never sees that HTTP call.

Agent permission

Local policy JSON: tool name as id, required: "allow". Prove before the agent is allowed to call a tool. Binary outcome, signed, spendable. MCP for AffixIO itself is a different surface (/mcp/).

Age or KYC style gate

The source record stays in your store. The adapter copies claim and you set required. The SDK proves equality. Do not send names or dates of birth to AffixIO on the default path. Edge-shaped demo of that boundary: Edge Audit.

Tests without a database

InMemorySqlDatabase plus createMemoryDocumentStore. HMAC prove in the unit test. UltraHonk only when you mean to load Barretenberg.

Overnight Merkle pack

With remote sync on, buildMerkleBatch({ maxItems: 50000, includeProofBytes: false }) then flush. Use it for a clinic or depot that ran offline all day.

HSM-backed host

setHsmProfile, credentials in env, npx affixio hsm test before go-live. Profile never holds the PIN.

CI gate

The verify-proof Action against api.affix-io.com before a deploy that is supposed to carry a live proof.

Operator box

A machine with Node 20, npx affixio menu, a Hub key, and a store. No application framework required. CLI prints JSON. Pipe it.

Errors

  • LicenceOnlyError if a remote AffixIO operation is attempted while licence-only is on.
  • LicenceError (affix_sdk_licence_invalid) when the lease is not usable.
  • SqlLookupError for driver/infra faults. null from check is not an error; the row is missing.
  • Local verify of a bad carrier: valid: false and a reason. CLI exit 2. Do not treat that as a thrown parser failure.
  • Remote API (when opted in): 409 double_spend_detected is a spent digest, not a transport failure. 429 has Retry-After. See How it Works.

Security defaults

  • Secrets under .affix/secrets/, mode 0600. .affix/ is excluded from the published package.
  • HSM profiles store environment variable names, never PINs or tokens.
  • Spend journal is append-only with no reset path.
  • Tampered proofs fail closed.
  • Keep keys off the browser, out of git, and out of model prompts.
  • Report SDK issues to security@affix-io.com, not a public GitHub issue. Policy: the package SECURITY.md.

This package implements FIPS 204 ML-DSA-65. It is not FIPS 140-3 validated. AffixIO holds no ISO 27001 or SOC 2 report. Details: Compliance, NIST, Security.

Why engineers take this package

One npm install. Prove beside the record. HMAC when you need throughput. UltraHonk when you need ZK. A signature on every proof. A journal that does not reset. AffixIO stays a licence check until you decide otherwise.

Where it fits

Doors, tills, kiosks, PAS lookups, CSV drops, agent tool gates, CI. Node 20 wherever you already run Node. Not a browser SDK. Not a PyPI package. HTTP from other languages is Developers.

What to cite

affixio 1.3.2, Apache-2.0, Node.js 20+. HMAC default, UltraHonk optional, ML-DSA-65 on every proof. licenceOnly defaults true. Import from the affixio package.

llms brief

SDK questions

Answers match the npm package, not a second SDK that is not on npm.

What is the current AffixIO SDK on npm?

affixio version 1.3.2, Apache-2.0, Node.js 20 or newer. CLI bins are affixio and affix-sdk. Canonical Node install: npm install affixio (1.3.2). Light edge package: npm install @affix-io/sdk-light (1.1.3). Do not cite the unpublished @affix-io/sdk scope.

Does AffixIO send my records to AffixIO?

No, not in the default licence-only mode. Prove, local verify, ML-DSA-65 signing, the spend journal, QR issue, and data-store lookups all run on your host. AffixIO sees GET /v1/auth/check. Remote proof operations throw LicenceOnlyError until you set licenceOnly to false.

Is every AffixIO proof zero-knowledge?

No. HMAC (scheme affix-light-v1, HMAC-SHA256) is the default and is not ZK. UltraHonk over bundled Noir circuits simple_yesno and yesno is the ZK path.

Which databases can AffixIO read?

AffixIO never queries your databases. Your host does. Adapters cover PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle, DB2, ODBC, plus JSON, CSV, XML, fixed-width, pipe, dBASE, LDIF, INI, MongoDB-style documents, Redis exports, and HTTP APIs via openDataStore.

Can I use AffixIO in the browser?

This package is Node.js 20+. There is no WASM prover in the tarball. Keep the key on a server you control. Pattern notes: /sdk-web/.

How do I start?

npm install affixio. npx affixio set-key --api-key aio_your_key. npx affixio prove --claim approved --offline. Then wire AffixSDK.prove in your host process.