SOCI4L

API Reference

The SOCI4L API returns a reputation read for any Avalanche C-Chain address: a 0–100 composite headline, the four-dimension S4 vector, follow-graph anomaly flags, and a Verified Human number. REST, JSON, one header to authenticate. Base URL https://soci4l.net/api/v1.

Authentication

Every endpoint except GET /api/v1 requires an API key, passed in the X-Api-Key header. Keys look like s4_live_… and are scoped to your wallet.

curl https://soci4l.net/api/v1/score/0x8ab0...e1b1 \
  -H "X-Api-Key: s4_live_..."

Create and revoke keys yourself: connect your wallet, open the Developer tab in your dashboard, and create a key (a one-time wallet signature confirms ownership). The raw key is shown once. Store it securely; it's held only as a hash and cannot be recovered. Keep keys server-side; never ship them in client code.

Rate limits

Free1,000 requests / day
Pro10,000 requests / day

Limits reset on a rolling 24-hour window. Batch calls consume one unit per address; a 50-address call costs 50. Every response carries:

  • X-RateLimit-Limit: your daily ceiling
  • X-RateLimit-Remaining: requests left this window
  • X-RateLimit-Reset: ISO 8601 reset time

On top of the daily quota there is a per-key burst brake: 60 requests/min on single-address endpoints and 10 requests/min on batch. A burst 429 carries a Retry-After header (seconds); back off and retry.

Get a wallet score

GET/api/v1/score/:address

The full reputation read for one address. On-chain signals are read live (24-hour cache); profile/social signals are first-party. Works for any C-Chain address whether or not it has a SOCI4L profile.

Response

{
  "address": "0x8ab0cf264df99d83525e9e11c7e4db01558ae1b1",
  "headline": {
    "score": 60,            // 0–100 composite, the public headline
    "tier": "elite",
    "tierLabel": "Elite",
    "model": "s4-composite"
  },
  "s4": {
    "humanity": 72.1,       // real-person evidence (0–100)
    "activity": 55.3,       // on-chain activity, recency-decayed
    "social": 38.0,         // follower graph, anomaly-damped
    "economic": 61.4,       // costly on-chain spend (log scale)
    "composite": 60.2,      // weighted blend → headline.score
    "confidence": 0.75      // data coverage (0–1); low data ≠ low score
  },
  "graph": {
    "analyzed": true,
    "flags": [],            // e.g. ["follow_ring","thin_followers"]
    "socialDamping": 1      // 0.25–1 multiplier applied to "social"
  },
  "verifiedHuman": 2,       // permanent Verified Human #N, or null

  // ── legacy v1 (kept for back-compat; prefer headline + s4) ──
  "score": 43.4,
  "tier": "established",
  "breakdown": { "walletAge": 15, "txActivity": 10, "gasSpent": 8, "...": 0 },

  "fetchedAt": "2026-06-24T10:30:00.000Z",
  "model": "v2-composite-headline+v1-legacy"
}

Batch scoring

POST/api/v1/score/batch

Score 1–50 addresses in one call: airdrop filtering, allowlist checks, sybil screening. Signals are read cache-only for speed: a result with signals: "none" has never been scored; warm it once via the single-address endpoint. Each address consumes one rate-limit unit.

Request

curl -X POST https://soci4l.net/api/v1/score/batch \
  -H "X-Api-Key: s4_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "addresses": ["0x8ab0...e1b1", "0xd8da...6045"] }'

Response

{
  "count": 2,
  "results": [
    {
      "address": "0x8ab0...e1b1",
      "score": 43.4, "tier": "established",
      "s4": { "humanity": 72.1, "composite": 60.2, "confidence": 0.75, "...": 0 },
      "graph": { "analyzed": true, "flags": [], "socialDamping": 1 },
      "verifiedHuman": 2,
      "signals": "fresh"     // "fresh" | "stale" | "none"
    }
    // ...
  ],
  "fetchedAt": "2026-06-24T10:30:00.000Z"
}

Score history

GET/api/v1/score/:address/history

Daily snapshots for trend analysis. Query params: days (1–90, default 30) and include=breakdown to add per-signal points to each entry. The time series itself is an anti-sybil signal; sudden discontinuities are suspicious.

Response

{
  "address": "0x8ab0...e1b1",
  "days": 30,
  "count": 28,
  "history": [
    { "date": "2026-05-28", "score": 41.0, "tier": "established" },
    { "date": "2026-05-29", "score": 41.4, "tier": "established" }
    // ...
  ],
  "fetchedAt": "2026-06-24T10:30:00.000Z"
}

Signed attestation

GET/api/v1/score/:address/signed

The score as an EIP-712 signed attestation: the canonical struct, the domain and type definitions, and a service signature over them. Verify it offline with viem or ethers verifyTypedData, or paste it into the verify page to check it in the browser. Compare the recovered signer against the attestation.signer field of GET /api/v1. Returns 501 on deployments where signing is not enabled. Consumes one rate-limit unit.

Response

{
  "domain": {
    "name": "SOCI4L Attestation", "version": "1",
    "chainId": 43114, "verifyingContract": "0x873e...1A13"
  },
  "types": { "ScoreAttestation": [ /* EIP-712 field definitions */ ] },
  "primaryType": "ScoreAttestation",
  "message": {
    "subject": "0x8ab0...e1b1",
    "score": "60",              // uint256 fields are decimal strings
    "humanity": "72",
    "tier": 4,                  // 0 starter ... 5 legendary
    "verifiedHuman": true,
    "issuedAt": "1783937400",
    "expiresAt": "1784542200",  // default TTL: 7 days
    "nonce": "1783937400123"
  },
  "signature": "0x...",         // 65-byte service signature
  "signer": "0x...",            // must match attestation.signer at GET /api/v1
  "readable": { /* same fields, human-readable */ },
  "model": "s4-composite"
}

Health

GET/api/v1/health

Public uptime probe, no API key required. Returns HTTP 200 when the app and its database are reachable, 503 when degraded. Point your monitor at the status code.

Response

{
  "status": "ok",               // "ok" | "degraded" (degraded returns HTTP 503)
  "version": "1",
  "db": "ok",                   // "ok" | "unreachable"
  "time": "2026-07-12T10:30:00.000Z"
}

Response schema

The headline (composite 0–100 + tier) is the number to show users. The s4 vector is what you threshold on for B2B logic. Legacy score/tier are the original v1 fields, kept for back-compat.

S4 dimensions (0–100)

humanityIs there a real person behind this wallet? Verified socials + wallet age.
activityOn-chain activity, recency-decayed. Volume is log-damped.
socialFollower-graph standing, damped by anomaly heuristics.
economicCostly, timestamped spend: gas, premium, paid slug, donations (log scale).
compositeWeighted blend of the four; this is headline.score.
confidence0–1 data coverage. Low data lowers confidence, not the score.

Tier bands (composite)

Legendary80–100
Elite60–79
Established40–59
Rising20–39
Newcomer10–19
Starter0–9

Graph anomaly flags

follow_ringMost followers are followed right back, a reciprocal ring.
follower_burstMost followers arrived inside a single 24-hour window.
thin_followersFollowers are mostly unclaimed wallets with no on-chain history.
self_donationDonations sent from the address to itself.

Flags shrink socialDamping (0.25–1) and lower the vector's confidence. They never silently zero a score.

Errors

400Invalid address, or a malformed batch body / out-of-range params.
401Missing or invalid API key.
429Rate limit exceeded. Honor the Retry-After header (burst) or wait for X-RateLimit-Reset (daily quota).
500Scoring failed server-side. Safe to retry with backoff.
501Signed attestations not enabled on this deployment (signed endpoint only).

Errors return { "error": "message" }. A 400 from the batch endpoint also includes an invalid array listing the offending addresses.

Code examples

JavaScript

const res = await fetch(
  "https://soci4l.net/api/v1/score/" + address,
  { headers: { "X-Api-Key": process.env.SOCI4L_API_KEY } }
);
if (res.status === 429) throw new Error("rate limited");
const data = await res.json();

// Gate a mint on real on-chain humanity
if (data.s4.humanity >= 70 && data.s4.confidence >= 0.5) {
  allowMint(address);
}

Python

import os, requests

r = requests.get(
    f"https://soci4l.net/api/v1/score/{address}",
    headers={"X-Api-Key": os.environ["SOCI4L_API_KEY"]},
)
r.raise_for_status()
data = r.json()

print(data["headline"]["score"], data["headline"]["tier"])

Common use cases

  • Airdrop filtering: batch-score the claim list, drop low humanity + flagged graphs.
  • Mint / allowlist gating: require humanity ≥ N and confidence ≥ 0.5.
  • Governance weighting: weight votes by composite or economic.
  • Sybil screening: flag clusters via the graph-anomaly flags and history discontinuities.