SpotBlock Labs Zurich LogoSpotBlock Labs Zurich
App
← Back to docs

Contributor Manual (Validator API)

If you are registered as a human analyst, you can use the public validator API to pull pending review tickets and submit your own analysis as a single comment blob. This is designed so contributors can use their own tools and workflows (including AI agents) while keeping decisions auditable.

What you can do

  • Fetch the current queue of pending scam-scanner detections waiting for validator review (from contracts_staging).
  • Run your own analysis locally (rules, scripts, LLMs, checklists).
  • Submit a verdict (safe/threat) and a comment (your full reasoning + tool output).

Note: the validator submission stores your comment on the contract entry once it is in the public malicious explorer database. It does not overwrite scanner or Super-Analyst data.

Authentication model

The API uses short-lived wallet signatures with a server-issued nonce. Your wallet must be linked to your human analyst identity in SpotBlock’s contributor database.

  • Get a nonce from GET /api/validator/nonce.
  • Sign a message containing your wallet, nonce, and issued timestamp.
  • Use the signature in the request to fetch pending tickets or submit a review.

Endpoints

1) Get nonce

GET /api/validator/nonce

2) Fetch pending review tickets

GET /api/validator/pending (authenticated)

  • Returns items from contracts_staging where status is pending or needs_review.
  • Supports limit (default 50, max 200).

3) Submit a validator review (comment)

POST /api/validator/review (authenticated)

  • Required: verdict (safe or threat) and comment.
  • Optional: toolName (slug) so UI can display you as reviewer-tool (example: hug656-agent).

Example: fetch pending tickets (Node)

Run this from any machine with Node.js 18+ where you can install ethers (for example, a fresh local folder or a small private validator tool repo).

mkdir spotblock-validator-tool && cd spotblock-validator-tool
npm init -y
npm i ethers

export API_BASE_URL="https://spotblock.org"
export VALIDATOR_PRIVATE_KEY="0xYOUR_PRIVATE_KEY"

node -e '
const { Wallet } = require("ethers");
(async () => {
  const base = (process.env.API_BASE_URL || "").replace(/\/$/, "");
  const pk = process.env.VALIDATOR_PRIVATE_KEY;
  const w = new Wallet(pk);
  const wallet = w.address;

  const n = await fetch(`${base}/api/validator/nonce`);
  const { nonce, issuedAt } = await n.json();

  const msg = [
    "SpotBlock validator pending review request",
    `Wallet: ${wallet}`,
    `Nonce: ${nonce}`,
    `IssuedAt: ${issuedAt}`,
  ].join("\n");

  const signature = await w.signMessage(msg);
  const url = new URL(`${base}/api/validator/pending`);
  url.searchParams.set("wallet", wallet);
  url.searchParams.set("nonce", nonce);
  url.searchParams.set("issuedAt", issuedAt);
  url.searchParams.set("signature", signature);
  url.searchParams.set("limit", "20");

  const r = await fetch(url);
  console.log(await r.json());
})().catch(console.error);
'

Security note: don’t use a high-value wallet private key for tooling automation. Prefer a dedicated hot wallet with minimal assets.

Example: submit a review comment (Node)

This signs a short-lived message, then submits a verdict + freeform comment. Use toolName to display as reviewer-tool in the UI.

export API_BASE_URL="https://spotblock.org"
export VALIDATOR_PRIVATE_KEY="0xYOUR_PRIVATE_KEY"

# Target address can be pulled from /api/validator/pending (contracts_staging queue)
export TARGET_ADDRESS="0x0000000000000000000000000000000000000000"
export VERDICT="threat"     # safe | threat
export TOOL_NAME="agent"    # [a-z0-9-_]{1,32}

node -e '
const { Wallet, ethers } = require("ethers");
(async () => {
  const base = (process.env.API_BASE_URL || "").replace(/\/$/, "");
  const pk = process.env.VALIDATOR_PRIVATE_KEY;
  const target = process.env.TARGET_ADDRESS;
  const verdict = String(process.env.VERDICT || "").toLowerCase();
  const toolName = String(process.env.TOOL_NAME || "agent").toLowerCase();

  if (!base || !pk) throw new Error("Set API_BASE_URL and VALIDATOR_PRIVATE_KEY");
  if (!ethers.isAddress(target)) throw new Error("Invalid TARGET_ADDRESS");
  if (verdict !== "safe" && verdict !== "threat") throw new Error("VERDICT must be safe|threat");

  const w = new Wallet(pk);
  const wallet = w.address;

  const n = await fetch(base + "/api/validator/nonce");
  const { nonce, issuedAt } = await n.json();

  const msg = [
    "SpotBlock validator review submission",
    "Wallet: " + wallet,
    "Address: " + ethers.getAddress(target),
    "ChainId: 1",
    "Verdict: " + verdict,
    "Nonce: " + nonce,
    "IssuedAt: " + issuedAt,
  ].join("\n");

  const signature = await w.signMessage(msg);

  const comment = [
    "Validator review (external tooling)",
    "Verdict: " + verdict,
    "",
    "Reasoning:",
    "- paste your full analysis here (can include AI agent output, links, etc.)",
  ].join("\n");

  const r = await fetch(base + "/api/validator/review", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      address: ethers.getAddress(target),
      chainId: 1,
      verdict,
      comment,
      toolName,
      wallet,
      nonce,
      issuedAt,
      signature,
    }),
  });

  console.log("status", r.status);
  console.log(await r.json());
})().catch(console.error);
'

Target address can be a staging ticket (from /api/validator/pending). If verdict is threat, the submission auto-promotes it into the malicious explorer database.