AffixIO Documentation
AffixIO is a decision engine API: run binary yes/no eligibility and verification across healthcare, government, finance, retail, education, and 17+ sectors. Every decision can produce a compact, verifiable proof without exposing raw data. It also powers agentic payments and offline-capable flows for edge and AI.
https://api.affix-io.com
What you can do
- Run binary eligibility and verification — yes/no decisions across healthcare, government, finance, retail, education, and 17+ sectors. No raw data stored; stateless and privacy-preserving.
- Use 100+ pre-built circuits — KYC, age verification, employment, income, compliance, and more. Call a circuit with input data; get a decision and an optional proof.
- Verify decisions without seeing raw data — proofs confirm a decision was valid without revealing underlying inputs. Any party can verify independently.
- Accept agentic payments offline — optional merchant SDK for card payments without internet. Double-spend prevention on-device; compact proofs under 90 bytes. Built for AI agents and edge hardware.
Agentic Payments
The same decision engine powers agentic payments: transactions initiated and completed by AI agents, autonomous systems, or edge hardware, with no human approving each step. AffixIO is built for this model.
Traditional payment APIs are built around a human completing a checkout and waiting on a server response. Agentic systems don't work that way. An AI agent orchestrating a supply chain doesn't wait for a checkout page. A delivery robot completing a transaction at a locker doesn't have a browser. A POS terminal in a remote location can't assume it has internet at the moment of sale.
AffixIO solves all of this. The payment decision, the proof, and the double-spend check all happen on the device, in under a millisecond, without any network call. The agent gets a definitive accept/reject response immediately. The cryptographic proof is uploaded when connectivity allows.
Why AffixIO for agentic payment systems
Agentic payment flow
Here is the complete agentic payment flow, from card presentation to server confirmation:
1. Card token arrives from POS hardware or agent input
↓
2. SDK generates nullifier (SHA-256 of card token)
↓
3. Nullifier checked against local store ← sub-millisecond, no network
→ DUPLICATE: reject immediately
→ NEW: continue
↓
4. Nullifier recorded in local store ← prevents double-spend
↓
5. Compact proof generated on-device ← HMAC-SHA256, < 90 bytes
↓
6. Online? → validate + sync to API immediately
Offline? → queue locally, return accepted: true
↓
7. Agent receives PaymentResult instantly
↓
8. (Background) Queue flushes to API on next connectivity
Integrating with AI agent frameworks
The SDK is a standard ES module and integrates cleanly into any AI agent framework. Here is an example of an agent tool that accepts a payment:
import { AffixioMerchant } from 'affixiomerchant';
// Initialise once at agent startup
const merchant = new AffixioMerchant({
apiKey: process.env.AFFIXIO_API_KEY,
memoryOnly: true, // or use storageDir for persistence
});
await merchant.setup();
// Expose as an agent tool
export async function acceptPayment(
cardToken: string,
amountGBP: number,
method: 'chip' | 'contactless' | 'nfc' = 'contactless'
) {
const result = await merchant.pay({
cardToken,
amount: amountGBP,
currency: 'GBP',
paymentMethod: method,
});
// Agent gets an instant, deterministic result
return {
success: result.accepted,
transactionId: result.transactionId,
proofSize: result.proofBytes, // always ≤ 90 bytes
isOnline: merchant.isOnline,
pendingSync: await merchant.queueSize(),
reason: result.error ?? null,
};
}
Autonomous fleet deployments
For fleets of autonomous terminals (delivery robots, vending machines, kiosks, distributed IoT payment nodes), register each device as a named terminal. This gives you per-terminal transaction history, centralised audit logs, and server-side fraud monitoring across the entire fleet.
const merchant = new AffixioMerchant({
apiKey: process.env.AFFIXIO_API_KEY,
terminalId: process.env.TERMINAL_ID, // e.g. 'ROBOT-042'
terminalSecret: process.env.TERMINAL_SECRET, // from registration
storageDir: '/var/lib/affixio', // persist across reboots
});
await merchant.setup();
// Terminal authenticates, loads saved queue, starts connectivity monitor
Authentication
Every request to /api/* must include your API key as a Bearer token in the Authorization header.
Authorization: Bearer affix_your_api_key_here
Quickstart
The fastest way to verify the API is working is to hit the health endpoint. No auth needed.
curl https://api.affix-io.com/health
{
"status": "ok",
"postgres": "connected",
"redis": "connected",
"timestamp": "2026-03-02T12:00:00.000Z"
}
Once you have an API key, try generating your first proof:
curl -X POST https://api.affix-io.com/api/proof/generate \
-H "Authorization: Bearer affix_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"userId": "user_001",
"rules": ["age_check"],
"data": { "age": 25 }
}'
{
"proofId": "prf_1234567890_abc",
"proof": "...",
"publicInputs": ["1"],
"timestamp": "2026-03-02T12:00:00.000Z",
"proofSize": 49
}
Merchant SDK
The affixiomerchant SDK is a lightweight library (under 33KB) for accepting offline ZK-proofed card payments on any terminal. It works in Node.js 18+, the browser, and Deno — no native dependencies.
Installation
npm install affixiomerchant
Setup
Create one AffixioMerchant instance per terminal at startup and call setup(). This authenticates your terminal and starts monitoring for connectivity.
import { AffixioMerchant } from 'affixiomerchant';
const merchant = new AffixioMerchant({
apiKey: process.env.AFFIXIO_API_KEY, // 'affix_...'
});
await merchant.setup();
// Terminal is now ready to take payments
Configuration options
| Option | Type | Description |
|---|---|---|
| apiKey | string | Your API key — must start with affix_. Required. |
| terminalId | string | optional Terminal ID registered in the dashboard. Enables server-side validation. |
| terminalSecret | string | optional Terminal secret from registration. Required if terminalId is set. |
| baseUrl | string | optional API base URL. Defaults to https://api.affix-io.com. |
| storageDir | string | optional Directory to persist nullifiers and the offline queue. Defaults to .affixio in the working directory. |
| memoryOnly | boolean | optional Disable disk persistence entirely (everything is in-memory). Defaults to false. |
Taking a payment
Call merchant.pay() with the card token from your POS hardware and the payment amount. It returns immediately, with no network round-trip.
const result = await merchant.pay({
cardToken: 'tok_chip_abc123', // from your POS reader
amount: 24.99,
currency: 'GBP',
paymentMethod: 'chip', // 'chip' | 'contactless' | 'nfc' | 'qr' | 'swipe'
});
if (result.accepted) {
console.log('Payment accepted');
console.log('Transaction ID:', result.transactionId);
console.log('Synced to server:', result.synced); // false if offline
console.log('Queued for later:', result.queued); // true if offline
} else {
console.log('Payment declined:', result.error);
}
Payment input fields
| Field | Type | Description |
|---|---|---|
| cardToken | string | Pre-tokenised card reference from your POS hardware. Never the raw card number. |
| amount | number | Amount in major currency units. e.g. 10.50 = £10.50. |
| currency | string | optional ISO 4217 code. Defaults to GBP. |
| paymentMethod | string | optional chip, contactless, nfc, qr, or swipe. |
| customerId | string | optional Your internal customer ID for reconciliation. |
| transactionId | string | optional Supply your own unique ID to guarantee idempotency. Auto-generated if omitted. |
| metadata | object | optional Any extra key/value data to attach to the transaction. |
Payment result fields
| Field | Type | Description |
|---|---|---|
| accepted | boolean | true = payment accepted. false = rejected (see error). |
| transactionId | string | Unique transaction ID. Use this to look the payment up later. |
| proofBytes | number | Size of the generated proof. Always ≤ 90 bytes. |
| synced | boolean | true if the transaction was immediately confirmed with the server. |
| queued | boolean | true if the device is offline and the transaction is held locally. |
| error | string | Human-readable reason for rejection. Only set when accepted is false. |
Offline payments
The SDK works completely without an internet connection. When a payment is processed offline:
- A compact proof is generated on-device using the card token (card data never leaves the device).
- A nullifier is stored locally to prevent the same card being used twice before syncing.
- The transaction is added to an encrypted local queue.
- As soon as connectivity is detected, the queue flushes automatically in the background.
Check connectivity status at any time:
merchant.isOnline // boolean const pending = await merchant.queueSize(); // number of unsynced transactions
How the ping works
The SDK polls /health aggressively on startup (starting at 1ms intervals) to detect connectivity as fast as possible. Once detected as offline, it backs off exponentially to a maximum of 5 seconds between checks. On reconnect, it immediately flushes the queue.
Manually syncing
The queue flushes automatically on reconnect, but you can also trigger a sync at any time:
const result = await merchant.syncNow();
console.log(result.uploaded); // number of transactions successfully sent
console.log(result.failed); // number that failed (will retry on next sync)
console.log(result.errors); // [{ transactionId, error }]
Persistent storage
By default the SDK writes its nullifier store and offline queue to .affixio/affixio-state.json in your working directory. This means the queue and double-spend protection survive a process restart.
// Custom storage directory
const merchant = new AffixioMerchant({
apiKey: process.env.AFFIXIO_API_KEY,
storageDir: '/var/lib/pos/affixio',
});
// Disable persistence (testing / ephemeral environments)
const merchant = new AffixioMerchant({
apiKey: process.env.AFFIXIO_API_KEY,
memoryOnly: true,
});
Shutting down cleanly
Call destroy() before your process exits to flush any pending writes and stop the connectivity monitor:
process.on('SIGTERM', async () => {
await merchant.destroy();
process.exit(0);
});
Full SDK reference
| Method / Property | Returns | Description |
|---|---|---|
| new AffixioMerchant(config) | — | Create a new merchant instance. Does not connect yet. |
| merchant.setup() | Promise<void> | Authenticate terminal, load storage, start ping loop. Call once at startup. |
| merchant.pay(input) | Promise<PaymentResult> | Process a payment. Works fully offline. Never throws — errors returned in result. |
| merchant.syncNow() | Promise<SyncResult> | Manually flush the offline queue to the server. |
| merchant.queueSize() | Promise<number> | Number of transactions waiting to be synced. |
| merchant.isOnline | boolean | Current connectivity status (live). |
| merchant.destroy() | Promise<void> | Stop the ping loop and flush storage. Call before process exit. |
The SDK also exports its building blocks if you need to use them directly:
import {
AffixioMerchant, // main class
generateProof, // low-level proof generation
DoubleSpendGuard, // nullifier store wrapper
SyncEngine, // offline queue engine
PingMonitor, // connectivity monitor
FileStorage, // disk-backed storage
InMemoryNullifierStorage,
InMemoryQueueStorage,
// Zod schemas for validation:
MerchantConfigSchema,
PaymentInputSchema,
PaymentResultSchema,
} from 'affixiomerchant';
REST API
Health check
Returns the current status of the API and its dependencies. Useful as a connectivity ping target.
{
"status": "ok",
"postgres": "connected",
"redis": "connected",
"timestamp": "2026-03-02T12:00:00.000Z"
}
Proofs
Generate a ZK proof for a user against a set of rules. The proof can be shared with any verifier who does not need to see the underlying data.
{
"userId": "user_001",
"rules": ["age_check", "income_bracket"],
"data": { "age": 30, "income": 45000 },
"transactionAmount": 500.00, // optional — enables fraud scoring
"deviceId": "terminal_001" // optional
}
{
"proofId": "prf_1234567890_abc",
"proof": "...",
"publicInputs": ["1"],
"timestamp": "2026-03-02T12:00:00.000Z",
"proofSize": 49
}
Verify a proof returned by /api/proof/generate. Returns a boolean decision. The verifier never sees the original data.
{
"proof": "...",
"publicInputs": ["1"]
}
{
"valid": true,
"verified": true,
"timestamp": "2026-03-02T12:00:00.000Z"
}
List all proofs generated by your organisation. Supports ?limit=100&offset=0 pagination.
Circuits
Circuits are pre-built verification programs. Send data in, get a yes/no decision and a proof back. Over 100 circuits are available across multiple sectors.
List all available circuits and their descriptions.
Run a specific circuit. Replace {circuit-name} with the circuit slug, e.g. kyc, eligibility, or age-verification.
curl -X POST https://api.affix-io.com/api/circuits/age-verification \
-H "Authorization: Bearer affix_your_key" \
-H "Content-Type: application/json" \
-d '{ "userId": "u_001", "data": { "age": 19 } }'
{
"decision": true,
"verified": true,
"proof": "proof_...",
"proofId": "prf_...",
"circuitId": "age-verification",
"timestamp": "2026-03-02T12:00:00.000Z"
}
/api/sandbox/circuits/{name}, useful for development and testing.
Merchant terminals
Terminals are registered POS devices. Registering a terminal lets you use server-side payment validation and per-terminal transaction history.
{
"terminalId": "TERM-001",
"terminalName": "Counter 1",
"locationName": "Main Store",
"terminalType": "pos" // 'pos' | 'mobile' | 'kiosk' | 'vending'
}
{
"terminal": { "id": "...", "terminalId": "TERM-001", "status": "active" },
"terminalSecret": "tsec_..." // shown only once — store securely
}
Exchange a terminalId + terminalSecret for a short-lived terminal token. This is handled automatically by the SDK when you supply terminalId and terminalSecret in the config.
{
"terminalId": "TERM-001",
"terminalSecret": "tsec_..."
}
{
"token": "eyJ...",
"expiresAt": "2026-03-02T18:00:00.000Z",
"terminalId": "TERM-001"
}
Payments
Online double-spend check. Returns an immediate approve/deny before the transaction is recorded. The merchant SDK calls this automatically when online and a terminal token is available.
{
"transactionId": "txn_abc123",
"amount": 24.99,
"currency": "GBP",
"paymentMethod": "chip"
}
{
"approved": true,
"reason": null
}
Transaction sync
Upload a batch of offline transactions to the server. The merchant SDK calls this automatically. You only need this endpoint if you are building your own sync integration.
{
"transactions": [
{
"transactionId": "txn_abc123",
"proofData": { "p": "...", "n": "...", "t": 1740913200, "a": 2499 },
"amount": 24.99,
"currency": "GBP",
"paymentMethod": "chip"
}
]
}
{
"synced": 1,
"errors": []
}
Retrieve the transaction history for the authenticated terminal.
Consent
Log, verify, and revoke user consent decisions. Useful for GDPR compliance workflows.
{
"userId": "user_001",
"consentType": "data_processing",
"granted": true
}
Audit logs
Retrieve a full audit trail of all proof generation and verification events for your organisation. Supports ?limit=100&offset=0 pagination.
Same as above but with all user identifiers replaced by pseudonyms — suitable for sharing with third parties.
Errors
All errors return JSON with an error field describing what went wrong.
{
"error": "Invalid or expired API key."
}
| Status | Meaning |
|---|---|
| 200 / 201 | Success. 200 for reads, 201 for created resources. |
| 400 | Bad request — missing or invalid field in the request body. |
| 401 | Authentication failed — missing, invalid, or expired API key. |
| 403 | Forbidden — your account tier doesn't include this feature, or your monthly limit is reached. |
| 409 | Conflict — e.g. double-spend detected, or a terminal ID already exists. |
| 429 | Rate limit exceeded. Slow down and retry. |
| 500 | Server error. If this persists, check status. |
Sandbox
All API routes under /api/sandbox/* require no authentication and are free to use. Sandbox requests are never billed and never affect production data.
/api/sandbox/circuits/{name} instead of /api/circuits/{name} to test without an API key.
curl -X POST https://api.affix-io.com/api/sandbox/circuits/kyc \
-H "Content-Type: application/json" \
-d '{ "userId": "test_user", "data": { "name": "Jane Doe", "dob": "1990-01-01" } }'
Offline Payment Architecture
Every component of AffixIO's offline payment system is designed with one constraint: the network cannot be assumed. Connectivity is a nice-to-have, not a requirement.
The offline-first guarantee
When merchant.pay() is called, the following are always performed locally, with no network call, regardless of connectivity:
- Nullifier generation — a SHA-256 fingerprint of the card token, scoped to prevent cross-terminal replay.
- Double-spend check — the nullifier is looked up in the local store. Reject if found. This takes under 1ms on any hardware.
- Nullifier recording — the nullifier is written to the local store before any further processing. This prevents a race condition where two payments from the same card are accepted in rapid succession.
- Proof generation — an HMAC-SHA256 commitment is generated from the card token, amount, a random nonce, a merchant ID, and a Unix timestamp. The result is encoded as a base64url string under 90 bytes.
- Immediate response — the agent or terminal receives
accepted: trueoraccepted: falsewithout waiting for anything external.
The proof format
Each payment generates a CompactProof — a four-field structure that serialises to under 90 bytes:
{
p: string, // HMAC-SHA256 commitment (base64url, 24 chars)
n: string, // nullifier — SHA-256(cardToken) truncated (base64url, 24 chars)
t: number, // Unix timestamp (seconds)
a: number // amount in minor units (pence / cents)
}
// Serialised: "p.n" — always 49 characters, well under 90 bytes
The proof is opaque to the merchant. No card data, no cardholder name, no PAN. The server receives the proof and can verify it is internally consistent without ever seeing the original card token.
Local storage and persistence
The SDK writes two data structures to disk (by default in .affixio/affixio-state.json):
- Nullifier store — a set of all card nullifiers seen on this terminal. Prevents double-spending across process restarts and power cycles.
- Offline queue — all transactions that have not yet been uploaded. Each entry includes the full proof, amount, currency, and metadata.
Writes are debounced and batched: a 50ms delay before writing to disk prevents high-frequency payments from overwhelming I/O on slower hardware.
Sync and the upload queue
When connectivity is detected, the SDK flushes the offline queue to POST /api/merchant/transactions/sync in batches of 50 transactions. The server processes each batch and returns a list of any that failed. Successfully uploaded transactions are removed from the queue; failures are retained and retried on the next sync. The batch size of 50 is chosen to balance throughput against the risk of a single large request timing out on a poor connection.
Connectivity monitoring
The SDK runs a lightweight ping loop against GET /health with a 500ms request timeout. The interval schedule is:
Attempts: 1 2 3 4 5 6 7 8 9 10+ Interval: 1ms 1ms 5ms 10ms 50ms 100ms 250ms 500ms 1000ms 2000ms → cap 5000ms On reconnect: queue flush triggered immediately, step counter reset to 0
The aggressive start (1ms, 1ms, 1ms) means the terminal detects a network becoming available within a single ping cycle, typically under 10ms from the moment the network is up. This matters for terminals that connect to intermittent Wi-Fi or cellular briefly.
Security properties
- Card data never leaves the device raw. The SDK only ever sees a pre-tokenised card reference; the raw PAN, CVV, and cardholder name are handled entirely by your POS hardware before the token is passed to the SDK.
- Proofs are non-reversible. You cannot derive the card token from the proof. The HMAC key is the card token itself; without the token, the proof is opaque.
- Nonces prevent replay. Each proof includes a random UUID nonce. Replaying the same proof bytes against the API will be rejected as the nonce-timestamp combination has already been seen.
- Server provides a second check. When the terminal syncs, the server performs its own independent double-spend verification. On-device and server-side checks are independent layers; neither alone is the sole safeguard.
Frequently Asked Questions
Common questions about AffixIO's agentic payments API and offline payment SDK.
What are agentic payments?
Agentic payments are financial transactions initiated and processed autonomously by AI agents, software systems, or edge hardware, without requiring human approval at each step. AffixIO is built for this model: payment decisions, cryptographic proofs, and double-spend checks all happen on-device with no network round-trip required. This makes AffixIO a good fit for AI agent frameworks, autonomous POS terminals, delivery robots, vending systems, and any stack where a human isn't present at the moment of payment.
Can AI agents process payments without human intervention?
Yes, entirely. An AI agent running the affixiomerchant SDK can accept card payments, generate verifiable proofs, reject double-spends, and queue transactions for upload without any human in the loop. The SDK's pay() method never throws: it returns a deterministic PaymentResult synchronously with a clear accepted: true/false decision. Agents can act on this result immediately.
How do offline card payments work without internet?
The SDK processes the entire payment on-device. A compact cryptographic proof is generated from the card token using HMAC-SHA256. A nullifier (a SHA-256 fingerprint of the card) is stored locally to prevent reuse. The transaction is queued in an encrypted local store. The queue uploads automatically the instant connectivity returns, with reconnection detection starting at 1ms intervals. No configuration required.
How does double-spend prevention work offline?
Before accepting any payment, the SDK derives a nullifier from the card token and checks it against an in-memory (and optionally persisted) set. If the nullifier has been seen before, the payment is rejected immediately, in under a millisecond with no network call. This works across process restarts when disk persistence is enabled. When the terminal syncs with the server, a second independent double-spend check is performed server-side as an additional safeguard.
How small is an AffixIO payment proof?
Payment proofs are always under 90 bytes (typically 49 characters as a base64url string). This makes them small enough to transmit over low-bandwidth links, embed in a QR code payload, carry in an NFC tag, or log in a single database column. The compact format is intentional: it was designed for constrained POS hardware, IoT devices, and agentic systems where bandwidth and storage are limited.
How quickly does an offline terminal detect reconnection?
The SDK pings /health starting at 1ms intervals. On detecting a loss of connectivity, it backs off exponentially to a maximum of 5 seconds. The moment the network returns, the terminal detects it within one ping cycle (typically within 10ms of the network becoming available) and immediately flushes the offline queue. For most deployments this means transactions appear in the server dashboard within seconds of a terminal coming back online.
What programming environments does the SDK support?
The affixiomerchant SDK runs in Node.js 18+, any modern browser, and Deno. It uses only the standard Web Crypto API (crypto.subtle) for cryptographic operations, which is available natively in all three environments and in most edge runtimes. There are no native modules, no OS-level dependencies, and no build-time requirements beyond TypeScript compilation.
What payment methods does AffixIO support?
Chip, contactless, NFC, QR code, and swipe. The SDK is hardware-agnostic — it accepts any pre-tokenised card reference from your POS reader. The underlying proof system is token-based and does not depend on the card entry method, so integrating AffixIO does not require changes to existing terminal hardware.
Is AffixIO suitable for regulated industries?
Yes. AffixIO includes full audit logging for every proof and verification event, pseudonymised audit exports for third-party review, GDPR consent management endpoints, HSM support for hardware key management, and 100+ pre-built compliance circuits covering KYC, AML, eligibility verification, ISO 20022, LTI protocol translation, and regulatory reporting. All transactions produce independently verifiable cryptographic proofs — a regulator or auditor can confirm a payment was valid without accessing any raw card data.
How does AffixIO compare to traditional payment APIs for agentic use cases?
Traditional payment APIs (Stripe, Adyen, etc.) are built for human-initiated online transactions. They require a live network connection at the moment of payment, return results only after a server round-trip, and have no built-in concept of offline queuing or on-device double-spend prevention. AffixIO inverts these assumptions: the network is optional, decisions are instant, proofs are generated on-device, and the system is built for autonomous agents with intermittent connectivity, constrained hardware, and no human present.