apira-guard
v0.1.0
Published
Catches bots, abuse, and broken flows after Cloudflare, before PostHog.
Maintainers
Readme
Apira
Catches bots, abuse, and broken flows after Cloudflare, before PostHog.
Detect fake signups, risky actions, API abuse, and silent backend failures — locally, with no API key.
npm install apira-guardApira is a local, no-network layer. watchActions() and watchAccess() attach riskScore, riskLevel, and reasons to matched form submits and HTTP requests. watchSignups() / watchForms() listen for conversion submits and fire signupguard:submit with intent metadata (no risk payload). protectForms() blocks obvious bad email and phone values before submit.
No account. No dashboard. No outbound calls.
Common use cases
Use Apira to:
- block or flag fake or junk signups (
protectForms, then optionalwatchActionsrisk) - detect disposable emails and fake-looking phones at submit time (
protectForms) - score checkout, password reset, account update, invite, waitlist, demo request, and contact sales forms (
watchActions— match by form copy ordata-signup-guard-form) - detect forms submitted unrealistically fast (
watchActions) - detect repeated form submissions (
watchActions) - add request-risk scoring to Express, Next.js, or Node APIs
- detect request velocity
- detect sudden traffic spikes
- detect simple API enumeration like
/api/users/1,/api/users/2,/api/users/3 - send risk events to your own logs, database, Sentry, PostHog, or analytics stack
Add one line
Signup forms
import { watchSignups } from "apira-guard";
watchSignups();Auto-detects signup forms with email or phone fields and fires signupguard:submit on submit (detail: intent, timestamp, metadata). It does not inject signupGuardRisk — use watchActions({ action: "signup" }) (plus matching form text or data-signup-guard-form="signup") when you want the same risk JSON on signup flows.
Use protectForms() if you want to block obvious bad submissions (invalid / disposable email, bad phone shapes).
Important actions
Protect conversion-critical forms like checkout, password reset, account update, invite, waitlist, demo request, and contact sales.
import { watchActions } from "apira-guard";
watchActions({ action: "checkout" });Apira watches the matching form, computes risk on submit, injects signupGuardRisk into the form body, and fires signupguard:action.
watchActions({
action: "checkout",
onAction(event) {
console.log(event.action, event.risk);
}
});Target a form explicitly:
<form data-signup-guard-form="checkout">
...
</form>API routes
Watch server-side requests for lightweight API abuse signals.
import { watchAccess } from "apira-guard/server";
app.use(watchAccess());Apira attaches risk directly to the request:
app.get("/api/users/:id", (req, res) => {
if (req.signupGuardRisk?.riskLevel === "high") {
return res.status(429).json({ error: "rate limited" });
}
res.json({ ok: true });
});Configure velocity and spike detection:
watchAccess({
velocityThreshold: 60,
spikeThreshold: 20,
velocityWindowMs: 60_000
});Risk output
watchActions() and watchAccess() both use the same payload shape:
{
riskScore: 85,
riskLevel: "high",
reasons: ["velocity", "fast_action", "enumeration"]
}| Reason | Where | What it means |
| ------------------------ | -------------- | ------------- |
| velocity | forms (watchActions), APIs | Too many submits or requests in the sliding window (default 60 s on the server; client watchActions uses a stricter per-form threshold) |
| fast_action | forms (watchActions) | Submitted faster than the fast-action threshold after the watcher attached |
| retry | forms (watchActions) | Same form submitted again |
| enumeration | APIs | Sequential numeric IDs in the URL path (run ≥ 3) |
| spike | APIs | Burst of requests in a 10 s window |
| endpoint_concentration | APIs | Same normalised endpoint hit repeatedly in the velocity window |
| probing | APIs | High 4xx rate from this client (after enough samples) |
| flow_anomaly | APIs | Write request with no prior read on the same key (bot-style shortcut) |
Score thresholds: low < 40 · medium 40–69 · high ≥ 70.
Can Apira block risk?
Yes. Apira can run as a signal layer or a blocking layer.
Signal mode: watchActions() injects a hidden signupGuardRisk JSON field on submit; watchAccess() sets req.signupGuardRisk on each request.
const risk = JSON.parse(req.body.signupGuardRisk ?? "{}");Block mode stops obvious bad activity before it continues.
import { protectForms } from "apira-guard";
protectForms();For APIs, block high-risk requests in middleware:
app.use(watchAccess());
app.use((req, res, next) => {
if (req.signupGuardRisk?.riskLevel === "high") {
return res.status(429).json({ error: "Too many suspicious requests" });
}
next();
});Apira can reduce obvious abuse. It does not guarantee fraud prevention or stop sophisticated attackers.
Reading risk on the server
watchActions() injects a hidden field on matched forms:
app.post("/checkout", (req, res) => {
const risk = JSON.parse(req.body.signupGuardRisk ?? "{}");
if (risk.riskLevel === "high") {
return res.status(422).json({ error: "Suspicious request" });
}
res.json({ ok: true });
});API routes get risk directly:
req.signupGuardRisk;For TypeScript Express apps:
declare global {
namespace Express {
interface Request {
signupGuardRisk?: import("apira-guard/server").RiskResult;
}
}
}Signup-first, conversion-ready
watchSignups() is shorthand for:
watchForms({ intent: "signup" });For any conversion-critical form, use watchForms():
import { watchForms } from "apira-guard";
watchForms({
intent: "demo_request",
metadata: { page: "pricing" },
onSubmit(event) {
console.log(event.intent, event.timestamp, event.metadata);
}
});Opt a form in or out:
<form data-signup-guard-form="checkout">...</form>
<form data-signup-guard-ignore>...</form>What protectForms() blocks
import { protectForms } from "apira-guard";
protectForms();Blocks obvious garbage at submit time using browser validation messages:
- invalid email formats
- disposable email domains
- invalid phone formats
- fake-looking phones like
0000000000,1234567890, and555-01xx
What ships in npm
watchSignups()watchForms()watchActions()protectForms()watchAccess()- shared risk payloads
- local scoring rules
- reason codes
- TypeScript types
Everything runs inside your app. Apira does not call external services.
What is not included
Not included today:
- hosted dashboard
- shared reputation network
- external IP reputation checks
- external email verification APIs
- ML fraud models
These may become optional later. The npm package works without them.
What Apira is not
Apira is not a CAPTCHA, identity verification system, hosted fraud platform, or replacement for server-side validation.
It is a local first line of defense for forms, actions, and API routes — the layer between edge protection and your analytics/logging stack.
