Docs · For engineers
Technical architecture
How it actually works: proof generation, double-spend detection, terminal behaviour offline, reconciliation when reconnecting, and security assumptions. No hand-waving, concrete architecture so you can evaluate the infrastructure.
Architecture overview
AffixIO is a stateless binary eligibility engine. The terminal (or edge device) sends an identifier and circuit context to the API; the API queries authoritative data sources in real time, applies circuit logic, and returns only Yes or No. No PII is stored. Offline-capable deployments generate local proofs that can be verified and reconciled when connectivity returns.
Proof generation
Proofs are generated in two modes.
Online
The terminal sends a verify request (identifier + circuit_id) to the AffixIO API. The API resolves the identifier against configured data sources, runs the circuit logic, and returns a signed binary result. The response is the proof: it can be logged or passed to downstream systems without exposing PII. Proof structure is deterministic and verifiable (signature + nonce + circuit binding).
Offline
When the terminal cannot reach the API, it can still produce a local proof: a commitment to the request (identifier, circuit, timestamp, device context) that does not reveal the identifier to later verifiers. The terminal stores this proof (or a digest) and optionally a safe default outcome (e.g. No). When connectivity returns, proofs are submitted for reconciliation; the backend resolves eligibility and detects double-spend (see below).
POST /v1/verify
"identifier": "<opaque or hashed>",
"circuit_id": "<circuit>"
// Response: binary result + proof material (no PII)
Developer quickstart with real API data
All examples below use the production base URL https://api.affix-io.com/v1. For sandbox testing, switch to https://sandbox.api.affix-io.com/v1. Authentication is via API key, Bearer token, or mTLS, as defined in openapi.json.
1. Discover circuits from the API
Use GET /circuits to see which eligibility circuits are available (IDs, sectors, and input methods). This is how you find a real circuit_id to plug into /verify.
curl -s \
"https://api.affix-io.com/v1/circuits" \
-H "Authorization: Bearer <YOUR_API_KEY>" | jq '.circuits[0:3]'
// Truncated example response
{
"circuits": [
{ "id": "health-age-verification", "sector": "health", "description": "Check age >= configured threshold without exposing DOB" },
{ "id": "finance-kyc-verification", "sector": "finance" },
{ "id": "govt-voting-eligibility", "sector": "government" }
],
"total": <N>
}
2. Run a real verification: POST /verify
The core operation is /verify. You send a pseudonymised identifier plus a circuit_id from /circuits. The API returns a simple eligible boolean, circuit ID, optional proof token, and latency. No identifier or PII is retained: data_retained is always null.
# echo -n "user-123@example.com" | sha256sum -> 3a7bd3e2...
# Call /verify with hashed identifier
curl -s -X POST \
"https://api.affix-io.com/v1/verify" \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"identifier": "3a7bd3e27785e7b7a6c0e7c1f4f3f6b8",
"circuit_id": "health-age-verification",
"metadata": {"timestamp": 1731000000, "source": "checkout-web"}
}'
// Real response shape from /verify (fields from openapi.json)
{
"eligible": true,
"circuit_id": "health-age-verification",
"token": "<JWT_VERIFICATION_TOKEN>",
"expires": "2026-03-10T12:00:00Z",
"latency_ms": 43,
"data_retained": null
}
3. Re-verify a token without re-running the circuit
If you cache the token from /verify, you can check it later via GET /tokens/{id}/verify without touching the original data source again.
curl -s \
"https://api.affix-io.com/v1/tokens/<JWT_VERIFICATION_TOKEN>/verify" \
-H "Authorization: Bearer <YOUR_API_KEY>"
{
"valid": true,
"eligible": true,
"circuit_id": "health-age-verification",
"issued_at": "2026-03-10T11:59:12Z",
"expires_at": "2026-03-10T12:00:00Z"
}
Double-spend detection
Double-spend is the risk that the same entitlement or spend is used more than once before the system can enforce a single use. AffixIO addresses this in two ways.
Online
Every verify request is evaluated against live data. If the data source encodes single-use (e.g. balance, one-time token, or consumed flag), the API sees the updated state on each call. Second use returns No once the source reflects consumption.
Offline and at reconciliation
Offline proofs are bound to a request context (identifier, circuit, time window, terminal/device id). When the terminal reconnects, it submits the list of proofs for reconciliation. The backend replays eligibility against the same data sources and time semantics. If multiple proofs would consume the same entitlement, only the first valid one is accepted; others are rejected and can be flagged for audit. Deterministic ordering (e.g. by timestamp and device id) prevents ambiguity. No PII is stored; only proof digests and outcomes are used for double-spend checks.
Terminal behaviour offline
When the terminal loses connectivity:
- No fail-open. The terminal does not assume eligibility. It either uses a cached/signed default (e.g. No) or waits for connectivity.
- Local proof generation. For each action that would have been a verify call, the terminal can generate and store a local proof (commitment). This preserves an audit trail and enables reconciliation later.
- Verifiable no-decision. The terminal can return a clear, verifiable outcome (e.g. “no” or “pending”) so the user experience is defined and auditable. No silent failure and no guesswork.
- No PII on device. Raw identifiers or PII are not stored on the terminal for later sync; only proof material and digests are kept, reducing breach impact if the device is lost or compromised.
Reconciliation when reconnecting
When the terminal reconnects:
- Submit proofs. The terminal sends the list of offline proofs (or their digests) to the AffixIO reconciliation endpoint.
- Replay and validate. The backend re-evaluates each proof against the same circuit and data sources (with consistent time/ordering rules). It returns which proofs are valid and which are not (e.g. double-spend or expired).
- Settle. Valid proofs can be marked as consumed; invalid ones are rejected. The terminal can update local state (e.g. show success/failure to the user) and drop or retry as per policy.
- Audit. All reconciliation results are auditable. No PII is retained; proof ids and outcomes suffice for compliance and dispute resolution.
Reconciliation is idempotent: submitting the same proof set twice does not change the outcome after the first successful run.
Security assumptions
Evaluating the infrastructure requires clear assumptions. We state them explicitly.
Eligibility is as trustworthy as the data sources you connect to AffixIO. We do not alter source data; we query and apply circuit logic. If a source is compromised or stale, results reflect that.
Circuits are versioned and identified by circuit_id. Tampering with circuit logic (e.g. on the client) does not change the result returned by the API; the API runs the canonical circuit. Offline proofs are bound to the circuit so replay uses the same rules.
AffixIO does not store PII. We process requests in real time and return binary results (and proof material). Logs and reconciliation data do not include raw identifiers or personal data.
HTTPS (or equivalent) protects data in transit. Terminal compromise can lead to local proof generation with attacker-controlled inputs; reconciliation still enforces double-spend and eligibility against authoritative sources. Secure boot and attestation on the terminal strengthen the model but are not required for the API contract.
Offline reconciliation relies on deterministic ordering (timestamp, sequence, device id) to resolve conflicts. Clocks should be loosely synchronized; if not, policies can define acceptance windows or fallbacks.