@a3api/signals
v0.1.2
Published
Browser signal collector for A3 Age Assurance API
Downloads
337
Readme
@a3api/signals
Drop-in browser SDK for the Arcadia Age API (A3). Passively collects behavioral and contextual signals from standard browser events and packages them into the exact payload shape expected by POST /v1/assurance/assess-age.
- < 5 KB gzipped
- Zero runtime dependencies
- Zero PII — no raw text, no images, no user content
- Vanilla JS, React, and Vue entrypoints
- ESM + CJS + full TypeScript types
Why?
California AB 1043 mandates age assurance, but browsers have no OS-level age signal. This SDK fills that gap by collecting touch precision, scroll velocity, form timing, input complexity, device context, and referrer data — the signals A3's fusion engine needs to produce a verdict on the web.
The SDK does not make API calls. You forward the collected signals from your backend, where your API key stays secure.
Install
npm install @a3api/signalsQuick Start
Vanilla JS
import { createSignalCollector } from '@a3api/signals';
const collector = createSignalCollector();
// ... user interacts with your page ...
const signals = collector.getSignals();
// signals = { behavioral_metrics, input_complexity, device_context, contextual_signals }
// Send signals to your backend, which forwards them to A3
await fetch('/api/assess-age', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
os_signal: 'not-available',
user_country_code: 'US',
...signals,
}),
});
// Clean up when done
collector.destroy();React
import { useSignalCollector } from '@a3api/signals/react';
function AgeGate() {
const { getSignals, isReady } = useSignalCollector();
const handleSubmit = async () => {
const signals = getSignals();
if (!signals) return;
await fetch('/api/assess-age', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
os_signal: 'not-available',
user_country_code: 'US',
...signals,
}),
});
};
return <button onClick={handleSubmit} disabled={!isReady}>Continue</button>;
}Vue
<script setup>
import { useSignalCollector } from '@a3api/signals/vue';
const { getSignals, isReady } = useSignalCollector();
async function handleSubmit() {
const signals = getSignals();
if (!signals) return;
await fetch('/api/assess-age', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
os_signal: 'not-available',
user_country_code: 'US',
...signals,
}),
});
}
</script>
<template>
<button @click="handleSubmit" :disabled="!isReady">Continue</button>
</template>What It Collects
| Category | Signals | Source | |----------|---------|--------| | Behavioral Metrics | Touch precision, scroll velocity, form completion time, autofill detection, pressure variance, multi-touch frequency | PointerEvent, WheelEvent, FocusEvent, AnimationEvent | | Input Complexity | Autocorrect rate, word complexity score | InputEvent | | Device Context | OS version, device model, high contrast mode, screen scale factor | navigator.userAgentData / userAgent, matchMedia, devicePixelRatio | | Contextual Signals | Referrer category, timezone offset | document.referrer, Date.getTimezoneOffset() |
All collection is passive — no prompts, no camera, no popups. The SDK listens to events the user is already generating.
Backend Integration
The SDK output matches the supplementary signal fields of POST /v1/assurance/assess-age. Your backend adds os_signal and user_country_code, then forwards to A3:
// Express.js example
app.post('/api/assess-age', async (req, res) => {
const response = await fetch('https://api.a3api.io/v1/assurance/assess-age', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.A3_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
os_signal: 'not-available',
user_country_code: 'US',
...req.body, // signals from the SDK
}),
});
const result = await response.json();
// Store result.verification_token in your audit log
res.json({ verdict: result.verdict, bracket: result.assessed_age_bracket });
});Never call the A3 API from the browser. Your API key must stay on your server. The SDK collects signals; your backend forwards them to A3.
API
createSignalCollector(options?)
Creates a new signal collector instance.
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| root | Document \| HTMLElement | document | Root element for event listeners |
Returns a SignalCollector with:
getSignals()— returns the currentSignalPayloadisReady—trueuntildestroy()is calleddestroy()— removes all event listeners
useSignalCollector() (React / Vue)
Framework binding that manages the collector lifecycle automatically.
Returns { getSignals, isReady } where getSignals() returns SignalPayload | null.
Links
License
MIT
