API reference

One GET per subject, one header for auth. Every check returns a decision, the signals behind it, and the evidence behind each signal.

Endpoints

EndpointAuthPurpose
GET /healthnoLiveness. Returns { status: "ok" } and nothing else.
GET /readynoReadiness: database reachable, index populated, and whether the feeds are stale.
GET /v1/check/ip/{ip}yesIP reputation. IPv4 and IPv6.
GET /v1/check/domain/{domain}yesDomain blocklists, plus the reputation of the IPs it resolves to.
GET /v1/check/email/{email}yesSyntax, domain lists, MX, mail server reputation, role accounts, typos.
GET /v1/signalsyesThe signal catalogue: every id this instance can emit, with meaning and guidance.
GET /v1/profilesyesThe policy profiles this instance offers, with thresholds and overrides.

Choosing a policy

Add ?profile=<name> to any check. Same signals, different thresholds and weights — a checkout should be stricter than a newsletter signup. An unknown name returns 400 with the list of valid ones, and the signal reference shows what each profile on this instance does.

bash
# same address, three answers
curl -H 'X-API-Key: <key>' '<ENGINE>/v1/check/email/kunde@gmail.com?profile=newsletter'  # allow
curl -H 'X-API-Key: <key>' '<ENGINE>/v1/check/email/kunde@gmail.com?profile=lead'        # challenge
curl -H 'X-API-Key: <key>' '<ENGINE>/v1/check/email/x@mailinator.com?profile=lead'       # block

Response

json
{
  "request_id": "4b1f…",
  "ts": 1758045123,
  "profile": "checkout",
  "decision": "challenge",
  "action": {
    "type": "soft_block",
    "retry_after_seconds": 300,
    "reason": "score_in_challenge_band"
  },
  "score": 40,
  "confidence": 0.7,
  "signals": [
    {
      "id": "feed:tor_exits:0",
      "category": "anonymizer",
      "weight": 40,
      "match": "185.220.101.1",
      "source": "tor_exits",
      "severity": "high"
    }
  ],
  "evidence": [
    {
      "source": "tor_exits",
      "category": "anonymizer",
      "match": "185.220.101.1",
      "weight": 40,
      "note": null
    }
  ],
  "enrichment": {
    "ip": "185.220.101.1",
    "country": "Germany",
    "country_iso": "DE",
    "asn": 205100,
    "asn_organization": "Example AS",
    "hostname": "tor-exit.example"
  },
  "timing_ms": {
    "total": 12
  }
}
FieldMeaning
decisionallow | challenge | block — the answer you act on
scoreSum of signal weights under the active profile, capped at 100
confidence0.2 with no signals, then 0.5 / 0.7 / 0.9 as total weight passes 0 / 30 / 80. How much evidence there is — not how likely abuse is
signalsOne entry per finding, with the weight it contributed
evidenceThe same findings keyed by source, for showing a human why
enrichmentFacts gathered along the way: resolved IPs, MX hosts, deliverability, did_you_mean, geo/ASN
actionPresent on challenge only: retry_after_seconds and a machine-readable reason
profileThe policy profile that produced this decision
request_idCorrelates with the engine log line for this request

Errors

StatusMeaningCause
401Missing API keyNo X-API-Key header was sent.
403Invalid API keyThe key is not known to this instance.
400unknown_profileThe profile does not exist here. The response body lists the ones that do.
429Rate limited60 requests per minute by default, 10 for the email check. Configurable per instance.

Treat an error as “no answer”, not as “block”. The client packages do this for you.

curl

bash
curl <ENGINE>/health
curl <ENGINE>/ready
curl -H 'X-API-Key: <key>' <ENGINE>/v1/check/ip/185.220.101.1
curl -H 'X-API-Key: <key>' <ENGINE>/v1/check/domain/example.com
curl -H 'X-API-Key: <key>' <ENGINE>/v1/check/email/name@example.com
curl -H 'X-API-Key: <key>' <ENGINE>/v1/profiles
curl -H 'X-API-Key: <key>' '<ENGINE>/v1/signals?signal=email:no_mx'