← All Posts

Add Spam Filtering to Your Nostr Client in 10 Lines

2026-02-10 — nostr, wot, nip85, spam, api

The Problem

Every Nostr client has spam. Bots, impersonators, follow-farmers. If you're building a client, you need trust scoring. But computing PageRank over 51K+ nodes and 620K edges isn't something you want to do client-side.

The API

The WoT Scoring API at wot.klabo.world does the heavy lifting. It crawls the follow graph every 6 hours, runs PageRank, publishes NIP-85 trust assertions, and exposes 49 endpoints for trust analysis.

The simplest integration — a trust score for any pubkey:

const res = await fetch(
  'https://wot.klabo.world/score?pubkey=' + hexPubkey
);
const data = await res.json();
// data.composite_score: 0-100 trust score
// data.followers: follower count
// data.found: true if pubkey is in the graph

That's it. One call. The composite_score blends PageRank with external NIP-85 assertions (70% internal, 30% external).

Filter Spam in Your Feed

Score every event author and hide low-trust accounts:

async function shouldShowEvent(event) {
  const res = await fetch(
    'https://wot.klabo.world/score?pubkey=' + event.pubkey
  );
  const data = await res.json();
  if (!data.found) return false;     // unknown = suspicious
  if (data.composite_score < 3) return false; // likely spam
  return true;
}

A score of 3+ filters out most spam while keeping real accounts. Adjust the threshold based on your client's needs — stricter clients might use 5+, permissive ones might use 1+.

Detect Bots

The /sybil endpoint runs 8 behavioral signals to classify accounts:

const res = await fetch(
  'https://wot.klabo.world/sybil?pubkey=' + hexPubkey
);
const data = await res.json();
// data.classification: "genuine", "likely_genuine",
//                      "suspicious", "likely_sybil"
// data.spam_probability: 0-100

Show a warning badge for "suspicious" or "likely_sybil" accounts. The signals include follower-to-following ratio, mutual follow density, engagement consistency, and follow velocity.

Show Trust Context

For profile pages, the /reputation endpoint gives a rich breakdown:

const res = await fetch(
  'https://wot.klabo.world/reputation?pubkey=' + hexPubkey
);
const data = await res.json();
// data.overall_grade: "A", "B", "C", "D", "F"
// data.trust_score, data.sybil_resistance,
// data.community_standing, data.anomaly_risk

Display the letter grade on profiles. "A" accounts are well-connected, long-standing, with diverse followers. "F" accounts have anomalous patterns.

Free Tier

50 requests per day per IP are free. After that, the API returns 402 with a Lightning invoice. For client-side usage, 50/day is usually enough — cache scores locally and refresh periodically.

For server-side integration with higher volume, the L402 flow is straightforward:

  1. Request returns 402 + WWW-Authenticate header with invoice
  2. Pay the invoice (1-10 sats depending on endpoint)
  3. Retry with Authorization: L402 <payment_hash>

JavaScript SDK

For a batteries-included integration:

npm install nostr-wot
import { WoTClient } from 'nostr-wot';

const wot = new WoTClient();
const score = await wot.score(hexPubkey);
const sybil = await wot.sybilCheck(hexPubkey);
const rep = await wot.reputation(hexPubkey);

Source: github.com/joelklabo/nostr-wot

Full API Docs

49 endpoints total covering trust scoring, sybil detection, anomaly detection, influence analysis, trust circles, follow quality, network health, link prediction, and cross-provider verification.

Found this useful?

Send a tip via Lightning. One click, no account needed.

Tip 100 sats ⚡