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
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 ceilingX-RateLimit-Remaining: requests left this windowX-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
/api/v1/score/:addressThe 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
/api/v1/score/batchScore 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
/api/v1/score/:address/historyDaily 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
/api/v1/score/:address/signedThe 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
/api/v1/healthPublic 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)
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 ≥ Nandconfidence ≥ 0.5. - Governance weighting: weight votes by
compositeoreconomic. - Sybil screening: flag clusters via the graph-anomaly
flagsand history discontinuities.