Plus AML Partner sign in

API reference

Base URL https://aml.progonka.com. Authenticate with Authorization: Bearer <key>. Requests and responses are JSON.

Where to start

Decide which of the two questions you are asking. These are not two quality tiers — they are different questions with different costs.

CallAnswersLatencyPrice
POST /api/v1/screen Is this address itself on a list? <50 ms, inline 0.02 USD
POST /api/v1/checks Where did the money come from (up to 3 hops)? 202, then webhook 0.30 USD

Instant screen

POST /api/v1/screen

curl -X POST https://aml.progonka.com/api/v1/screen \
  -H "Authorization: Bearer $PLUS_AML_KEY" \
  -H "Content-Type: application/json" \
  -d '{"address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}'

One indexed lookup, no node traffic. It answers only about the address itself and knows nothing about its counterparties. This is the call to make when a payment is waiting on the answer.

Deep check

POST /api/v1/checks

curl -X POST https://aml.progonka.com/api/v1/checks \
  -H "Authorization: Bearer $PLUS_AML_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "address",
    "target": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
    "direction": "inbound",
    "depth": 3,
    "external_ref": "order-1841",
    "webhook_url": "https://your.host/hooks/aml"
  }'
kind
address — screen a wallet. transaction — put a transaction id in target; the trace then runs from the counterparty: the sender for inbound, the recipient for outbound.
direction
inbound — where the money came from. The question when accepting a payment.
outbound — where it is going. The question before sending one.
both — both, with the traversal budget split between them.
depth
1–3. Depth affects how long the walk takes, not what it costs.
webhook_url
Where to deliver the result. Without it, poll GET /api/v1/checks/{id}.

The 202 comes back immediately:

{
  "id": "9f1c…",
  "status": "pending",
  "preliminary": {
    "risk_score": 0,
    "risk_level": "CLEAN",
    "verdict": "PASS",
    "basis": "list match only; the graph walk has not run yet"
  }
}

For a sanctioned address the preliminary verdict is already the final one — the walk is not run at all in that case.

The result

GET /api/v1/checks/{id}

{
  "id": "9f1c…",
  "status": "done",
  "risk_score": 63,
  "risk_level": "MEDIUM",
  "verdict": "REVIEW",
  "findings": [
    {
      "address": "TXyz…",
      "category": "frozen_by_issuer",
      "hop": 2,
      "share_of_volume": 0.184,
      "amount_usdt": 18400.0,
      "contribution": 51.0,
      "path": ["TSubject…", "TMid…", "TXyz…"]
    }
  ],
  "coverage": {
    "complete": false,
    "note": "TBA6Cy… is a hub, funds commingled — branch stopped",
    "nodes_expanded": 47,
    "list_version": "345d06…",
    "engine": { "depth": 3, "min_branch_share": 0.005, "…": "…" }
  }
}

How to read it

risk_score
0–100, and objective: each finding contributes its category weight times its share of volume times the decay for its hop.
verdict
PASS / REVIEW / FAIL — this is your policy applied to the score. The thresholds are set per account (currently 50 and 75).
share_of_volume
The fraction of traced volume that reached this address. It is what stops a single speck of dust from a scammer condemning a wallet.
coverage.complete
false means a limit cut the walk short, and note says which. A low score with complete: false must not be read as "clean".
coverage.list_version
The snapshot of the lists the verdict was decided on, for reproducibility.

Monitoring

POST /api/v1/monitors

curl -X POST https://aml.progonka.com/api/v1/monitors \
  -H "Authorization: Bearer $PLUS_AML_KEY" \
  -d '{"address": "T…", "label": "hot wallet", "webhook_url": "https://your.host/hooks/aml"}'

The address is polled every 15 minutes. When a listed counterparty turns up among its new transfers, a monitor.alert is delivered. Billed at 1.50 USD per address per month, charged on subscribing and on renewal.

GET /api/v1/monitors lists them, GET /api/v1/monitors/{id}/alerts returns alerts, DELETE /api/v1/monitors/{id} unsubscribes.

Webhooks

A JSON POST to your URL, retried three times with growing backoff.

{ "event": "check.completed", "check": { … } }
{ "event": "monitor.alert", "monitor_id": "…", "address": "T…", "alert": { … } }

Errors

CodeWhen
400 / 422Malformed address or transaction id. Address checksums are verified, so a typo is rejected rather than screened as if it were a real address.
401Key missing, revoked or wrong.
402Balance does not cover the call. The body carries your balance and the price.
404No such check or monitor, or it belongs to another partner.
429Rate limited.

What the service does not do