GRAPEVINE_API.md raw

GrapeVine WoT Influence Scoring API

The GrapeVine API computes Web of Trust influence scores using an iterative convergence algorithm over the follow graph. It uses Badger's materialized graph indexes (ppg/gpp) for fast traversal, avoiding the need for Neo4j or SQL databases.

Enabling

The API is disabled by default. Enable it with:

ORLY_GRAPEVINE_ENABLED=true

This requires the Badger database backend (ORLY_DB_TYPE=badger, which is the default).

Configuration

VariableDefaultDescription
ORLY_GRAPEVINE_ENABLEDfalseEnable the GrapeVine scoring API
ORLY_GRAPEVINE_MAX_DEPTH6Maximum BFS depth for follow graph traversal
ORLY_GRAPEVINE_CYCLES5Number of convergence iterations
ORLY_GRAPEVINE_ATTENUATION0.8Weight decay factor per hop (0-1). Lower = faster decay.
ORLY_GRAPEVINE_RIGOR0.25Certainty curve steepness (0-1). Lower = more certainty from fewer inputs.
ORLY_GRAPEVINE_FOLLOW_CONFIDENCE0.05Base confidence weight for a follow edge
ORLY_GRAPEVINE_OBSERVERSComma-separated hex pubkeys to auto-calculate scores for
ORLY_GRAPEVINE_REFRESH6hHow often to recalculate scores for configured observers

Example

ORLY_GRAPEVINE_ENABLED=true
ORLY_GRAPEVINE_OBSERVERS=abc123...hex64,def456...hex64
ORLY_GRAPEVINE_REFRESH=4h
ORLY_GRAPEVINE_MAX_DEPTH=4

Algorithm

The scoring algorithm has two components:

Influence Scores (GrapeVine convergence)

  1. Starting from the observer's pubkey, BFS traverses the follow graph to MAX_DEPTH hops
  2. All pubkeys in this set get an initial influence of 0.0; the observer starts at 1.0
  3. For CYCLES iterations:

- For each pubkey (ratee) in the set: - Find all followers of that ratee who are also in the set - Each follower's "vote" is weighted by: ATTENUATION * follower_influence * FOLLOW_CONFIDENCE - The observer's own follows get no attenuation (direct trust) - average = sum_of_products / sum_of_weights - certainty = 1 - exp(-sum_of_weights * -ln(RIGOR)) - influence = average * certainty

  1. Scores stabilize after a few iterations (typically 3-5)

WoT Intersection Scores

For each pubkey in the hop set, count how many of the observer's direct follows also follow that pubkey. This gives a simple "social proof" metric independent of the influence calculation.

Placeholders

Mute lists (kind 10000) and report events (kind 1984) are not yet factored into the calculation. The algorithm has TODO hooks for integrating these signals.

API Endpoints

All endpoints require NIP-98 HTTP authentication. Use the nurl tool for testing:

# Build nurl
go build -o nurl ./cmd/nurl

GET /api/grapevine/scores

Returns all scores for an observer.

Parameters:

Authorization: Any authenticated user can query their own scores. Owner/admin can query any observer.

Example:

NOSTR_SECRET_KEY=nsec1... ./nurl "https://relay.example.com/api/grapevine/scores?observer=abc123..."

Response (200):

{
  "observer": "abc123...",
  "scores": [
    {
      "pubkey": "def456...",
      "influence": 0.85,
      "average": 0.90,
      "certainty": 0.94,
      "input": 0.15,
      "wot_score": 42,
      "depth": 1
    }
  ],
  "computed_at": "2026-03-12T10:30:00Z",
  "compute_ms": 4500,
  "total_pubkeys": 1234
}

Response (404): No scores computed yet for this observer.

GET /api/grapevine/score

Returns a single score for an observer+target pair.

Parameters:

Example:

NOSTR_SECRET_KEY=nsec1... ./nurl "https://relay.example.com/api/grapevine/score?observer=abc123...&target=def456..."

Response (200):

{
  "observer": "abc123...",
  "target": "def456...",
  "influence": 0.85,
  "average": 0.90,
  "certainty": 0.94,
  "input": 0.15,
  "wot_score": 42,
  "depth": 1
}

POST /api/grapevine/recalculate

Triggers an async score recalculation for an observer. Returns immediately.

Body (JSON):

{"observer": "abc123..."}

If observer is omitted, uses the authenticated pubkey. Owner/admin can trigger for any observer; others can only trigger their own.

Example:

NOSTR_SECRET_KEY=nsec1... ./nurl "https://relay.example.com/api/grapevine/recalculate" '{"observer":"abc123..."}'

Response (202):

{"status": "started", "observer": "abc123..."}

Or if already computing:

{"status": "already_computing", "observer": "abc123..."}

Score Fields

FieldTypeDescription
pubkeystringHex pubkey of the scored user
influencefloat64Combined score: average * certainty (0.0 to 1.0)
averagefloat64Weighted average of rater scores (0.0 to 1.0)
certaintyfloat64Confidence based on total input weight (0.0 to 1.0)
inputfloat64Raw sum of input weights from raters
wot_scoreintNumber of observer's follows who also follow this pubkey
depthintBFS hop distance from observer (1 = direct follow)

Storage

Scores are persisted in Badger under the key prefixes GV_SET_ (full score sets) and GV_ENT_ (individual entries). They survive relay restarts. A recalculation replaces the previous scores for that observer.

Performance

The algorithm uses Badger's materialized ppg/gpp indexes, which encode follow graph edges as 21-byte binary keys. Each follower lookup is a single sorted prefix scan — no event decoding, no SQL parsing.

For a typical relay with ~50k pubkeys in the 2-hop set and 5 convergence cycles, computation completes in seconds. Follower lookups are cached within a single computation run.