Create a key
In your dashboard, under API keys. Shown once.
Send an address
One POST request. It takes 10 to 60 seconds.
Read the report
Risk, flow of funds, named exchanges and mixers.
| Base URL | https://augis.eu/api/v1 |
| Format | HTTPS, JSON in and out. |
| Authentication | A secret key: Authorization: Bearer wk_… |
| Pricing | An API plan with a quota of traces. It never uses your credits. |
Quickstart
Replace AUGIS_KEY with your key and run this. It traces an Ethereum address and prints the verdict.
curl -X POST https://augis.eu/api/v1/wake/trace \
-H "Authorization: Bearer $AUGIS_KEY" \
-H "Content-Type: application/json" \
--max-time 120 \
-d '{"address": "0x8589427373D6D84E98730D7795D8f6f8731FDA16", "depth": 3, "direction": "out"}' const res = await fetch("https://augis.eu/api/v1/wake/trace", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUGIS_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ address: "0x8589427373D6D84E98730D7795D8f6f8731FDA16", depth: 3, direction: "out" }),
signal: AbortSignal.timeout(120_000), // a trace can take up to a minute
});
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.code ?? ""} ${body.error}`);
const { result } = body;
console.log(result.risk, result.verdict);
for (const hit of result.reached.filter((r) => r.label)) console.log(hit.label, hit.categories);import os, requests
res = requests.post(
"https://augis.eu/api/v1/wake/trace",
headers={"Authorization": f"Bearer {os.environ['AUGIS_KEY']}"},
json={"address": "0x8589427373D6D84E98730D7795D8f6f8731FDA16", "depth": 3, "direction": "out"},
timeout=120, # a trace can take up to a minute
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {body.get('code', '')} {body['error']}")
result = body["result"]
print(result["risk"], result["verdict"])
for hit in (r for r in result["reached"] if r["label"]):
print(hit["label"], hit["categories"])The answer is a JSON report. A trace takes up to about a minute, so give your HTTP client a timeout of at least 120 seconds.
Try it
Run a real trace from your browser. Your key is sent only to Augis and is not stored. This uses one trace of your quota.
Authentication and limits
Your key
- Send it on every request:
Authorization: Bearer wk_… wk_then 48 hexadecimal characters.- 5 active keys at most. Use one per application.
- We store only a hash. Lost it? Revoke it and make another. Revoking is immediate (
401afterwards). - Keep it on your server, never in a web page or an app.
Your quota
- Monthly = the last 30 days. Daily = the last 24 hours. Rolling, no reset date.
- Only a finished trace counts. A failure costs nothing.
"effort": "thorough"counts 3 traces.- API traces are not saved in your history.
- Check it any time:
GET /account.
| Plan | Price / 30 days | Traces / 30 days | Traces / day | At once | Requests / min |
|---|---|---|---|---|---|
| Sandbox | $15 | 200 | 20 | 1 | 5 |
| Developer | $99 | 3,000 | 150 | 1 | 20 |
| Production | $399 | 15,000 | 750 | 3 | 100 |
| Scale | $1,299 | 60,000 | 3,000 | 8 | 300 |
At once: traces that can run at the same time on your account. Requests / min: per key, on every endpoint.
Trace an address
/api/v1/wake/traceFollows funds from an address, hop by hop, largest flows first, and returns the report. One call is one trace.
Body
| Field | Type | Description |
|---|---|---|
address required | string | A Bitcoin address (bc1…, 1…, 3…) or an Ethereum address (0x…). The chain is detected for you. |
direction | "out" | "in" | "out" (default) follows where funds went. "in" follows where they came from. |
depth | 1 to 6 | Hops to follow. Default 3. |
from, to | YYYY-MM-DD | Only transfers between these days (UTC). |
minValue | number | Ignore transfers below this value. USD on Ethereum, BTC on Bitcoin. |
assets | "all" | "native" | "tokens" | Ethereum only. ETH only, tokens only, or both. |
exclude | string[] | Up to 25 addresses of the same chain to leave out. |
detail | "broad" | "standard" | "fine" | How small a flow is still followed. "fine" returns the most addresses. |
effort | "quick" | "standard" | "thorough" | How much data to read. "thorough" goes furthest and counts 3 traces. |
If result.partial is true, the trace stopped early at the limits of its data source. The report is valid for what it covers: trace again from one of the addresses that were not followed.
Request
curl -X POST https://augis.eu/api/v1/wake/trace \
-H "Authorization: Bearer $AUGIS_KEY" \
-H "Content-Type: application/json" \
--max-time 120 \
-d '{"address": "0x8589427373D6D84E98730D7795D8f6f8731FDA16", "depth": 3, "direction": "out"}' const res = await fetch("https://augis.eu/api/v1/wake/trace", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUGIS_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ address: "0x8589427373D6D84E98730D7795D8f6f8731FDA16", depth: 3, direction: "out" }),
signal: AbortSignal.timeout(120_000), // a trace can take up to a minute
});
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.code ?? ""} ${body.error}`);
const { result } = body;
console.log(result.risk, result.verdict);
for (const hit of result.reached.filter((r) => r.label)) console.log(hit.label, hit.categories);import os, requests
res = requests.post(
"https://augis.eu/api/v1/wake/trace",
headers={"Authorization": f"Bearer {os.environ['AUGIS_KEY']}"},
json={"address": "0x8589427373D6D84E98730D7795D8f6f8731FDA16", "depth": 3, "direction": "out"},
timeout=120, # a trace can take up to a minute
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {body.get('code', '')} {body['error']}")
result = body["result"]
print(result["risk"], result["verdict"])
for hit in (r for r in result["reached"] if r["label"]):
print(hit["label"], hit["categories"])Response, 200
{
"result": {
"chain": "eth",
"direction": "out",
"start": "0x8589427373d6d84e98730d7795d8f6f8731fda16",
"depth": 3,
"partial": false,
"params": { "from": null, "to": null, "minValue": 0, "assets": "all", "exclude": [], "detail": "standard", "effort": "standard", "filtered": "" },
"generatedAt": "2026-10-15T19:46:51.551Z",
"tool": { "name": "Wake", "version": "2.0" },
"risk": "high",
"summary": { "addresses": 25, "transactions": 40, "apiCalls": 11, "labelled": 7, "expanded": 1, "coverage": 0.669 },
"verdict": "Of the $629,459 that left this address, 34% went through mixing services, 30% is still held, 33% was not followed further. 3 high-severity alerts.",
"flags": [{ "severity": "high", "type": "labelled start address", "detail": "The address you entered is itself labelled: tornado.cash (mixing)." }],
"reached": [{ "address": "0x905b63ff…7601", "hop": 1, "label": "tornado.cash", "categories": ["mixing"], "source": "Augis address intelligence", "received": { "ETH": 123 }, "txCount": null, "reason": "known address" }],
"held": [{ "address": "0xdcbeffbe…3193", "label": "OFAC-listed address", "balances": { "ETH": 13.5, "USDT": 30108.36, "USDC": 0 }, "freezable": ["USDT"] }],
"attribution": { "unit": "USD", "total": 629459.13, "buckets": [{ "bucket": "mixing", "label": "Mixing service", "value": 215000.4, "share": 0.34 }], "flows": [] },
"nodes": [], "edges": [], "timeline": [], "exposure": [], "freeze": [], "caveats": [],
"sha256": "2fe52c040cfeaa0f15cc2bd99d60f09f3cd996bcd874071ba7b1821607c1e945"
}
}Check your usage
/api/v1/accountYour API plan and how much of the daily and monthly quota you have used. It does not start a trace.
| Field | Description |
|---|---|
plan | Name of your API plan. |
expiresAt | End of the plan, in milliseconds since 1970 (UTC). |
day | used in the last 24 hours, and your limit. |
month | used in the last 30 days, and your limit. |
Request
curl https://augis.eu/api/v1/account \
-H "Authorization: Bearer $AUGIS_KEY"Response, 200
{
"plan": "Developer",
"expiresAt": 1794678374000,
"day": { "used": 12, "limit": 150 },
"month": { "used": 431, "limit": 3000 }
}The report
Start with these four fields. Everything else is detail you can read when you need it.
riskOne word: high, medium, low or none found.
verdictThe result in a sentence you can show a person.
reached[]The named places the funds went: exchanges, mixers, sanctioned addresses.
held[]Where money still sits at the end, and what a stablecoin issuer could review.
Every field
Open an object to see its fields. New fields may appear over time: ignore keys you do not know.
resulttop level
| Field | Type | Description |
|---|---|---|
chain, direction, start, depth | What you asked for. Ethereum addresses are lower case. | |
partial | boolean | true when the trace stopped early. |
params | object | The filters applied, and filtered, a sentence describing them. |
generatedAt, labelsBuilt, tool, sources | When the report was made, when names were last updated, the report version, the kinds of data used. | |
summary | object | addresses, transactions, labelled, expanded, coverage (0 to 1: the share of the value that ends at a known destination). |
exposure[] | array | Named addresses with the path from the start. Risky ones first. |
timeline[] | array | day, value, count, top: value moved per day and the largest transaction. |
freeze[] | array | Stablecoin balances an issuer could review, with a draft request. Outgoing traces only. |
caveats[] | string[] | The limits of the method. Show them with the results. |
sha256 | string | Fingerprint of the report. See Verifying a report. |
flags[]alerts
| Field | Type | Description |
|---|---|---|
severity | "high" | "medium" | "info" | How serious. |
type | string | A short name for the kind of alert. |
detail | string | What was found, in a sentence. |
reached[]named places reached
| Field | Type | Description |
|---|---|---|
address | string | |
hop | integer | Distance from the start address. |
label | string | null | The name, when known. |
categories | string[] | What kind of address it is. |
source | string | null | Where the name comes from. |
received | object | Amount received per asset, e.g. {"ETH": 123} |
txCount | integer | null | |
reason | string | Why it is listed. |
held[]balances left at the end
| Field | Type | Description |
|---|---|---|
address | string | |
label | string | null | |
balances | object | Balance per asset: the native coin and the main stablecoins. |
freezable | string[] | Stablecoins in the balance that an issuer can restrict, e.g. USDT. |
attributionwhere the value went
| Field | Type | Description |
|---|---|---|
unit | "USD" | "BTC" | USD for Ethereum, BTC for Bitcoin. |
total | number | Value traced, in unit. |
buckets[] | array | bucket, label, value, share (share is 0 to 1). Buckets: sanctioned, mixing, flagged, exchange, bridge, defi, contract, service, mining, held, beyond. |
flows[] | array | from, to, value, fromHop, toHop: value moving between two addresses, ready for a flow diagram. |
nodes[]up to 500 addresses
| Field | Type | Description |
|---|---|---|
address, hop, label, categories | As in reached. | |
bucket | string | null | The attribution bucket the address falls in. |
terminal | string | null | Why the trace stopped here: "labelled" (a named service) or "high-volume". |
expanded | boolean | Whether its transfers were read. |
balances | object | null | Present where the funds ended. |
inflow | number | Value attributed to the address, in attribution.unit. |
firstSeen, lastSeen | integer | null | Unix seconds. |
received, sent | object | Totals per asset within the trace. |
edges[]up to 600 transfers
| Field | Type | Description |
|---|---|---|
from, to | string | Addresses. |
asset | string | The coin or token symbol. |
amount | number | In units of the asset. |
usd | number | Value in dollars on the day (Ethereum). |
value | number | The value used for the shares. |
priced | boolean | false: the token has no market price, so it is left out of the shares. |
txid, time, block | Transaction hash, Unix seconds, block number. | |
kind | string | "transfer", "internal" or "token". |
fromLabel, toLabel, fromHop, toHop | Names and distances, when known. |
Recipes
Copy, set AUGIS_KEY, run. Each one starts from the same small trace() helper.
Is this address risky, and why?
const KEY = process.env.AUGIS_KEY;
async function trace(address, extra = {}) {
const res = await fetch("https://augis.eu/api/v1/wake/trace", {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ address, ...extra }),
signal: AbortSignal.timeout(120_000),
});
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.code ?? ""} ${body.error}`);
return body.result;
}
const report = await trace("0x8589427373D6D84E98730D7795D8f6f8731FDA16");
console.log(report.risk, "-", report.verdict);
for (const f of report.flags.filter((f) => f.severity === "high")) console.log(" !", f.type, "-", f.detail);import os, requests
KEY = os.environ["AUGIS_KEY"]
def trace(address, **extra):
res = requests.post(
"https://augis.eu/api/v1/wake/trace",
headers={"Authorization": f"Bearer {KEY}"},
json={"address": address, **extra},
timeout=120,
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {body.get('code', '')} {body['error']}")
return body["result"]
report = trace("0x8589427373D6D84E98730D7795D8f6f8731FDA16")
print(report["risk"], "-", report["verdict"])
for f in (f for f in report["flags"] if f["severity"] == "high"):
print(" !", f["type"], "-", f["detail"])Which exchanges did the funds reach?
const KEY = process.env.AUGIS_KEY;
async function trace(address, extra = {}) {
const res = await fetch("https://augis.eu/api/v1/wake/trace", {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ address, ...extra }),
signal: AbortSignal.timeout(120_000),
});
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.code ?? ""} ${body.error}`);
return body.result;
}
const report = await trace("bc1q…your address…", { direction: "out", depth: 4 });
const exchanges = report.reached.filter((r) => r.categories.includes("exchange"));
for (const x of exchanges) console.log(x.label, x.received, `(hop ${x.hop})`);import os, requests
KEY = os.environ["AUGIS_KEY"]
def trace(address, **extra):
res = requests.post(
"https://augis.eu/api/v1/wake/trace",
headers={"Authorization": f"Bearer {KEY}"},
json={"address": address, **extra},
timeout=120,
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {body.get('code', '')} {body['error']}")
return body["result"]
report = trace("bc1q…your address…", direction="out", depth=4)
for x in (r for r in report["reached"] if "exchange" in r["categories"]):
print(x["label"], x["received"], f"(hop {x['hop']})")Is money still sitting somewhere?
const KEY = process.env.AUGIS_KEY;
async function trace(address, extra = {}) {
const res = await fetch("https://augis.eu/api/v1/wake/trace", {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ address, ...extra }),
signal: AbortSignal.timeout(120_000),
});
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.code ?? ""} ${body.error}`);
return body.result;
}
const report = await trace("0x…the address…");
for (const h of report.held) {
console.log(h.address, h.label ?? "unnamed", h.balances);
if (h.freezable.length) console.log(" a stablecoin issuer could review:", h.freezable.join(", "));
}import os, requests
KEY = os.environ["AUGIS_KEY"]
def trace(address, **extra):
res = requests.post(
"https://augis.eu/api/v1/wake/trace",
headers={"Authorization": f"Bearer {KEY}"},
json={"address": address, **extra},
timeout=120,
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {body.get('code', '')} {body['error']}")
return body["result"]
report = trace("0x…the address…")
for h in report["held"]:
print(h["address"], h["label"] or "unnamed", h["balances"])
if h["freezable"]:
print(" a stablecoin issuer could review:", ", ".join(h["freezable"]))Trace a list without going over your quota
const KEY = process.env.AUGIS_KEY;
async function trace(address, extra = {}) {
const res = await fetch("https://augis.eu/api/v1/wake/trace", {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ address, ...extra }),
signal: AbortSignal.timeout(120_000),
});
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.code ?? ""} ${body.error}`);
return body.result;
}
// Stay inside your quota: ask how much is left, then trace that many addresses, one after the other.
const acc = await (await fetch("https://augis.eu/api/v1/account", { headers: { Authorization: `Bearer ${KEY}` } })).json();
const left = Math.min(acc.day.limit - acc.day.used, acc.month.limit - acc.month.used);
const addresses = ["0x…", "0x…", "0x…"].slice(0, left);
for (const a of addresses) {
const r = await trace(a, { effort: "quick" });
console.log(a, r.risk);
}import os, requests
KEY = os.environ["AUGIS_KEY"]
def trace(address, **extra):
res = requests.post(
"https://augis.eu/api/v1/wake/trace",
headers={"Authorization": f"Bearer {KEY}"},
json={"address": address, **extra},
timeout=120,
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {body.get('code', '')} {body['error']}")
return body["result"]
# Stay inside your quota: ask how much is left, then trace that many addresses, one after the other.
acc = requests.get("https://augis.eu/api/v1/account", headers={"Authorization": f"Bearer {KEY}"}).json()
left = min(acc["day"]["limit"] - acc["day"]["used"], acc["month"]["limit"] - acc["month"]["used"])
for a in ["0x…", "0x…", "0x…"][:left]:
print(a, trace(a, effort="quick")["risk"])Errors
An error has the HTTP status and a JSON body: {"error": "message", "code": "…"}. code appears only for the cases below.
| Status | Code | Meaning | What to do |
|---|---|---|---|
400 | Invalid request: address is missing or is not a Bitcoin or Ethereum address. | Fix the request. | |
401 | No key, a malformed key, a revoked key or an unknown key. | Check the Authorization header. | |
403 | NO_API_PLAN | No active API plan, or it expired. | Buy or renew an API plan. |
422 | The trace could not be completed (a data source did not answer). The message says why. Nothing is counted. | Retry later. | |
429 | DAILY_LIMIT | Daily limit reached. | Wait, or move to a bigger plan. |
429 | MONTHLY_QUOTA | 30-day quota reached. | Wait, or move to a bigger plan. |
429 | Too many requests per minute on the key, or too many traces at once on the account. | Wait a few seconds, retry. | |
500 | A problem on our side. Not counted. | Retry with a delay. | |
503 | Ethereum tracing is unavailable for a moment. | Retry with a delay. |
Retrying the right errors
async function trace(body, tries = 3) {
for (let attempt = 1; ; attempt++) {
const res = await fetch("https://augis.eu/api/v1/wake/trace", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.AUGIS_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
signal: AbortSignal.timeout(120_000),
});
const data = await res.json();
if (res.ok) return data.result;
// 429 with no code: too many requests per minute, or a trace already running. Wait and try again.
// DAILY_LIMIT and MONTHLY_QUOTA will not clear by waiting a few seconds: stop.
const retryable = res.status === 429 && !data.code || res.status >= 500;
if (!retryable || attempt === tries) throw new Error(`${res.status} ${data.code ?? ""} ${data.error}`);
await new Promise((r) => setTimeout(r, 2000 * attempt));
}
}Verifying a report
sha256 is the SHA-256 of the report without that field, written as JSON in the order the fields arrive. Recompute it to check that a saved report was not edited.
import { createHash } from "node:crypto";
const { sha256, ...rest } = result;
const ok = createHash("sha256").update(JSON.stringify(rest)).digest("hex") === sha256;This proves the file is unchanged since Wake produced it, not that its content is correct. Another language gets the same hash only if it writes numbers exactly as JavaScript does: verify with Node.js or in Wake.
Good practice
Be robust
- Timeout of 120 seconds. Never retry a request that is still running: it will finish and count.
- Retry
429without a code and5xx. Do not retryDAILY_LIMITorMONTHLY_QUOTA. - Keep keys server-side. Rotate one if a laptop or a repository is exposed.
Be economical
- A report does not change once made: store it.
- Narrow with
from,to,minValueandexcludebefore reaching for"fine"or"thorough". - Pass
caveatsalong when you show results to someone else.
Changelog
First release: POST /api/v1/wake/trace and GET /api/v1/account, API plans with daily and monthly quotas, API keys in the dashboard.
Support
A question, a limit that gets in your way, a plan that does not fit: contact@augis.eu. The Parallax API is in early access: write to the same address to join the waitlist.