docs // $ man daoaml(1) · api: v1 · base: https://daoaml.com/api/v1

The whole REST API, on one page

Authentication, the endpoints that matter, every error code. The same surface the client-app speaks — public, paginated where it makes sense, OpenAPI-described.

# 01

Quickstart — first verdict in 30 seconds

Anonymous flow needs no signup — 3 checks / month per IP+browser are free. Past that, sign in (magic-link or wallet) and either burn the free quota of a paid plan or pay per call via x402.

# Anonymous one-shot — same shape the client-app uses
$ curl -X POST https://daoaml.com/api/v1/check \
    -H "Content-Type: application/json" \
    -d '{"address":"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045","blockchain":"ethereum"}'

→ { "result": { "risk_score": 5, "risk_level": "LOW",
                "check_id": "chk_01HZK4M7Q3X9", ... },
    "quota": { "limit": 3, "used": 1, ... } }

# The check_id powers permalinks at https://daoaml.com/v/<short_id>

A verdict check_id is the handle compliance teams share — it resolves to daoaml.com/v/<id>, an immutable, signed permalink they can verify on their side.

# 02

Authentication

Three identity paths: anonymous (no auth, IP-quota), cookie session (magic-link email or SIWE wallet — 30-day cookie), and API key (Business plan, machine clients). All paths share the same endpoint surface — the server picks the quota path based on what you sent.

Magic-link (email)

$ curl -X POST https://daoaml.com/api/v1/auth/magic-link \
    -H "Content-Type: application/json" \
    -d '{"email":"you@domain.com"}'
→ 202 Accepted; email contains a one-time token link

$ # Recipient clicks the link →
$ curl https://daoaml.com/api/v1/auth/verify?token=<token>
→ 302 to /me + Set-Cookie: client_session=...; HttpOnly

$ curl -X POST https://daoaml.com/api/v1/auth/logout
→ 204; cookie cleared

Wallet sign-in (EIP-4361 / SIWE)

$ curl -X POST https://daoaml.com/api/v1/auth/wallet/nonce \
    -d '{"address":"0x..."}'
→ { "message": "...EIP-4361 SIWE...", "nonce": "...", "expiration_time": "..." }

# Client signs `message` via personal_sign

$ curl -X POST https://daoaml.com/api/v1/auth/wallet/verify \
    -d '{"message":"...","signature":"0x..."}'
→ 200; Set-Cookie: client_session=...; account upserted by wallet_address

API keys (Business plan)

$ # With an active cookie session:
$ curl -X POST https://daoaml.com/api/v1/me/api-keys \
    -H "Cookie: client_session=..." \
    -d '{"label":"prod-bot"}'
→ { "id": "...", "key": "daoaml_...", "label": "prod-bot" }   ← shown ONCE

$ curl https://daoaml.com/api/v1/check \
    -X POST -H "X-API-Key: daoaml_..." \
    -d '{"address":"0x...","blockchain":"ethereum"}'

The key is returned in plaintext exactly once at creation — store it. GET /me/api-keys lists only metadata; DELETE /me/api-keys/{key_id} revokes immediately, no grace window.

# 03

Address checking

Base URL: https://daoaml.com/api/v1. All endpoints return JSON unless noted.

POST/checkmain verdict endpoint
GET/address/{address}?blockchain=…same data, GET form
GET/check/{check_id}stored snapshot, redacted
POST/address/batchup to 100, single chain
GET/address/{chain}/{addr}/transactionscursor-paginated tx history

POST /check

Score an address. Quota: anonymous 3/mo (IP+browser), Free 3/mo, paid plans burn the included quota first then a pack-credit. Latency p95 ≈ 0.8–2.4s.

Request body
addressrequiredstring1–128 chars. Native format per chain.
blockchainstringSlug, default "tron". ethereum, bitcoin, tron, solana, bsc, polygon, arbitrum, base, ton, … (≈25 supported).
include_detailsboolDefault false. When true, response includes address_info and direct_exposure.
turnstile_token, __hp, submitted_at_ms, focused_at_msanyAnti-bot fields used by the client-app's anonymous flow. Optional for server-side callers.
Example response
{
  "result": {
    "address":         "0xd8dA...",
    "blockchain":      "ethereum",
    "risk_score":      5,
    "risk_level":      "LOW",
    "risk_categories": [{ "slug": "individual", ... }],
    "entities":        [...],
    "address_info":    /* if include_details */,
    "direct_exposure": /* if include_details */,
    "checked_at":      "2026-05-22T11:22:43Z",
    "check_id":        "chk_01HZK4M7Q3X9"
  },
  "quota": { "limit": 3, "used": 1, "resets_at": "..." }
}

GET /address/{address}

Same payload as POST /check, GET-shaped for cache-friendly callers. Chain is passed as ?blockchain=…; defaults to tron.

$ curl "https://daoaml.com/api/v1/address/0xd8dA…?blockchain=ethereum&include_details=true"

GET /check/{check_id}

Fetch a frozen check snapshot. Returns 404 for unknown id, 410 Gone if the owner soft-deleted it via DELETE /me/checks/{check_id}. Read-only, redacted for the public audience (no provider attribution, no internal UUIDs).

POST /address/batch

Up to 100 addresses in one request — single blockchain, same include_details flag. Returns a list of AddressCheckResponse objects with shared checked_at and total.

{
  "addresses":       ["0x...", "0x..."],
  "blockchain":      "ethereum",
  "include_details": false
}

GET /address/{chain}/{addr}/transactions

Per-chain dispatch — identical response shape across 25+ chains. Cursor-paginated.

GET /api/v1/address/{chain}/{addr}/transactions
    ?limit=25&direction=in|out|all&cursor=&token=
# 04

Cabinet (/me)

All /me/* endpoints require an active client_session cookie (magic-link or wallet) or an X-API-Key header bound to the same account.

GET    /api/v1/me                 # current account + plan + quota
GET    /api/v1/me/checks          # personal verdict history
GET    /api/v1/me/reports         # community-reports I submitted
GET    /api/v1/me/defrost         # my Defrost orders
GET    /api/v1/me/billing         # subscription state + pack credits
GET    /api/v1/me/referrals       # referral codes & earnings
DELETE /api/v1/me/checks/{check_id}    # soft-delete (cascades to share-links)
POST   /api/v1/me/link-wallet     # bind a SIWE wallet to the current account

GET    /api/v1/me/api-keys             # list scoped keys (metadata only)
POST   /api/v1/me/api-keys             # create — plaintext key shown ONCE
DELETE /api/v1/me/api-keys/{key_id}    # revoke immediately
# 05

Share-links

Any check snapshot can be exposed as a permalink at daoaml.com/v/<short_id>. Modes: public, password, expiring.

POST   /api/v1/share-links
       body = { "check_id", "mode", "password"?, "expires_at"? }

GET    /api/v1/share-links/{short_id}?p=...
       # 200 active · 401 locked (password) · 410 gone (expired/revoked)

DELETE /api/v1/share-links/{short_id}    # owner revoke
# 06

Community reports

Submit + browse + per-address filter. Verified reports automatically attach to address_labels.

POST  /api/v1/community-reports                # submit a scam report
GET   /api/v1/community-reports                # public list (status=verified)
GET   /api/v1/community-reports?address=...    # per-address filter
GET   /api/v1/community-reports/{report_id}    # single verified report
# 07

Defrost — Source-of-Funds Attestation

PDF report for unfreezing a CEX deposit. Tiers Quick / Standard / Pro (3 / 5 / 7 hops). Pay-per-order — x402 settles $49 USDC on Base.

POST /api/v1/defrost/orders                    # create order
GET  /api/v1/defrost/orders/{order_id}         # owner-side status + tier + audit
GET  /api/v1/defrost/orders/{order_id}/pdf     # signed PDF (owner only)
GET  /api/v1/defrost/verify/{order_no}         # public recipient-side verify (no auth)
# 08

Plans & billing

Crypto-first checkout via NowPayments and Cryptomus (USDT, USDC, BTC, ETH and more). Fiat available via Stripe and YooMoney in supported regions.

GET  /api/v1/plans            # public catalog
POST /api/v1/billing/checkout
     body = { "plan_code", "provider", "currency" }
     # → { "redirect_url": "..." }

Provider IPN webhooks (/api/v1/billing/webhook/{provider}) are intentionally not part of the public surface — they are provider-callable only and excluded from /openapi.json.

# 09

x402 — pay per call, no signup

HTTP 402 Payment Required, finally implemented. Agents (or any non-browser caller) can settle in USDC on Base instead of holding an account — handy for one-off calls from a script or an LLM-driven workflow that doesn't want to manage credentials.

Set Accept: application/x402+json on POST /api/v1/check. Server replies 402 with the price table in x402.accepts[] (one entry per network: base, base-sepolia). Pay the indicated amount of USDC to pay_to, encode the proof {network, tx_hash} as base64, and retry the same request with X-Payment: <proof>. The verdict comes back on that retry.

# 1. Discover the price
$ curl -X POST https://daoaml.com/api/v1/check \
    -H "Accept: application/x402+json" \
    -H "Content-Type: application/json" \
    -d '{"address":"0x...","blockchain":"ethereum"}'
→ 402 { "x402": { "accepts": [{"network":"base","amount":"0.10",
                                "asset":"USDC","pay_to":"0x...",
                                "resource":"/api/v1/check"}, ...] } }

# 2. Pay USDC on Base, base64-encode {"network":"base","tx_hash":"0x..."}

# 3. Retry with proof
$ curl -X POST https://daoaml.com/api/v1/check \
    -H "X-Payment: eyJuZXR3b3JrIjoiYmFzZSIsInR4X2hhc2giOiIweC4uLiJ9" \
    -d '{"address":"0x...","blockchain":"ethereum"}'
→ 200 { "result": { "risk_score": 5, ... } }

Priced endpoints: /api/v1/check ($0.10) and /api/v1/defrost/orders ($49). Browser / curl requests without the agent Accept header keep the normal anonymous-quota / session flow — x402 is opt-in.

# 10

Errors

FastAPI / Starlette error envelope. error is a short slug; message a human hint.

400 invalid_addressstrAddress fails native validation for the given blockchain.
400 unsupported_blockchainstrSlug not in SUPPORTED_BLOCKCHAINS.
401 auth_requiredstrNo session cookie / no X-API-Key on a protected route.
402 payment_requiredx402Agent context on a paid endpoint without an X-Payment proof. Body carries x402.accepts.
403 anti_bot_*strHoneypot / time-on-form / Turnstile failed on anonymous flow.
404 not_foundstrUnknown check_id / order_id / short_id, etc.
410 gonestrResource existed but is soft-deleted / expired (share-links, checks).
429 plan_quota_exceededobjMonthly quota burned and no pack credits left. Body carries plan, limit, used, resets_at.