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.