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.
Docs
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.
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/.
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.
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.
| 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.
new AffixSDK({ apiKey, ... }). apiKey is required on the object (the stored key from set-key also works for the CLI). Useful fields from AffixSdkConfig:
| 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.
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.
| 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 |
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 (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.
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.
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.
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.
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.
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.
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",
});
| 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.
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.
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+).
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.
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.
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
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.
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}
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).
Same SDK. Different hosts. The store and the gate change. The prove call does not.
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.
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.
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.
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.
maxUses: 255 or "unlimited". Same carrier. Spend journal still hash-chains each consume. Gate id separates doors.
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.
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.
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.
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/).
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.
InMemorySqlDatabase plus createMemoryDocumentStore. HMAC prove in the unit test. UltraHonk only when you mean to load Barretenberg.
With remote sync on, buildMerkleBatch({ maxItems: 50000, includeProofBytes: false }) then flush. Use it for a clinic or depot that ran offline all day.
setHsmProfile, credentials in env, npx affixio hsm test before go-live. Profile never holds the PIN.
The verify-proof Action against api.affix-io.com before a deploy that is supposed to carry a live proof.
A machine with Node 20, npx affixio menu, a Hub key, and a store. No application framework required. CLI prints JSON. Pipe it.
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.valid: false and a reason. CLI exit 2. Do not treat that as a thrown parser failure.double_spend_detected is a spent digest, not a transport failure. 429 has Retry-After. See How it Works..affix/secrets/, mode 0600. .affix/ is excluded from the published 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.
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.
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.
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 briefAnswers match the npm package, not a second SDK that is not 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.
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.
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.
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.
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/.
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.