// api docs
Docs
Every example below hits the live sandbox at https://api.sybilshield.org. Replace $KEY with your sandbox key from /dashboard/api-keys.
1 · Getting started
Sign up at /dashboard with an email — no card required. SybilShield is a free public good: you get an API key with a public-sandbox fair-use limit of 100 calls/month. Store the key somewhere safe; it cannot be retrieved later (only rotated).
2 · Authentication
All authenticated endpoints take a bearer token.
curl -H "Authorization: Bearer $KEY" \
https://api.sybilshield.org/v1/account{
"id": "...",
"email": "you@example.com",
"plan": "free",
"api_key_prefix": "sk_live_xxxx...",
"usage": { "calls_this_month": 3, "limit": 100 }
}3 · Presets & decisions
Every analysis comes back with a per-address verdict — DROP, REVIEW, or KEEP — computed server-side from the preset you pick at submission time. Pass preset in the POST body; the verdict is then stored against the analysis row (and audit-log entries) so the decision is reproducible after the fact.
| Preset | DROP if | REVIEW if | Tuned for |
|---|---|---|---|
| balanced | score ≥ 80 | score ≥ 50 | default — pick if unsure |
| airdrop | score ≥ 85 OR cluster_size ≥ 50 | score ≥ 60 OR cluster_size ≥ 20 | aggressive — token distributions |
| dao | score ≥ 90 OR cluster_size ≥ 30 | score ≥ 50 OR cluster_size ≥ 10 | conservative — governance voting |
| grant | cluster_size ≥ 20 | cluster_size ≥ 5 OR score ≥ 70 | cluster-first — grant committees |
Cluster-only mode. Pass mode: "cluster_only" to skip per-address ML scoring entirely and return only multi-wallet farm groupings. Useful when you care more about "these 47 wallets are one entity" than about per-address risk scoring.
# Submit with a preset + mode
curl -X POST https://api.sybilshield.org/v1/analyses \
-H "Authorization: Bearer $KEY" \
-H 'content-type: application/json' \
-d '{
"name": "wave-2 airdrop scan",
"chains": ["ethereum"],
"preset": "airdrop",
"mode": "full",
"addresses": ["0xabc...", "0xdef..."]
}'Threshold overrides. Tune a preset for one analysis with threshold_overrides. Each knob is optional — a number tightens/loosens it, null disables it, omitting it keeps the preset value. Rows decided with an override carry a custom_thresholds rationale code, and the override is stored on the analysis so the decision is reproducible.
# Airdrop preset, but I've already excluded my own CEX
# deposit wallets, so tighten the cluster knob from 50 -> 12
curl -X POST https://api.sybilshield.org/v1/analyses \
-H "Authorization: Bearer $KEY" \
-H 'content-type: application/json' \
-d '{
"name": "wave-2 (cex-excluded)",
"chains": ["ethereum"],
"preset": "airdrop",
"threshold_overrides": {
"drop": { "cluster_size_gte": 12 },
"review": { "cluster_size_gte": 5 }
},
"addresses": ["0xabc...", "0xdef..."]
}'Decision rules live in apps/api/src/lib/presets.ts (canonical) and apps/ml/sybilshield/scoring/presets.py (Python mirror) — open-source on GitHub.
4 · Create an analysis
Submit a list of addresses on one or more chains. Returns immediately with an id andstatus: pending. The worker picks the job up, ingests on-chain data via Alchemy, runs the detection pipeline, and writes scores.
curl -X POST https://api.sybilshield.org/v1/analyses \
-H "Authorization: Bearer $KEY" \
-H 'content-type: application/json' \
-d '{
"name": "my first analysis",
"chains": ["ethereum"],
"preset": "balanced",
"addresses": [
"0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"0xab5801a7d398351b8be11c439e05c5b3259aec9b"
]
}'{
"id": "325554d5-559a-40dc-bd34-b3d180d5eb08",
"status": "pending",
"address_count": 2,
"estimated_time_minutes": 2
}5 · Check analysis status
Poll the resource until status === "complete".
curl -H "Authorization: Bearer $KEY" \
https://api.sybilshield.org/v1/analyses/$ID{
"id": "325554d5-...",
"status": "complete",
"name": "my first analysis",
"chains": ["ethereum"],
"address_count": 2,
"summary": {
"total_scored": 2,
"sybil_count": 0,
"suspicious_count": 0,
"genuine_count": 2,
"cluster_count": 0
},
"processing_time_seconds": 5,
"completed_at": "2026-05-25T..."
}Or configure a webhook — see §7 — and skip polling entirely.
6 · Read results
Per-address scores with structured evidence + the preset-aware decision verdict. Paginated; default 100 per page. Filters: ?decision=DROP (or REVIEW/KEEP), or legacy ?label=sybil.
curl -H "Authorization: Bearer $KEY" \
"https://api.sybilshield.org/v1/analyses/$ID/results?decision=DROP&limit=10"{
"data": [
{
"address": "0x9bfc0b6c1f1f6c9c30b6e8c4e8c1f2a1b3c4d5e6",
"chain": "ethereum",
"sybil_score": 100,
"label": "sybil",
"confidence": "1.000",
"cluster_id": "F-a8b2c1d4",
"cluster_size": 12,
"decision": "DROP",
"decision_confidence": "high",
"rationale_codes": [
"shared_funder_cluster",
"score_ge_85",
"cluster_size_ge_10"
],
"evidence": [
{
"type": "shared_funding",
"description": "Funded from same source as 11 other addresses within 6h.",
"confidence": 0.95
}
]
}
],
"page": 0,
"limit": 10
}Decision is the preset-aware verdict (preferred for filtering). Score 0–100 remains available for callers who want to apply their own thresholds. Evidence is a JSON array describing which methods fired and which features pushed the score — populated only for flagged scores.
7 · Export CSV
One row per address, same scoring columns. Auth required (Bearer header).
curl -H "Authorization: Bearer $KEY" \
"https://api.sybilshield.org/v1/analyses/$ID/results/export" \
-o analysis-$ID.csvaddress,chain,sybil_score,label,confidence,cluster_id,cluster_size
0xd8da6bf26964af9d7eed9e03e53415d37aa96045,ethereum,0,genuine,0.000,,
0xab5801a7d398351b8be11c439e05c5b3259aec9b,ethereum,0,genuine,0.000,,8 · Webhook behavior
Configure a webhook URL + secret on /dashboard/api-keys. When an analysis completes, we POST a JSON event with a SHA-256 HMAC signature in X-SybilShield-Signature.
POST https://your-server.com/webhook
Content-Type: application/json
X-SybilShield-Signature: sha256=...
{
"type": "analysis.completed",
"analysisId": "325554d5-...",
"data": {
"total_scored": 2,
"sybil_count": 0,
"suspicious_count": 0,
"genuine_count": 2,
"cluster_count": 0
}
}Verify with HMAC-SHA256 over the raw body, using your webhook_secret as the key. If signatures don't match, reject the event.
9 · Audit log
Every flagged score and every appeal verdict writes an append-only row to the audit log. Read your slice with:
curl -H "Authorization: Bearer $KEY" \
"https://api.sybilshield.org/v1/audit-log?analysis_id=$ID&limit=100"10 · Fair-use limits
SybilShield is a free public good. These fair-use limits keep the shared sandbox healthy — they are not a paywall. Running heavier research? Email us for more headroom.
| Limit | Value |
|---|---|
| Requests / minute | 30 |
| Write calls / month | 100 |
| Addresses / analysis | 1,000 |
| Concurrent analyses | 1 |
| Upload size | 1 MB |
What counts toward the monthly limit. Write calls — POST /v1/analyses, POST /v1/score/batch, POST /v1/feedback, and other write endpoints. What does NOT count: all GET reads (/v1/analyses/:id, /v1/analyses/:id/results, /v1/account, etc.) — dashboard polling and result viewing are free.
Error codes. 429 monthly_quota_exceeded (fair-use monthly limit, resets monthly); 429 rate_limit_exceeded with retry_after_seconds; 429 concurrent_limit_exceeded; 400 too_many_addresses; 400 estimated_cu_exceeds_budget.
11 · Current sandbox limitations
The public sandbox is intended for testing the API flow, dashboard, evidence format, and scoring workflow. Real on-chain ingestion runs through Alchemy on 5 chains (Ethereum, Arbitrum, Optimism, Base, Polygon). Production model calibration on a real labeled corpus is handled separately from the sandbox environment.
- Scores in the sandbox may shift as the model is recalibrated.
- Cluster IDs are stable within an analysis, not across analyses.
- Evidence arrays are empty when sybil_score < 40.
- Webhook retries are manual today (auto-retry with backoff is on the roadmap).
Bug or unclear doc? support@sybilshield.org