og_live_ and are high-entropy random. We store
only a SHA-256 hash of your key — we cannot show it to you again, and a database of ours does not contain
a usable key.Base URL and your first call
The API is served over HTTPS at:
https://api.omniguardlabs.com
https://check.provewall.com is an equivalent alternate origin — same
service, same routes, same keys. Use either. There is no api.provewall.com; if you have
it written down somewhere, it will not resolve.
curl -X POST https://api.omniguardlabs.com/v1/verdict \
-H "X-Api-Key: og_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"address":"0xdAC17F958D2ee523a2206206994597C13D831ec7","chain":"eth"}'
Every endpoint below lives on that base URL.
| Endpoint | Auth | What it does |
|---|---|---|
| POST /v1/verdict | API key | Execution-truth honeypot / exitability verdict for one token. |
| GET /v1/usage | API key | Your plan and this month’s call count. |
| POST /api/receipt/verify | none | Verify a signed receipt we issued. Keyless on purpose — your users can check us without an account. |
| GET /health | none | {"ok":true}. For your uptime monitor. |
| GET /status | none | Which detection layers are live or degraded on the serving box, right now. |
One key, two accepted headers
Send your key as either header. Both work identically; pick one.
X-Api-Key: og_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
— or —
Authorization: Bearer og_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
401, never a silent pass and never a partial verdict.401.Rotating or lost a key? Reply to your key email or write to support and we will revoke the old one and issue a replacement. Revocation takes effect on the next call.
Per key, per minute and per day
These are the limits configured in the service today. Both windows are sliding, and both are counted per key.
| Plan | Requests / minute | Requests / day |
|---|---|---|
| Dev | 30 | 2,000 |
| Pro | 120 | 20,000 |
| Enterprise | 600 | 200,000 |
Exceeding either window returns 429 with the plan
named in the body. There is no monthly call quota on any plan — we meter your usage and show it back
to you, but we do not cut you off at a number. If you need a higher ceiling than Enterprise,
talk to us.
POST /v1/verdict
POST /v1/verdict
X-Api-Key: og_live_…
Content-Type: application/json
{"address": "0x…", "chain": "eth"}
| Field | Required | Notes |
|---|---|---|
| address | yes | EVM: 0x + 40 hex characters. Solana: a base58 mint address, 32–44 characters. |
| chain | no | One of eth, bsc, sol. Aliases ethereum, bnb, solana also work. Defaults to eth. Need Base or another chain? Ask us — the router accepts it but we will confirm it is provisioned for you before you build on it. |
Unknown fields are ignored. One token per call.
200 — a freshly proven verdict
Returned when we ran a fork for this call (cached:
false). Illustrative shape:
{
"verdict": "SELLABLE", // HONEYPOT | SELLABLE | UNVERIFIED
"proven": true, // true only when execution produced this verdict
"sellable": true, // the sell succeeded AT THIS BLOCK
"buy_tax": 0.0,
"sell_tax": 3.0,
"tax_tier": "GREEN",
"severity": { "color": "GREEN", "emoji": "…", "meaning": "…", "label": "…", "sell_tax": 3.0 },
"owner_powers": ["mint - owner can mint / dilute supply"],
"owner_powers_status": "known", // known | unavailable
"block": <int>, // block height the fork ran at
"receipt": { "id": "rc_…", "signed": { … } },
"heuristics": [ { "name": "owner_power", "level": "mandatory", "note": "…" } ],
"chain": "eth",
"address": "0x…",
"detail": "SELL REVERTED", // machine-ish reason
"summary": "…", // one sentence, plain English
"trap_capable": false,
"reachable_traps": [],
"cached": false,
"usage": { "plan": "pro", "used_this_month": 412 }
}
| Field | Type | Meaning |
|---|---|---|
| verdict | string | HONEYPOT — we bought it and the sell reverted or trapped the funds. SELLABLE — the sell went through at this block. UNVERIFIED — we could not execute a proof, so we are not grading it. |
| proven | bool | True only when execution produced the verdict. Never infer safety from proven: false. |
| sellable | bool | The exit worked at block. This is a fact about one block, not a promise about the future. |
| buy_tax / sell_tax | number | null | Measured percentages. null when not measurable. |
| tax_tier | string | null | Band for the sell tax: GREEN / YELLOW / ORANGE / RED. |
| severity | object | color (GREEN / YELLOW / ORANGE / RED / GREY), plus a human label and meaning. Derived from the proof, not from a score. |
| owner_powers | string[] | Owner capabilities we found — mint, blacklist, pause, fee switches and similar. |
| owner_powers_status | string | known or unavailable. Tells you whether the check ran, so an empty list can never be mistaken for “checked, found nothing”. |
| block | int | null | Block height the fork ran at. |
| receipt | object | null | { id, signed } when the verdict is proven. Verify it yourself at POST /api/receipt/verify — no key needed. |
| heuristics | object[] | { name, level, note }. level: "mandatory" marks owner powers you should surface to your user. |
| chain / address | string | Echoed back. |
| detail | string | Short machine-ish reason, e.g. SELL REVERTED or no Uniswap-V2 route to buy. |
| summary | string | One-sentence plain-English explanation, safe to show a user. |
| trap_capable / reachable_traps | bool / array | Whether the owner can turn an exitable token into a trap after you buy. Surface trap_capable to your user when present. |
| cached | bool | Whether this came from the verdict ledger. Branch on this — see below. |
| usage | object | { plan, used_this_month }. |
| degraded | string[] | Only present when a check could not run. See “Fail closed” below. |
| degraded_detail | object | Why each degraded check could not run. |
| exit_proven / exit_proof | bool / object | Present alongside degraded: the execution truth we did establish, preserved even when the overall grade was downgraded. |
Fail closed: a downgrade is not a clean bill of health
If a control could not run — most often reading owner powers — we do not hand out a
green light we did not earn. The execution truth is preserved in exit_proven /
exit_proof, but the grade degrades: verdict becomes
UNVERIFIED, proven becomes false,
severity.color becomes GREY, and
degraded_detail says exactly what failed. A proven HONEYPOT is never downgraded this
way — softening a proven trap because a side-check failed would be the same mistake in the other direction.
Integration rule: never treat “not HONEYPOT” as safe.
Gate on verdict === "SELLABLE" && proven === true, and surface
degraded and trap_capable to your user when present.
200 — served from the verdict ledger (cached: true)
We research once and serve forever until the record ages out or a monitored change invalidates it, so you are not paying fork latency for a token we already proved. The freshness window is 24 hours. A cached response is a smaller object — this is the one shape difference you must handle:
{
"verdict": "SELLABLE", "proven": true, "sellable": true,
"block": <int>, "receipt_id": "rc_…", "ts": <unix seconds>, "stale": false,
"chain": "eth", "address": "0x…",
"trap_capable": false, "reachable_traps": [],
"receipt": { "id": "rc_…", "signed": { … } },
"cached": true,
"usage": { "plan": "pro", "used_this_month": 413 }
}
A cached response has no severity,
summary, heuristics, owner_powers,
detail, tax_tier or tax fields. ts is the
unix time the fork actually ran, so you can always tell how old the underlying proof is. If you need a guaranteed
fresh fork on a specific token, tell us — forced
re-proving is available and we will wire it to your key.
Every status this endpoint returns
| Status | Body | Cause |
|---|---|---|
| 401 | {"error":"invalid or revoked API key","verdict":null} | Key missing, malformed, unknown, or revoked (including after a cancelled or unpaid subscription). |
| 401 | {"error":"key lacks required scope: scan:read","verdict":null} | Valid key, but scoped away from this endpoint. Contact us to widen it. |
| 429 | {"error":"rate limit exceeded for plan '<plan>'","verdict":null} | Per-minute or per-day window exceeded. Back off and retry; the windows are sliding. |
| 405 | {"detail":"Method Not Allowed"} | Anything other than POST. The allow header names the right one. |
| 400 | Disallowed CORS origin | A browser preflight from an origin outside our allowlist. Call from your server instead. |
Input errors arrive as 200, not 4xx. A malformed address or an
unsupported chain returns 200 with
{"error":"invalid address","verdict":"UNVERIFIED", …}. Check for an
error key in the body, not only the HTTP status. We consider this wrong and intend to move
input validation to 400; it is documented here because it is what the endpoint does today,
and we would rather you code against the truth than against our intentions.
Copy, paste, run
curl
curl -sS -X POST https://api.omniguardlabs.com/v1/verdict \
-H "X-Api-Key: $OMNIGUARD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"address":"0xdAC17F958D2ee523a2206206994597C13D831ec7","chain":"eth"}'
Node.js (server-side fetch)
// Runs on your server. Never ship the key to a browser.
async function verdict(address, chain = "eth") {
const res = await fetch("https://api.omniguardlabs.com/v1/verdict", {
method: "POST",
headers: {
"X-Api-Key": process.env.OMNIGUARD_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ address, chain }),
});
if (res.status === 401) throw new Error("OmniGuard: key rejected");
if (res.status === 429) throw new Error("OmniGuard: rate limited — back off");
const data = await res.json();
if (data.error) throw new Error("OmniGuard: " + data.error); // input errors come back as 200
// Fail closed: only "proven SELLABLE, nothing degraded" counts as a pass.
const safeToTrade =
data.verdict === "SELLABLE" && data.proven === true &&
!data.degraded && data.trap_capable !== true;
return { safeToTrade, verdict: data.verdict, cached: data.cached, raw: data };
}
Python (requests)
import os, requests
BASE = "https://api.omniguardlabs.com"
SESSION = requests.Session()
SESSION.headers.update({"X-Api-Key": os.environ["OMNIGUARD_API_KEY"],
"Content-Type": "application/json"})
def verdict(address: str, chain: str = "eth") -> dict:
r = SESSION.post(f"{BASE}/v1/verdict",
json={"address": address, "chain": chain}, timeout=120)
if r.status_code == 401:
raise RuntimeError("OmniGuard: key rejected")
if r.status_code == 429:
raise RuntimeError("OmniGuard: rate limited - back off and retry")
r.raise_for_status()
data = r.json()
if "error" in data: # input errors come back as HTTP 200
raise ValueError(f"OmniGuard: {data['error']}")
return data
v = verdict("0xdAC17F958D2ee523a2206206994597C13D831ec7", "eth")
# Fail closed. "Not HONEYPOT" is not the same thing as safe.
ok = (v["verdict"] == "SELLABLE" and v["proven"] and
not v.get("degraded") and not v.get("trap_capable"))
# A cached record is a smaller object - only the fresh shape carries `summary`.
print(v["verdict"], "| cached:", v["cached"], "|", v.get("summary", "(cached record)"))
Checking your usage
curl -sS https://api.omniguardlabs.com/v1/usage -H "X-Api-Key: $OMNIGUARD_API_KEY"
→ {"plan":"pro","prefix":"og_live_a1b2","used_this_month":413}
Verifying a receipt (no key required)
curl -sS -X POST https://api.omniguardlabs.com/api/receipt/verify \
-H "Content-Type: application/json" \
-d '{"receipt": { … the signed object from a verdict … }}'
→ {"valid":true}
This one is deliberately keyless: your users can check our proof without an account with us.
How you actually get access
Being straight with you about this, because you are buying a developer product and need to know whether a human is in the loop:
Your first email after checkout is a generic thank-you for the subscription. The key arrives in a separate email titled “Your OmniGuard API key”. If you only ever see the first one, write to us — do not assume it is still processing.
Evaluating before you buy, or need an invoice, a DPA, a specific chain, or a higher ceiling? Talk to us first — we would rather scope it properly than take a subscription that does not fit.
Three tiers, priced by throughput
All three tiers return the same verdict, with the same proof and the same signed receipt. What you are buying as you move up is throughput, not a better answer. We would rather say that plainly than imply a capability ladder that does not exist.
Buying Enterprise for the volume alone works, but if you want it for anything else — an SLA, dedicated capacity, a private hostname, procurement paperwork — speak to us first so we can put it in writing rather than have you infer it from a price.
What this API does not do
trap_capable is warning you about.cached and
ts always tell you which you got.Something in these docs not matching what you observe? That is a bug and we want to hear about it — tell us and we will fix the endpoint or the page, whichever is wrong.