{"openapi":"3.1.0","info":{"title":"scamcrawl Public API","version":"1.0.0","summary":"Read access to published scam-operator networks and intake of suspected scam URLs.","description":"The scamcrawl public API exposes **published, reviewed** scam-operator\nnetworks and their evidence chains. It deliberately never reveals detection\nmethodology: there are no internal tier names, no raw linking values, and no\nunpublished records. Links are reported only as coarse confidence labels\n(`Confirmed link` / `Likely link` / `Possible link`) and shared evidence is\nreported only by category (e.g. `recycled website copy`).\n\nAll read endpoints are unauthenticated, cacheable, and CORS-open for `GET`.\nThe bearer (`ApiKey`) scheme documents the private/admin surface; public\nread endpoints do not require it.\n\nResponses use a consistent envelope: success is `{ \"data\": … }` (with an\noptional `meta` block for paginated lists), and errors are\n`{ \"error\": { \"code\", \"message\" } }`.","contact":{"name":"scamcrawl","url":"https://scamcrawl.com/contact"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://scamcrawl.com/api/v1","description":"Production"}],"tags":[{"name":"Operators","description":"Published operator networks (the public face of a cluster)."},{"name":"Clusters","description":"The graph-oriented view of the same published networks."},{"name":"Domains","description":"Look up the public status of a single domain."},{"name":"Activity","description":"Recent pipeline activity and aggregate counts."},{"name":"Community","description":"Submitter leaderboard."},{"name":"Intake","description":"Submit a suspected scam URL for review."},{"name":"System","description":"Liveness and the machine-readable API description."}],"paths":{"/operators":{"get":{"tags":["Operators"],"operationId":"listOperators","summary":"List published operators","description":"Paginated list of published operator networks (non-contaminated clusters), ordered by domain count then recency. Public-safe summary only.","parameters":[{"name":"page","in":"query","required":false,"description":"1-based page number. Defaults to 1; values < 1 are coerced to 1.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"pageSize","in":"query","required":false,"description":"Items per page. Defaults to 20, hard-capped at 100 (larger values are clamped).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"A page of operator summaries.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=60"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OperatorSummary"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}}}}},"/operators/{id}":{"get":{"tags":["Operators"],"operationId":"getOperator","summary":"Get a published operator","description":"Public-safe detail for one operator: member domains, confidence-labeled shared-evidence bindings, and regulator warnings. Unpublished or contaminated operators return 404 — the API never reveals that a non-public record exists.","parameters":[{"name":"id","in":"path","required":true,"description":"Cluster id (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"Operator detail.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ClusterDetail"}}}}}},"404":{"description":"Operator not found (or not published).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/clusters":{"get":{"tags":["Clusters"],"operationId":"listClusters","summary":"List published clusters","description":"Paginated list of published clusters. A cluster is the same underlying entity as an operator; this is the graph-oriented listing. Identical shape to /operators.","parameters":[{"name":"page","in":"query","required":false,"description":"1-based page number. Defaults to 1; values < 1 are coerced to 1.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"pageSize","in":"query","required":false,"description":"Items per page. Defaults to 20, hard-capped at 100 (larger values are clamped).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"A page of cluster summaries.","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OperatorSummary"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}}}}},"/clusters/{id}":{"get":{"tags":["Clusters"],"operationId":"getCluster","summary":"Get a published cluster","description":"Public-safe cluster detail plus adjacency to other published clusters (confidence label only, never edge evidence values). Unpublished or contaminated clusters return 404.","parameters":[{"name":"id","in":"path","required":true,"description":"Cluster id (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"Cluster detail with public adjacency edges.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ClusterDetailWithEdges"}}}}}},"404":{"description":"Cluster not found (or not published).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/domains/{domain}":{"get":{"tags":["Domains"],"operationId":"lookupDomain","summary":"Look up a domain","description":"The \"check a URL\" lookup. The path segment is normalized to a registrable domain. Returns the public verdict, the operator it belongs to (if published), and any regulator warnings. Unknown, unpublished, or invalid input returns 404/400 — only PUBLISHED targets are exposed.","parameters":[{"name":"domain","in":"path","required":true,"description":"A domain or URL (URL-encoded). Normalized to a registrable domain server-side.","schema":{"type":"string","example":"example-scam.com"}}],"responses":{"200":{"description":"The domain is publicly listed.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/DomainLookup"}}}}}},"400":{"description":"The supplied value could not be parsed as a domain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Domain not listed (unknown or not published).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/feed":{"get":{"tags":["Activity"],"operationId":"getFeed","summary":"Recent pipeline activity","description":"Up to 50 recent public pipeline events (crawls, discovery searches, pivots). Never includes verbatim phrases, query text, or match thresholds. A domain appearing here is NOT labelled a scam.","responses":{"200":{"description":"Recent events, newest first.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/FeedEvent"}}}}}}}}}},"/stats":{"get":{"tags":["Activity"],"operationId":"getStats","summary":"Aggregate counts","description":"Public counts: operators exposed (published, non-contaminated clusters), domains tracked, and total clusters. An empty or unreachable database yields zeros, never an error.","responses":{"200":{"description":"Aggregate counts.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Stats"}}}}}}}}},"/leaderboard":{"get":{"tags":["Community"],"operationId":"getLeaderboard","summary":"Submitter leaderboard","description":"Top submitters ranked by impact: scams they reported and the associated scams the discovery loop surfaced from those seeds. Public fields only (handle + counts) — never email or linking detail.","responses":{"200":{"description":"Ranked submitters.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["leaderboard"],"properties":{"leaderboard":{"type":"array","items":{"$ref":"#/components/schemas/LeaderboardRow"}}}}}}}}}}}},"/report":{"post":{"tags":["Intake"],"operationId":"submitReport","summary":"Report a suspected scam URL","description":"Submit a suspected scam URL or domain for review. The value is normalized to a registrable domain and recorded as a DISCOVERED target; it NEVER publishes or asserts a verdict. Accepts either a JSON body or a form post. If the request carries a signed-in browser session cookie, the submission is credited to that user; API-key and anonymous posts stay anonymous.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"A URL or domain.","example":"https://example-scam.com/"}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","example":"example-scam.com"}}}}}},"responses":{"202":{"description":"Report received and recorded for review.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ReportReceipt"}}}}}},"400":{"description":"Missing/malformed body, or the value is not a valid domain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The report could not be recorded right now; retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/health":{"get":{"tags":["System"],"operationId":"getHealth","summary":"Liveness check","description":"Liveness probe. Does not touch the database, so it stays up even when the DB is unreachable.","responses":{"200":{"description":"The service is up.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Health"}}}}}}}}},"/openapi":{"get":{"tags":["System"],"operationId":"getOpenApi","summary":"This OpenAPI document","description":"Returns this OpenAPI 3.1 document as JSON.","responses":{"200":{"description":"The OpenAPI 3.1 description of this API.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","bearerFormat":"sk_<prefix8>_<secret>","description":"Bearer token for the **private/admin** API. Keys look like `sk_<prefix8>_<secret>`; only the 8-char prefix and a SHA-256 of the key are stored, and the plaintext is shown exactly once at issuance. Roles are hierarchical: VIEWER < MODERATOR < ADMIN. The public read endpoints documented here do NOT require this scheme."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","forbidden","not_found","rate_limited","invalid_request","internal"]},"message":{"type":"string"}}}}},"PageMeta":{"type":"object","required":["page","pageSize","total","hasMore"],"properties":{"page":{"type":"integer"},"pageSize":{"type":"integer"},"total":{"type":"integer"},"hasMore":{"type":"boolean"}}},"Confidence":{"type":"string","description":"Coarse, methodology-free link confidence.","enum":["Confirmed link","Likely link","Possible link"]},"Verdict":{"type":"string","enum":["UNKNOWN","LIKELY_LEGIT","SUSPICIOUS","LIKELY_SCAM","CONFIRMED_SCAM"]},"OperatorSummary":{"type":"object","required":["id","label","domainCount","regulatorConfirmedPct"],"properties":{"id":{"type":"string"},"label":{"type":["string","null"],"description":"Human-readable operator label, if assigned."},"domainCount":{"type":"integer"},"regulatorConfirmedPct":{"type":"integer","description":"Percentage of member domains that overlap a regulator warning (0–100)."}}},"PublicBinding":{"type":"object","description":"A category of shared evidence linking the member domains. Never the raw value.","required":["category","confidence","domainCount"],"properties":{"category":{"type":"string","description":"Evidence category, e.g. 'recycled website copy', 'shared hosting fingerprint'.","example":"recycled website copy"},"confidence":{"$ref":"#/components/schemas/Confidence"},"domainCount":{"type":"integer","description":"Member domains carrying this evidence."}}},"PublicWarning":{"type":"object","required":["regulator","warningUrl","publishedAt"],"properties":{"regulator":{"type":"string","description":"Regulator code, e.g. 'FCA'.","example":"FCA"},"warningUrl":{"type":["string","null"],"format":"uri"},"publishedAt":{"type":["string","null"],"format":"date-time"}}},"ClusterMemberDomain":{"type":"object","required":["domain","status","warnings"],"properties":{"domain":{"type":"string"},"status":{"type":"string","description":"Public status, e.g. PUBLISHED / CRAWLED."},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/PublicWarning"}}}},"ClusterDetail":{"type":"object","required":["id","label","domainCount","liveCount","verdict","regulatorConfirmedPct","bindings","domains"],"properties":{"id":{"type":"string"},"label":{"type":["string","null"]},"domainCount":{"type":"integer"},"liveCount":{"type":"integer","description":"Member domains that are published/live."},"verdict":{"$ref":"#/components/schemas/Verdict"},"regulatorConfirmedPct":{"type":"integer"},"bindings":{"type":"array","items":{"$ref":"#/components/schemas/PublicBinding"}},"domains":{"type":"array","items":{"$ref":"#/components/schemas/ClusterMemberDomain"}}}},"ClusterEdge":{"type":"object","required":["clusterId","label","confidence"],"properties":{"clusterId":{"type":"string"},"label":{"type":["string","null"]},"confidence":{"$ref":"#/components/schemas/Confidence"}}},"ClusterDetailWithEdges":{"allOf":[{"$ref":"#/components/schemas/ClusterDetail"},{"type":"object","required":["edges"],"properties":{"edges":{"type":"array","description":"Adjacency to other published clusters (confidence label only).","items":{"$ref":"#/components/schemas/ClusterEdge"}}}}]},"DomainLookup":{"type":"object","required":["domain","listed","verdict","operator","warnings"],"properties":{"domain":{"type":"string"},"listed":{"type":"boolean","description":"Always true on a 200 (only published targets are exposed)."},"verdict":{"$ref":"#/components/schemas/Verdict"},"operator":{"type":["object","null"],"properties":{"id":{"type":"string"},"label":{"type":["string","null"]}}},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/PublicWarning"}}}},"FeedEvent":{"type":"object","required":["at","kind","body"],"properties":{"at":{"type":"string","format":"date-time"},"kind":{"type":"string","enum":["CRAWL","DISCOVER","PIVOT"]},"body":{"type":"string","description":"Human-readable, methodology-free event description."}}},"Stats":{"type":"object","required":["operatorsExposed","domainsTracked","clusters"],"properties":{"operatorsExposed":{"type":"integer"},"domainsTracked":{"type":"integer"},"clusters":{"type":"integer"}}},"LeaderboardRow":{"type":"object","required":["rank","handle","reported","detected"],"properties":{"rank":{"type":"integer"},"handle":{"type":"string","description":"Public display handle (never the email)."},"reported":{"type":"integer","description":"Fresh scams this user submitted."},"detected":{"type":"integer","description":"Associated scams the discovery loop surfaced from those seeds."}}},"ReportReceipt":{"type":"object","required":["domain","status"],"properties":{"domain":{"type":"string","description":"The normalized registrable domain that was recorded."},"status":{"type":"string","enum":["received"]}}},"Health":{"type":"object","required":["status","time"],"properties":{"status":{"type":"string","enum":["ok"]},"time":{"type":"string","format":"date-time"}}}}}}