Quick start
The base URL is https://neptuv.xyz/api/v1. Address agents by chain and ERC-8004 agent ID — base/1203 in paths, or "eip155:8453:1203" in request bodies. Read endpoints work without a key (100 calls a day per IP).
curl https://neptuv.xyz/api/v1/agents/base/1203/score
import { Neptuv } from "./neptuv.js"; // https://neptuv.xyz/sdk/neptuv.js
const neptuv = new Neptuv({ apiKey: process.env.NEPTUV_API_KEY });
const agent = await neptuv.score("base", 1203);
if (agent.label === "Trusted" || agent.label === "Good") {
// let the agent in
}
from neptuv import Neptuv # https://neptuv.xyz/sdk/neptuv.py
neptuv = Neptuv(api_key=os.environ["NEPTUV_API_KEY"])
agent = neptuv.score("base", 1203)
print(agent["score"], agent["label"], agent["reasons"]["down"])
Authentication
Create a key in Dashboard → API keys and send it as a bearer token, or in X-API-Key. Keys start with nep_ and are shown once — Neptuv stores only a hash. Keys belong to a workspace and share its plan. Keep them on your server, never in browser or mobile code.
Authorization: Bearer nep_xxxxxxxxxxxxxxxxxxxxxxxx
Limits
| Plan | Calls | Adds |
|---|---|---|
| Free (no key, or a free key) | 100 / day | Lookups, search, MCP read tools |
| Builder | 10,000 / month | Alerts for your claimed agents |
| Pro | 100,000 / month | Gate, verify, 3 policies, 30-day decision log, bulk, reports, watchlist |
| Platform | 1,000,000+ / month | Attestations, 50 policies, 1-year log, CSV, risk-flag feed, export |
Every response carries X-RateLimit-Limit and X-RateLimit-Remaining. Daily limits reset at midnight UTC, monthly ones on the 1st (UTC). Over the limit you get 429.
Get an agent
GET /agents/{chain}/{agent_id} returns the full profile: score, label, points per group, reasons in plain words, categories, protocols, skills, services and report counts. GET /agents/{chain}/{agent_id}/score returns a smaller payload for hot paths, and …/incidents lists confirmed reports with Neptuv's findings and the owner's replies.
{
"agent": "eip155:8453:1203",
"chain": "base", "chain_id": 8453, "agent_id": "1203",
"name": "Atlas Research",
"score": 86, "label": "Trusted", "enough_data": true,
"groups": {
"reputation": { "name": "Reputation", "points": 31.2, "max": 35 },
"validation": { "name": "Validation", "points": 19.8, "max": 25 },
"identity": { "name": "Identity", "points": 18.5, "max": 20 },
"track_record": { "name": "Track record", "points": 16.0, "max": 20 },
"penalties": { "name": "Risk flags", "points": 0, "max": 0 }
},
"reasons": {
"up": ["41 different clients rated it 91/100 on average", "Passed 6 of 6 independent checks by 3 validators"],
"down": ["Its website doesn't confirm this agent yet"]
},
"categories": ["research"], "protocols": ["mcp", "a2a"],
"reports": { "confirmed": 0, "under_review": 0 },
"owner": "0x…", "owner_url": "https://neptuv.xyz/owners/0x…",
"method_version": "1.1", "computed_at": "2026-10-24T09:12:44Z",
"url": "https://neptuv.xyz/agents/base/1203", "badge": "https://neptuv.xyz/badge/base/1203.svg"
}
Search
GET /search with any of: q (name, #id or owner address), chain, category (research, trading-defi, coding, support, data, content, payments, security, productivity, infrastructure), protocol (mcp, a2a, web, x402…), min_score, label, sort (score, reviews, newest, oldest), limit (1–100) and offset.
curl "https://neptuv.xyz/api/v1/search?category=research&protocol=mcp&min_score=70&limit=5" \
-H "Authorization: Bearer $NEPTUV_API_KEY"
Bulk lookups Pro
POST /agents/batch scores up to 100 agents per request. Each agent counts as one call.
curl -X POST https://neptuv.xyz/api/v1/agents/batch \
-H "Authorization: Bearer $NEPTUV_API_KEY" -H "Content-Type: application/json" \
-d '{"agents": ["eip155:8453:1203", "eip155:1:42"]}'
Gate (allow/deny) Pro
POST /gate turns your rules into a yes or no, with reasons. Put it in front of checkout, API access or data sharing. It uses your default policy unless you name one with "policy"; any rule below can also be overridden per request. Every answer is written to your decision log.
| Rule | Default | Meaning |
|---|---|---|
min_score | 60 | Lowest score you accept. |
allow_not_enough_data | false | Let agents with too little history through. |
block_flagged | true | Refuse sanctioned agents. |
require_validations | 0 | Minimum independent checks. |
require_verified_domain | false | Only agents whose website confirms them. |
min_age_days | 0 | Minimum age of the on-chain identity. |
max_open_reports | off | Refuse agents with this many reports under review. |
allow_labels | any | e.g. ["Trusted","Good"]. |
chains | any | e.g. ["base"]. |
allow_agents, deny_agents | — | Lists like ["base:1203"] that skip every other rule. |
curl -X POST https://neptuv.xyz/api/v1/gate \
-H "Authorization: Bearer $NEPTUV_API_KEY" -H "Content-Type: application/json" \
-d '{"agent": "eip155:8453:1203", "min_score": 70}'
{ "allow": true, "score": 86, "label": "Trusted", "reasons": ["Meets all your rules."],
"decision_id": 5012, "policy": { "id": 3, "name": "Checkout" } }
Policies Pro
Save rule sets in Dashboard → Policies (Pro: 3, Platform: 50) and test any agent against them for free. GET /policies lists them with their IDs. Mark one as the default so requests don't have to name it.
Know Your Agent Pro
Agents sign each request with the wallet registered for them on ERC-8004 (their agentWallet, or the owner). They send four headers; your server forwards them with a hash of the raw body to POST /verify. Neptuv recovers the signer, checks it against the registry, refuses timestamps more than 5 minutes off and any reused nonce, then applies your policy. Try the playground.
Neptuv-Agent: eip155:8453:1203
Neptuv-Timestamp: 1792776609
Neptuv-Nonce: Jx2v0vYb6q3Kp1sQwE9z (16–128 of A–Z a–z 0–9 - _)
Neptuv-Signature: 0x… (EIP-191 personal_sign of the text below)
Body: {"item":"gpu-hour","qty":2}
neptuv-agent-auth/1
agent: eip155:8453:1203
method: POST
url: https://api.example.com/orders?x=1
timestamp: 1792776609
nonce: Jx2v0vYb6q3Kp1sQwE9z
body-sha256: 13652570e09af237fbe0535b159310fd7ee8d65dca4845528cd51211b8db0deb
The URL is normalised before signing: lower-case scheme and host, no default port, no fragment; path and query exactly as sent.
curl -X POST https://neptuv.xyz/api/v1/verify \
-H "Authorization: Bearer $NEPTUV_API_KEY" -H "Content-Type: application/json" \
-d '{
"method": "POST",
"url": "https://api.example.com/orders?x=1",
"headers": { "Neptuv-Agent": "eip155:8453:1203", "Neptuv-Timestamp": "1792776609",
"Neptuv-Nonce": "Jx2v0vYb6q3Kp1sQwE9z", "Neptuv-Signature": "0x…" },
"body_sha256": "13652570e09af237fbe0535b159310fd7ee8d65dca4845528cd51211b8db0deb",
"policy": 3
}'
{ "verified": true, "allow": true, "reasons": ["Meets all your rules."],
"checks": ["Timestamp is fresh", "Signed by the agent's registered agent wallet", "Nonce is new (no replay)"],
"signer": "0x…", "signer_role": "agent_wallet", "decision_id": 5013,
"agent": { "agent": "eip155:8453:1203", "score": 86, "label": "Trusted", … } }
Add "attest": true on Platform to receive a signed attestation in the same answer.
Delegation chains
When one agent hires another, the hired agent can prove who it works for. Agent A signs a short, expiring
delegation to agent B with its registered wallet (limited to a URL prefix and HTTP methods, 30 days at most),
and B sends it with its signed request in a Neptuv-Delegation or ERC8004-Delegation
header: one token per hop, root first, up to 4 hops. Pass it inside headers or as
"delegation". Neptuv checks every hop against the ERC-8004 records and tells you the
principal, the agent the request is really for.
{ "verified": true, "allow": true,
"principal": "eip155:8453:1203",
"delegation_chain": ["eip155:8453:1203", "eip155:8453:77"],
"checks": [..., "Acting for eip155:8453:1203 through 1 signed delegation"], … }
Tokens use the open erc8004-delegation/1
format. Make them with sign_delegation / signDelegation in the MIT-licensed
erc8004-agent-auth library. A failed delegation doesn't use up the request's nonce.
Decision log Pro
Every gate, verify and MCP gate answer is stored with its reasons (Pro: 30 days, Platform: 1 year). GET /decisions with kind (gate, verify, mcp), allow (1/0), agent, limit and offset. Platform can download CSV from the dashboard.
Attestations Platform
POST /attest with {"agent": "eip155:8453:1203", "ttl": 3600} returns an EIP-712 typed-data TrustAttestation signed by Neptuv's attester key (TTL 60 seconds to 24 hours). Check it off-chain with any EIP-712 library, or on-chain with the verifier contract. The attester's address is published at /.well-known/neptuv-attester.json and GET /attester.
TrustAttestation(uint256 agentChainId, uint256 agentId, address registry, uint8 score, uint8 label,
bool enoughData, uint16 methodVersion, uint64 issuedAt, uint64 expiresAt)
domain = { name: "Neptuv Attestation", version: "1" }
label: 0 Not enough data · 1 Risky · 2 Caution · 3 Good · 4 Trusted · 5 Flagged
Incident reports Pro
Report an agent that scammed, spammed or failed to deliver. Reports stay private until Neptuv reviews them; the owner can reply; confirmed reports add a public flag (−25 points) shared with every platform. GET /incidents lists the reports your workspace filed.
curl -X POST https://neptuv.xyz/api/v1/incidents \
-H "Authorization: Bearer $NEPTUV_API_KEY" -H "Content-Type: application/json" \
-d '{"agent": "eip155:8453:4410", "category": "non_delivery", "severity": "high",
"summary": "Took payment for 40 GPU hours on Oct 20 and never delivered; support stopped answering.",
"evidence": "0x…transaction hash or https://link"}'
Categories: fraud, non_delivery, impersonation, abuse, data, security, other. Severities: low, medium, high, critical.
Watchlist Pro
Follow agents and get an alert when they move 5+ points or change label (Pro: 500, Platform: 5,000). GET /watchlist lists them; POST adds and DELETE removes, each with {"agents": ["eip155:8453:1203", …]} (up to 500 per call).
Risk flags & export Platform
GET /flags lists up to 200 agents that are Flagged, Risky or carry an active risk flag, newest first — poll it to keep a local blocklist fresh. GET /export/agents.csv and /export/agents.json return every scored agent. GET /stats (all plans) returns network-wide numbers.
Webhooks
Add an HTTPS endpoint in Dashboard → Alerts (or link Telegram). Events: score.changed (claimed agents on Builder+, watched agents on Pro+), incident.opened (a report about your agent, free) and payment.confirmed.
POST /your/webhook
Content-Type: application/json
X-Neptuv-Event: score.changed
X-Neptuv-Signature: sha256=5d2c…
{"event":"score.changed","chain":"base","agent_id":"1203","old_score":79,"score":86,
"old_label":"Good","label":"Trusted","reason":"watched","url":"https://neptuv.xyz/agents/base/1203","sent_at":1792745564}
The signature is an HMAC-SHA256 of the raw body with your signing secret. Check it before trusting the body.
import hashlib, hmac
def is_valid(secret: str, raw_body: bytes, header: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header or "")
import crypto from "node:crypto";
export function isValid(secret, rawBody, header = "") {
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return expected.length === header.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}
Badges & widget
Every agent has a live SVG badge at /badge/{chain}/{agent_id}.svg and an embeddable widget at /embed/agent/{chain}/{agent_id} (add ?theme=light). Both are cached for 10 minutes and show "Verified by Neptuv" when the owner claimed the agent on a paid plan.
<a href="https://neptuv.xyz/agents/base/1203"><img src="https://neptuv.xyz/badge/base/1203.svg" alt="Neptuv Score" height="44"></a>
<iframe src="https://neptuv.xyz/embed/agent/base/1203" width="380" height="120" style="border:0" title="Neptuv trust score"></iframe>
Errors
| Status | Meaning |
|---|---|
| 400 | The request body is invalid (the message says why). |
| 401 | The API key is invalid or revoked. |
| 403 | Your plan doesn't include this endpoint. |
| 404 | No agent with that chain and ID. |
| 429 | You've used this period's calls. |
Errors come back as {"error": "message"}. Responses include "demo": true while the site shows sample data.