Intelligence before risk

RugPull Risk API

Public MVP documentation for scoring Ethereum contracts for rug-pull risk signals. This site reflects the current routes, headers, and JSON contracts in the codebase.

Code-accurate
Routes and payloads match the current server implementation.
Metered
Unit-based quota with plan caps, 422 plan limits, and 429 quota behavior.
Reports & PDFs
Narrative report JSON + PDF rendering, stored for 30 days (plan-gated).
Fast scoring example
curl -sS "https://api.ravenwolflabs.com/v1/risk/score" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "blockchain": "ethereum",
    "contractAddress": "0x0000000000000000000000000000000000000000",
    "maxBlockScanRange": 2000
  }'
Typical response shape
{
  "riskScore": 42,
  "riskLevel": "MEDIUM",
  "confidence": "MEDIUM",
  "coveragePercent": 85,
  "dataQuality": "GOOD",
  "factors": [
    {
      "id": "RF01_PROXY_UPGRADEABLE",
      "status": "NOT_TRIGGERED",
      "severity": "MEDIUM",
      "evidence": { "isProxy": false }
    },
    {
      "id": "RF06_LIQUIDITY_NOT_LOCKED",
      "status": "UNKNOWN",
      "severity": "HIGH",
      "evidence": { "reason": "not enough data" }
    }
  ],
  "unknowns": [
    "RF06_LIQUIDITY_NOT_LOCKED"
  ],
  "aiExplanation": null,
  "analyzedAtUtc": "2026-02-23T17:45:00.0000000+00:00"
}
Heads up: MVP supports blockchain: "ethereum" only. Anything else returns a 400.

Overview

The RugPull Risk API evaluates a contract and returns a risk score (0–100), a risk tier, confidence, coverage, and a list of factor evaluations with evidence.

What’s included in the Public MVP
  • Score: POST /v1/risk/score
  • Quote: POST /v1/risk/quote (units required + quota + plan cap + detector feasibility)
  • Usage: GET /v1/risk/usage (units used/remaining + reset time)
  • Deep scan jobs: POST /v1/risk/deep-scan and poll GET /v1/risk/deep-scan/{jobId}
  • Reporting (plan-gated): POST /v1/risk/report, POST /v1/risk/report/pdf, and PDF retrieval endpoints

Base URL

All public MVP endpoints are served under:

https://api.ravenwolflabs.com

Authentication

Every public MVP route requires an API key via the header configured in code: X-Api-Key.

X-Api-Key: YOUR_API_KEY
401 behavior
  • Missing X-Api-Key401 Unauthorized
  • Unknown/invalid key → 401 Unauthorized

Plans, units & limits

Requests are metered by “units” based on your effective block lookback window. Your plan defines a monthly unit quota and a max scan range per request.

Key knobs (from config)
  • DefaultMaxBlockScanRange (used when request omits maxBlockScanRange)
  • BlockUnitSize (units per block range chunk)
  • Plans.*.MonthlyUnitQuota
  • Plans.*.MaxBlockScanRangePerRequest
What happens when you exceed limits
  • Over plan scan cap → 422 Unprocessable Entity
  • Over quota → 429 Too Many Requests with a JSON body
  • Cache hit → X-Units-Consumed: 0 (no charge)
Plan gates
  • AI explanation (score): plan-gated (Pro/Scale). If requested on other plans → 422.
  • Reporting/PDFs: plan-gated (Pro/Scale). If called on other plans → 402 with upgrade_required.

POST /v1/risk/score

POST /v1/risk/score

Scores a contract and returns a RiskScoreResponse. MVP supports blockchain = "ethereum" only.

Request body (RiskScoreRequest)
{
  "blockchain": "ethereum",
  "contractAddress": "0x…",
  "maxBlockScanRange": 2000,
  "explanationStyle": "brief"
}
AI explanation (optional)
If you provide explanationStyle, the API may attach a structured aiExplanation object. Allowed values: brief, standard, investor_memo, compliance. Availability is plan-gated (Pro/Scale).
Validation
  • blockchain must be "ethereum" or returns 400 with {"error":"MVP supports ethereum only"}
  • contractAddress must be a 42-char EVM address (0x + 40 hex) or returns 400 with {"error":"Invalid contractAddress"}
  • maxBlockScanRange over plan cap → 422 with details
  • Invalid explanationStyle400 with allowed styles
  • Disallowed explanationStyle for plan → 422 with required plans
Example curl
curl -sS "https://api.ravenwolflabs.com/v1/risk/score" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "blockchain": "ethereum",
    "contractAddress": "0x0000000000000000000000000000000000000000",
    "maxBlockScanRange": 2000,
    "explanationStyle": "brief"
  }'
200 response (RiskScoreResponse)
{
  "riskScore": 17,
  "riskLevel": "LOW",
  "confidence": "HIGH",
  "coveragePercent": 92,
  "dataQuality": "GOOD",
  "factors": [
    {
      "id": "RF02_OWNER_CAN_MINT",
      "status": "NOT_TRIGGERED",
      "severity": "HIGH",
      "evidence": {
        "mintSelectorsFound": [],
        "notes": "No mint selectors detected"
      }
    }
  ],
  "unknowns": [],
  "aiExplanation": {
    "style": "brief",
    "summary": "This contract scored LOW risk with HIGH confidence based on current evidence.",
    "topDrivers": [
      { "driver": "Ownership controls", "impact": "LOW", "rationale": "No elevated owner privileges were detected in the scanned window." }
    ],
    "riskScenarios": [
      "If ownership can be transferred to a malicious party later, permissions could change."
    ],
    "benignAlternatives": [
      "Some contracts intentionally use proxy patterns for upgrades."
    ],
    "confidence": {
      "level": "HIGH",
      "whatWouldRaiseConfidence": [
        "More on-chain history",
        "Liquidity lock evidence"
      ]
    },
    "recommendedNextChecks": [
      "Verify contract on Etherscan",
      "Review liquidity lock status"
    ]
  },
  "analyzedAtUtc": "2026-02-23T17:45:00.0000000+00:00"
}
Status codes
200 400 401 422 429

POST /v1/risk/quote

POST /v1/risk/quote

Returns a preflight quote showing units required for the request, whether you’re within plan scan caps, whether you have enough remaining quota, and whether key detectors are executable for the requested window.

Request body (RiskScoreRequest)
{
  "blockchain": "ethereum",
  "contractAddress": "0x…",
  "maxBlockScanRange": 2000
}
200 response (RiskQuoteResponse)
{
  "plan": "starter",
  "executable": true,
  "effectiveMaxBlockScanRange": 2000,
  "allowedMaxBlockScanRange": 5000,
  "blockUnitSize": 1000,
  "unitsRequired": 2,
  "allowedByPlanCap": true,
  "allowedByQuota": true,
  "unitsRemaining": 1986,
  "unitsRemainingAfter": 1984,
  "resetsAtUtc": "2026-02-01T00:00:00.0000000+00:00",
  "execution": {
    "requestedBlockSpan": 2000,
    "maxExecutableBlockSpan": 5000,
    "failureReason": "",
    "actionRequired": ""
  },
  "detectors": {
    "RF13_SUSPICIOUS_INITIAL_DISTRIBUTION": {
      "executable": true,
      "reason": "Bounded scan enabled (lookback≈2000 blocks)."
    },
    "RF19_DEVELOPER_REPUTATION_RISK": {
      "executable": true,
      "reason": "Bounded scan enabled (lookback≈2000 blocks)."
    }
  }
}
Note
Quote is messaging + planning. Actual scoring/deep-scan still enforces plan caps (422) and quota (429).
Status codes
200 400 401 422

GET /v1/risk/usage

GET /v1/risk/usage

Returns the current billing-period usage and reset timestamp for the API key’s plan.

Example curl
curl -sS "https://api.ravenwolflabs.com/v1/risk/usage" \
  -H "X-Api-Key: YOUR_API_KEY"
Example response
{
  "plan": "starter",
  "unitsUsedThisPeriod": 14,
  "unitsLimit": 2000,
  "unitsRemaining": 1986,
  "resetsAtUtc": "2026-02-01T00:00:00.0000000+00:00"
}
Status codes
200 401

POST /v1/risk/deep-scan

POST /v1/risk/deep-scan

Creates an asynchronous deep scan job. Units are charged upfront to prevent job spamming. Returns 202 Accepted and a jobId you can poll.

Request body (RiskScoreRequest)
{
  "blockchain": "ethereum",
  "contractAddress": "0x…",
  "maxBlockScanRange": 25000
}
202 response
{
  "jobId": "b1a2c3d4e5f6...",
  "status": "Pending",
  "plan": "builder",
  "unitsCharged": 25,
  "effectiveMaxBlockScanRange": 25000
}
Important
Deep scan can be disabled in config. If disabled, server returns 422 with: {"error":"deep scan is disabled"}.
Status codes
202 400 401 422 429

GET /v1/risk/deep-scan/{jobId}

GET /v1/risk/deep-scan/{jobId}

Fetches a deep scan job. Basic isolation is enforced: in non-development environments, a job is only visible to the API key that created it. If the key doesn’t match, you get a 404.

Example curl
curl -sS "https://api.ravenwolflabs.com/v1/risk/deep-scan/b1a2c3d4e5f6..." \
  -H "X-Api-Key: YOUR_API_KEY"
Example response
{
  "jobId": "b1a2c3d4e5f6...",
  "status": "Completed",
  "plan": "builder",
  "blockchain": "ethereum",
  "contractAddress": "0x0000000000000000000000000000000000000000",
  "effectiveMaxBlockScanRange": 25000,
  "unitsCharged": 25,
  "createdAtUtc": "2026-01-21T22:10:00.0000000Z",
  "updatedAtUtc": "2026-01-21T22:10:04.0000000Z",
  "resultJson": "{ ... }",
  "error": null
}
Status codes
200 401 404

POST /v1/risk/report

POST /v1/risk/report

Generates a high perceived-value narrative report (JSON) and stores it for 30 days. The response includes a stable reportId used by the PDF endpoints. Plan-gated: Pro/Scale only (otherwise 402 upgrade_required).

Request body (RiskReportRequest)
{
  "blockchain": "ethereum",
  "contractAddress": "0x…",
  "template": "standard",
  "maxBlockScanRange": 5000
}
Template values
  • short
  • standard (default)
  • detailed
200 response (RiskReportResponse)
{
  "reportId": "2b2a0f4d7c4b4a9b9b7e0d2f7b7a1a2c",
  "engineVersion": "rprs-1.0.0",
  "blockchain": "ethereum",
  "contractAddress": "0x0000000000000000000000000000000000000000",
  "analyzedAtUtc": "2026-02-23T17:45:00.0000000+00:00",
  "template": "standard",
  "summary": {
    "riskScore": 42,
    "riskLevel": "MEDIUM",
    "confidence": "MEDIUM",
    "coveragePercent": 85,
    "dataQuality": "GOOD"
  },
  "narrative": {
    "headline": "Moderate risk with unresolved liquidity signals",
    "overview": "The contract shows a mix of normal patterns and incomplete evidence in critical areas.",
    "keyTakeaways": [
      "No proxy upgrade pattern detected in the scanned window",
      "Liquidity lock evidence could not be confirmed",
      "Consider deeper inspection before committing significant funds"
    ]
  },
  "evidenceHighlights": [
    {
      "factorId": "RF06_LIQUIDITY_NOT_LOCKED",
      "severity": "HIGH",
      "title": "Liquidity lock could not be confirmed",
      "highlights": [
        "No lock transaction found in the scanned window",
        "On-chain evidence is incomplete for this factor"
      ],
      "evidence": { "reason": "not enough data" }
    }
  ],
  "recommendedChecks": [
    {
      "checkId": "CHK01_VERIFY_CONTRACT",
      "priority": "HIGH",
      "title": "Verify contract source and ownership controls",
      "why": "Verification reduces uncertainty and helps confirm privileged functions.",
      "how": [
        "Check Etherscan verification status",
        "Review owner privileges and transfer patterns"
      ]
    }
  ],
  "unknowns": [
    "RF06_LIQUIDITY_NOT_LOCKED"
  ]
}
Example curl
curl -sS "https://api.ravenwolflabs.com/v1/risk/report" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "blockchain": "ethereum",
    "contractAddress": "0x0000000000000000000000000000000000000000",
    "template": "standard",
    "maxBlockScanRange": 5000
  }'
Status codes
200 400 401 402 422 429

POST /v1/risk/report/pdf

POST /v1/risk/report/pdf

Stateless PDF generation: analyzes + composes a report, stores reportJson and uploads the PDF (stored for 30 days), and returns PDF bytes directly. Plan-gated: Pro/Scale only (otherwise 402 upgrade_required).

Request body (RiskReportRequest)
{
  "blockchain": "ethereum",
  "contractAddress": "0x…",
  "template": "standard",
  "maxBlockScanRange": 5000
}
Example curl
curl -sS "https://api.ravenwolflabs.com/v1/risk/report/pdf" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "blockchain": "ethereum",
    "contractAddress": "0x0000000000000000000000000000000000000000",
    "template": "standard",
    "maxBlockScanRange": 5000
  }' \
  --output risk-report.pdf
Response
Returns application/pdf bytes. The server may include helpful headers like: X-Report-Id, X-Pdf-Sha256, and X-Report-Template.
Status codes
200 400 401 402 422 429

GET /v1/risk/report/{reportId}/pdf

GET /v1/risk/report/{reportId}/pdf

Fetches a stored PDF for a previously generated reportId. If the PDF blob is missing but the stored report JSON exists, the server may regenerate the PDF and return it. Plan-gated: Pro/Scale only (otherwise 402 upgrade_required).

Example curl
curl -sS "https://api.ravenwolflabs.com/v1/risk/report/2b2a0f4d7c4b4a9b9b7e0d2f7b7a1a2c/pdf" \
  -H "X-Api-Key: YOUR_API_KEY" \
  --output risk-report.pdf
Status codes
200 401 402 404

GET /v1/risk/report/{reportId}/pdf-url

GET /v1/risk/report/{reportId}/pdf-url

Returns a presigned URL for the PDF. Optional query: ?download=1 forces download disposition. If the PDF blob is missing but the stored report JSON exists, the server may regenerate + upload first. Plan-gated: Pro/Scale only (otherwise 402 upgrade_required).

Example curl
curl -sS "https://api.ravenwolflabs.com/v1/risk/report/2b2a0f4d7c4b4a9b9b7e0d2f7b7a1a2c/pdf-url?download=1" \
  -H "X-Api-Key: YOUR_API_KEY"
200 response
{
  "reportId": "2b2a0f4d7c4b4a9b9b7e0d2f7b7a1a2c",
  "url": "https://...presigned...",
  "expiresAtUtc": "2026-02-23T18:15:00.0000000Z",
  "download": true
}
Status codes
200 401 402 404

Errors

The API returns consistent JSON error bodies for common conditions.

Plan scan cap exceeded (422)
{
  "error": "maxBlockScanRange exceeds plan limit",
  "plan": "starter",
  "requestedMaxBlockScanRange": 9000,
  "allowedMaxBlockScanRange": 5000,
  "blockUnitSize": 1000,
  "estimatedUnitsForRequestedRange": 9
}
Quota exceeded (429)
{
  "error": "quota exceeded",
  "plan": "starter",
  "unitsRequested": 12,
  "unitsRemaining": 3,
  "unitsLimit": 2000,
  "resetsAtUtc": "2026-02-01T00:00:00.0000000+00:00"
}
Reporting upgrade required (402)
{
  "error": "upgrade_required",
  "message": "Reporting is available on the Pro/Scale plans. Please upgrade to access reports.",
  "currentPlan": "starter",
  "requiredPlan": "pro",
  "upgradeUrl": "/billing/upgrade?plan=pro",
  "upgradeApiRoute": "/v1/billing/checkout-session",
  "features": [
    "reporting",
    "higher_quota",
    "higher_rate_limits",
    "priority_processing"
  ]
}
AI explanation not available on plan (422)
{
  "error": "ai explanation is not available on this plan",
  "plan": "starter",
  "requiredPlans": [ "pro", "scale" ],
  "explanationStyle": "brief"
}
Quick status guide
  • 400 — invalid blockchain, invalid contractAddress, or invalid explanationStyle
  • 401 — missing/invalid API key
  • 402 — reporting/PDF endpoints require upgrade (Pro/Scale)
  • 422 — deep scan disabled, plan scan cap exceeded, or AI explanation not available on plan
  • 429 — quota exceeded (metering)
  • 404 — deep-scan job or reportId not found

Response headers

The API includes headers that help you debug caching + metering behavior.

X-Plan: starter
X-Cache: HIT | MISS
X-Units-Consumed: 0 | <units>
X-Units-Remaining: <remaining>           (only set on MISS after consumption)
X-Units-Reset-At-Utc: 2026-02-01T00:00:00.0000000Z   (only set on MISS after consumption)

# Reporting-specific (when applicable)
X-Report-Id: <reportId>
X-Report-Template: short | standard | detailed
X-Report-Url: https://api.ravenwolflabs.com/v1/risk/report/<reportId>/pdf
X-Pdf-Sha256: <hex>

Cache hits return X-Units-Consumed: 0 and do not charge quota.

Best practices

Client reliability
  • On 429, back off and retry after a short delay.
  • Use POST /v1/risk/quote before expensive scans in automation.
  • Log X-Plan, X-Cache, and unit headers for observability.
  • For reporting/PDF flows, persist reportId if you want to fetch PDFs later.
Cost control
  • Prefer smaller maxBlockScanRange unless you truly need more history.
  • Leverage caching and avoid rescoring the same address repeatedly.
  • Use deep scan selectively (e.g., for borderline scores or higher-risk thresholds).
  • For PDFs, use /pdf-url when you want the client to download directly from storage.