CRYPTO/OR/NOT

API reference

The classification API

Send a counterparty name or raw payment text from a wire, ACH entry, or card settlement line. Get back a verdict, the registry record it matched, the source behind that record, and a calibrated confidence.
Base URLhttps://www.cryptoornot.com/api

Early access tier · live

  • Live now at the base URL above: health, classify, classify/batch, assessment, calibration, and registry summary.
  • No API key. 60 requests per minute per IP address.
  • It runs the same engine as the self-serve assessment, a TypeScript port held identical to the classifier service by a parity test.
  • Keyed access, higher limits, the monitoring endpoints (/v1/monitor/*, /v1/feed/registry), webhook delivery, and the Unit21 alerts adapter run on the full service, which we set up for design partners. See Full service.

For a key or a design partnership, email contact@cryptoornot.com.

Try it · POST /api/v1/classify

Try a legal name such as FORIS DAX INC, ELECTRIC SOLIDUS INC or RED LEAF CHICAGO LLC, or a decoy such as PAYPAL INC.

Access and limits

No key, 60 requests per minute

The early access tier needs no authentication. Requests are counted per IP address over a sliding 60 second window. Over the limit, the API returns 429 with a Retry-After header. The counter is held in memory on each server instance, so treat the limit as best effort, not a quota.

Every response carries an X-Request-ID header. Send your own and it is echoed back; otherwise one is generated. CORS is open (*), so you can call the API from a browser.

Requests60 per minute per IP address, sliding window
classify text2,000 characters
classify/batch items1,000 per request
assessment file4 MB of UTF-8 CSV, 500,000 data rows; the whole request body under 4.5 MB
API keyNone on this tier

Over-limit response

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0

{"detail":"Rate limit exceeded; retry after the indicated interval."}

Endpoints

Six endpoints on the early access tier

Every example response on this page is real output of this API, captured with curl. Long responses are abbreviated where marked with ....

GET/v1/health

Liveness, plus the versions that decide what a classification means: the registry version and the calibration table version, including whether the calibration is synthetic. Not rate limited.

Response 200

{
  "status": "ok",
  "registry_version": "0.4.1",
  "calibration_version": "0.4.1+2026-10-10",
  "calibration_dataset_kind": "synthetic"
}
POST/v1/classify

Classify one counterparty name or raw payment descriptor. Send the text exactly as your system rendered it: a wire ORIG/BNF block, an ACH descriptor, a card settlement line, or a name. Give the rail if you know it. The rail picks the grammar used to parse the text before matching.

ParameterInRequiredDescription
textbodyyesCounterparty name or raw payment text, up to 2,000 characters.
railbodyno"wire", "ach", "card", or "unknown" (default). Other values are treated as unknown.

Request

curl -X POST "https://www.cryptoornot.com/api/v1/classify" \
  -H "Content-Type: application/json" \
  -d '{"text": "ATM OPS INC", "rail": "ach"}'

Response 200 · crypto

{
  "verdict": "crypto",
  "confidence": 0.978432,
  "score": 100,
  "strategy": "exact",
  "entity_id": "bitstop",
  "entity_name": "Bitstop",
  "categories": [
    "kiosk_operator"
  ],
  "evidence": {
    "observable_type": "legal_name",
    "observable_value": "ATM Ops Inc",
    "observable_confidence": 0.98,
    "source_url": "https://msb.fincen.gov/retrieve.msb.list.php",
    "observed_at": "2026-08-08",
    "method": "FinCEN MSB registrant list, Coral Gables FL, 1473 branches, refreshed 2026-06-02; re-checked 2026-09-19 against the full FinCEN MSB registrant list (OpenSanctions mirror us_fincen_msb): registrant present",
    "review_status": "reviewed"
  },
  "matched_field": {
    "field": "raw_text",
    "source": "raw",
    "text": "ATM OPS INC"
  },
  "calibration": {
    "bin_key": "exact|all",
    "bin_samples": 625,
    "bin_precision": 0.9984,
    "observable_confidence": 0.98,
    "table_version": "0.4.1+2026-10-10",
    "dataset": "datasets/labeled_descriptors.csv",
    "dataset_kind": "synthetic",
    "synthetic": true
  },
  "rail": "ach",
  "grammar": "ach.plain",
  "normalized_input": "ATM OPS INC",
  "note": ""
}

Response 200 · needs_review ({"text": "BLOCK INC", "rail": "wire"})

{
  "verdict": "needs_review",
  "confidence": 0.5990399999999999,
  "score": 100,
  "strategy": "exact",
  "entity_id": "block-cash-app",
  "entity_name": "Cash App (Block)",
  "categories": [
    "broker"
  ],
  "evidence": {
    "observable_type": "legal_name",
    "observable_value": "Block, Inc.",
    "observable_confidence": 0.6,
    "source_url": "https://www.dfs.ny.gov/virtual_currency_businesses",
    "observed_at": "2026-09-12",
    "method": "NYDFS Virtual Currency Businesses regulated-entity list (BitLicense and limited purpose trust charter holder) Confidence capped: the bare word is ambiguous.; source re-checked 2026-09-19: value present verbatim",
    "review_status": "reviewed"
  },
  "matched_field": {
    "field": "raw_text",
    "source": "raw",
    "text": "BLOCK INC"
  },
  "calibration": {
    "bin_key": "exact|all",
    "bin_samples": 625,
    "bin_precision": 0.9984,
    "observable_confidence": 0.6,
    "table_version": "0.4.1+2026-10-10",
    "dataset": "datasets/labeled_descriptors.csv",
    "dataset_kind": "synthetic",
    "synthetic": true
  },
  "rail": "wire",
  "grammar": "wire.untagged",
  "normalized_input": "BLOCK INC",
  "note": "Below the auto-classification threshold (0.92); route to an analyst."
}

Response 200 · no_match ({"text": "HARBORVIEW LOGISTICS INC", "rail": "ach"})

{
  "verdict": "no_match",
  "confidence": 0,
  "score": 0,
  "strategy": null,
  "entity_id": null,
  "entity_name": null,
  "categories": [],
  "evidence": null,
  "matched_field": null,
  "calibration": null,
  "rail": "ach",
  "grammar": "ach.plain",
  "normalized_input": "HARBORVIEW LOGISTICS INC",
  "note": "No registry match. Absence of a match is not evidence the counterparty is unrelated to crypto; the registry does not claim complete coverage."
}
POST/v1/classify/batch

Classify up to 1,000 items in one request. Each item takes the same fields as /v1/classify. The response is an array of classification objects in input order.

ParameterInRequiredDescription
itemsbodyyesArray of {text, rail} objects, at most 1,000.

Request

curl -X POST "https://www.cryptoornot.com/api/v1/classify/batch" \
  -H "Content-Type: application/json" \
  -d '{"items": [
        {"text": "FORIS DAX INC", "rail": "wire"},
        {"text": "PAYPAL INC", "rail": "wire"}
      ]}'

Response 200 · abbreviated

[
  {
    "verdict": "crypto",
    "confidence": 0.968448,
    "score": 100,
    "strategy": "exact",
    "entity_id": "crypto-com-us",
    "entity_name": "Crypto.com (US)",
    "categories": [
      "exchange",
      "broker"
    ],
    "...": "evidence, matched_field, calibration, rail, grammar, normalized_input, note"
  },
  {
    "verdict": "needs_review",
    "confidence": 0.7960999999999999,
    "score": 100,
    "strategy": "fuzzy",
    "entity_id": "paypal-crypto",
    "entity_name": "PayPal and Venmo crypto",
    "categories": [
      "broker",
      "custodian"
    ],
    "...": "evidence, matched_field, calibration, rail, grammar, normalized_input, note"
  }
]
POST/v1/assessment

The Exposure Assessment: a transactions CSV in, a classified ledger and an assessment report out. Send it as multipart form data (the same shape as the full service) or as JSON. Columns are mapped by common aliases, and the mapping used is reported back. Rows that cannot be read become recorded issues, not silent drops. The file is processed in memory and never stored.

ParameterInRequiredDescription
fileformyes*The transactions CSV. UTF-8, at most 4 MB and 500,000 data rows.
railform or bodynoRail for rows without their own channel column. Same values as classify.
csvbody (JSON)yes*The CSV as a string, instead of a file upload.
filenamebody (JSON)noName shown in the report. Default upload.csv.
formatqueryno"json" (default) returns {report, ledger}. "markdown" returns the report document. "csv" returns the classified ledger.

* file for multipart requests, csv for JSON requests.

Request

# Multipart, like the full service
curl -X POST "https://www.cryptoornot.com/api/v1/assessment?format=json" \
  -F "file=@sample_transactions.csv" \
  -F "rail=unknown"

# Or JSON
curl -X POST "https://www.cryptoornot.com/api/v1/assessment?format=markdown" \
  -H "Content-Type: application/json" \
  -d '{"csv": "description,amount\nPAYWARD INC,1200.00\n", "filename": "march.csv", "rail": "wire"}'

Response 200 · format=json, sample file, abbreviated

{
  "report": {
    "title": "Digital asset exposure assessment",
    "source": {
      "filename": "sample_transactions.csv",
      "rows_read": 25,
      "transactions_classified": 25,
      ...
    },
    "registry_version": "0.4.1",
    "registry_entity_count": 127,
    "totals": {
      "crypto": {
        "transactions": 13,
        "amount": 5944770
      },
      "needs_review": {
        "transactions": 7,
        "amount": 17190.5
      },
      "no_match": {
        "transactions": 5,
        "amount": 860.92
      }
    },
    "by_entity": [
      {
        "entity_id": "paxos",
        "entity_name": "Paxos",
        "categories": [
          "stablecoin_issuer",
          "custodian",
          "bank"
        ],
        "transactions": 1,
        "amount": 2400000,
        "max_confidence": 0.9784,
        "min_confidence": 0.9784,
        "verdicts": {
          "crypto": 1
        },
        "sources": [
          "https://paxos.com/legal"
        ]
      },
      ...
    ],
    "...": "by_category, review_queue, calibration, limitations"
  },
  "ledger": [
    {
      "transaction": {
        "row_number": 2,
        "transaction_id": "TXN-1001",
        "posted_at": "2026-07-02",
        "amount": "254000.00",
        "currency": "USD",
        "direction": "debit",
        "rail": "wire",
        "descriptor": "ORIG:PAYWARD VENTURES INC 548 MARKET ST SAN FRANCISCO BNF:FIRST HORIZON CUSTOMER AC-4419820011 REF:88213"
      },
      "classification": {
        "verdict": "crypto",
        "confidence": 0.968448,
        "score": 100,
        "strategy": "exact",
        "entity_id": "kraken",
        "entity_name": "Kraken",
        "categories": [
          "exchange"
        ],
        "evidence": {
          "observable_type": "legal_name",
          "observable_value": "Payward Ventures, Inc.",
          "observable_confidence": 0.97,
          "source_url": "https://www.kraken.com/legal",
          "observed_at": "2026-08-01",
          "method": "public-record review",
          "review_status": "reviewed"
        },
        "matched_field": {
          "field": "originator",
          "source": "parsed",
          "text": "PAYWARD VENTURES INC 548 MARKET ST SAN FRANCISCO"
        },
        "calibration": {
          "bin_key": "exact|parsed|all",
          "bin_samples": 625,
          "bin_precision": 0.9984,
          "observable_confidence": 0.97,
          "table_version": "0.4.1+2026-10-10",
          "dataset": "datasets/labeled_descriptors.csv",
          "dataset_kind": "synthetic",
          "synthetic": true
        },
        "rail": "wire",
        "grammar": "wire.tagged",
        "normalized_input": "ORIG PAYWARD VENTURES INC 548 MARKET ST SAN FRANCISCO BNF FIRST HORIZON CUSTOMER AC 4419820011 REF 88213",
        "note": ""
      }
    },
    ...
  ]
}

Response 200 · format=markdown, first lines

# Digital asset exposure assessment

Source file: `sample_transactions.csv`
Generated: 2026-10-10T06:57:10+00:00
Registry: v0.4.1 (127 entities)
Calibration: v0.4.1+2026-10-10 (synthetic, fitted 2026-10-10)

> The confidence values in this report come from a calibration fitted on synthetic data, not on labeled bank transactions. Read the limitations section before acting on any number here.

## Summary

| Verdict | Transactions | Amount |
| --- | ---: | ---: |
| Crypto counterparty | 13 | 5,944,770.00 |
| Needs review | 7 | 17,190.50 |
| No registry match | 5 | 860.92 |

25 of 25 rows were classified. 0 row-level issues were recorded during ingestion.
...

Response 200 · format=csv, first rows

row_number,transaction_id,posted_at,amount,currency,direction,rail,descriptor,verdict,confidence,entity_id,entity_name,categories,matched_field,matched_text,strategy,raw_score,observable_type,observable_value,source_url,observed_at,review_status,calibration_bin,calibration_samples,note
2,TXN-1001,2026-07-02,254000.00,USD,debit,wire,ORIG:PAYWARD VENTURES INC 548 MARKET ST SAN FRANCISCO BNF:FIRST HORIZON CUSTOMER AC-4419820011 REF:88213,crypto,0.9684,kraken,Kraken,exchange,originator,PAYWARD VENTURES INC 548 MARKET ST SAN FRANCISCO,exact,100.0,legal_name,"Payward Ventures, Inc.",https://www.kraken.com/legal,2026-08-01,reviewed,exact|parsed|all,625,
3,TXN-1002,2026-07-02,1450.00,USD,debit,ach,COINBASE INC DES:PURCHASE ID:9938271004 INDN:JOHN R SMITH CO ID:1455293997 WEB,crypto,0.9884,coinbase,Coinbase,exchange|custodian|broker,company_name,COINBASE INC,exact,100.0,legal_name,"Coinbase, Inc.",https://www.coinbase.com/legal/user_agreement/united_states,2026-08-01,reviewed,exact|parsed|all,625,
...
GET/v1/calibration

The full calibration table behind every confidence: per-bin sample counts and precision, the verdict thresholds and the policy choices behind them, holdout metrics, and the caveat. It is published so the number can be checked.

Response 200 · abbreviated

{
  "version": "0.4.1+2026-10-10",
  "generated_at": "2026-10-10",
  "dataset": "datasets/labeled_descriptors.csv",
  "dataset_kind": "synthetic",
  "synthetic": true,
  "thresholds": {
    "crypto": 0.91586,
    "review": 0.348375,
    "target_precision": 0.9,
    "target_met": true,
    "review_min_yield": 0.3333,
    "f2_optimal_review_reference": 0.1564,
    "note": "target_precision and review_min_yield are policy choices about consequences, not measurements. The thresholds derived from them are the lowest calibrated confidences that satisfy them on the calibration fold. f2_optimal_review_reference is reported for comparison only: it is prevalence-sensitive and this set's prevalence is fabricated."
  },
  "holdout_fold": {
    "rows": 3760,
    "positives": 1848,
    "auto_crypto": 505,
    "auto_crypto_correct": 505,
    "precision_at_crypto": 1,
    "recall_at_crypto": 0.2733,
    "needs_review": 1129,
    "needs_review_correct": 859,
    "review_queue_yield": 0.7609,
    "recall_including_review": 0.7381,
    "missed_entirely": 465
  },
  "caveat": "Fitted on synthetic descriptors generated by scripts/build_dataset.py, at a crypto prevalence chosen by this project rather than observed in a bank file. Precision depends on prevalence, so these confidences are optimistic by an unknown margin and are not a field accuracy claim. Refit on labeled bank data before relying on a confidence for an exposure decision.",
  "...": "bins, calibration_fold, smoothing"
}
GET/v1/registry/summary

What coverage a verdict was measured against: registry version, entity and observable counts, counts by category, observable type and review status, and every entity by id, name and category. Observables and their sources are not published on this tier. The registry page shows the same numbers.

Response 200 · abbreviated

{
  "version": "0.4.1",
  "generated_at": "2026-09-19",
  "entity_count": 127,
  "observable_count": 345,
  "entities_by_category": {
    "bank": 8,
    "broker": 31,
    "custodian": 28,
    "exchange": 21,
    "infrastructure": 15,
    "kiosk_operator": 26,
    "miner": 12,
    "otc_desk": 11,
    "payment_processor": 14,
    "stablecoin_issuer": 11
  },
  "observables_by_type": {
    "alias": 33,
    "dba": 40,
    "legal_name": 272
  },
  "observables_by_review_status": {
    "disputed": 18,
    "reviewed": 322,
    "unreviewed": 5
  },
  "entities": [
    {
      "id": "abra",
      "display_name": "Abra",
      "categories": [
        "broker",
        "otc_desk"
      ]
    },
    {
      "id": "alpaca-crypto",
      "display_name": "Alpaca Crypto",
      "categories": [
        "broker"
      ]
    },
    ...
  ]
}

Errors

Error responses

Errors are JSON with a detail field that says what went wrong. Malformed input always gets a 4xx with a reason.

StatusWhen
400The body is not valid JSON, the multipart body is malformed, or the uploaded file is not UTF-8.
413The body or the uploaded file is over its size limit. Split the file and send the parts separately.
415The assessment request is neither multipart/form-data nor JSON.
422The request fails validation (missing text, more than 1,000 batch items, text over 2,000 characters, an unknown format), or the CSV cannot be ingested: no payment text column, more than 500,000 rows, or a CSV parse error. The detail field says which.
429Over the rate limit. The Retry-After header gives the seconds to wait.
500A fault on our side. The body never echoes your input. Quote the X-Request-ID header when you report it.

Example · 1,001 batch items

HTTP/1.1 422 Unprocessable Entity

{"detail":"items holds 1,001 entries; the limit is 1,000 per request."}

Reading a verdict

What the three verdicts mean

crypto

A registry entity matched with a calibrated confidence above the auto-classify threshold. The response includes the matched observable and its source URL, observed date, method, and review status.

needs_review

The evidence is real but not strong enough to act on without a person. Route it to an analyst with the evidence in the response. The analyst makes the call.

no_match

No registry entity matched. This does not prove the counterparty is unrelated to crypto. The registry does not claim complete coverage, and the response says so.

Every confidence carries one caveat: the calibration table is fitted on synthetic payment descriptors, because no institution has labeled a real set with us yet. Treat confidences as a working prior, not a field accuracy claim. GET /v1/calibration publishes the whole basis.

Full service · design partners

Monitoring, feeds, and delivery

The classifier service (Python, FastAPI) adds the layers a bank needs after the first assessment. It is not publicly hosted. We set it up for each design partner. Paths below are relative to that deployment.

  • Keyed access: every /v1 endpoint requires an X-API-Key header. The default limit is 120 requests per minute per key, configurable per deployment.
  • Monitoring state (watched book, events, webhooks) is scoped to each API key.
  • The same classify, batch, assessment (up to 25 MB), and calibration endpoints, plus an entity list with observable counts at /v1/entities.

Monitoring endpoints

POST/v1/monitor/counterpartiesRegister a counterparty book. Each item is classified now and watched from then on.
GET/v1/monitor/counterpartiesList the watched book.
DELETE/v1/monitor/counterparties/{id}Stop watching one entry.
POST/v1/monitor/transactionsFold observed transactions into the watch state.
POST/v1/monitor/rescanRe-run the whole book against the current registry. A counterparty that was no_match becomes an exposure the day the registry learns its name.
GET/v1/monitor/summaryVerdict counts, exposure count, and the current feed cursor.
GET/v1/monitor/eventsThe event feed from a cursor (since, limit, format). Formats: json, jsonl, csv, unit21, actimize-draft.
GET/v1/monitor/adaptersThe delivery formats, with descriptions.
POST/v1/monitor/webhooksSubscribe an HTTPS endpoint to events, signed with HMAC-SHA256.
GET/v1/monitor/webhooksList subscriptions. Secrets are masked.
DELETE/v1/monitor/webhooks/{id}Remove a subscription.
GET/v1/monitor/webhooks/{id}/deliveriesEvery recorded delivery attempt for a subscription.
GET/v1/feed/registryThe registry changelog: entities and observables added, removed, and updated between versions, each with its provenance.

Event types

  • exposure_detected: A watched counterparty classified as crypto, first time or on a change into crypto.
  • verdict_changed: Any verdict transition, with the before and after states and the registry version behind each.
  • review_required: A counterparty classified needs_review.
  • exposure_cleared: A counterparty that was crypto no longer matches. The coverage caveat travels in the payload.
  • registry_updated: The service saw a new registry version. The payload carries the delta.

Every event that carries a confidence also carries the synthetic-calibration caveat.

Delivery

  • Pull feed. The event id is the cursor. Pass the last id you processed as since. The next cursor is in the X-CryptoOrNot-Next-Cursor header.
  • Webhooks. Each delivery POSTs one event with X-CryptoOrNot-Event-Id and X-CryptoOrNot-Signature, an HMAC-SHA256 of the raw body formatted sha256=<hex>. There is no retry queue yet. Every attempt is recorded, and the pull feed is the recovery path.
  • Unit21. The unit21 format emits one create-alert request body per event for Unit21's POST /v1/alerts/create. It is verified against Unit21's published API reference and pinned by contract tests. A push client handles Unit21 auth, idempotency, retries, and rate limits. It has not yet run against a live Unit21 organization.
  • NICE Actimize. The actimize-draft format is a draft, alert-shaped mapping labeled "mapping_status": "draft". Actimize publishes no ingestion API, so it stays a draft until a design partner supplies the specification.

Verify a webhook signature (Python)

import hashlib, hmac

expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
assert hmac.compare_digest(expected, request.headers["X-CryptoOrNot-Signature"])

Agent tools

The classifier as an MCP server

For AML platforms whose agents triage alerts, the classifier is also a Model Context Protocol server over stdio. The tools call the same library code as the HTTP service, so an agent gets the same classification, evidence, and confidence. Every payload with a confidence carries the synthetic-calibration caveat.

The stdio server is local and single-tenant: the monitoring tools work on one default tenant in a local database, not on the per-key state of a running HTTP service.

classify_descriptor

Classify one payment text or name. Returns the verdict with the full evidence chain, the same result as POST /v1/classify.

classify_batch

Classify up to 1,000 items in one call, in input order.

registry_summary

Registry version, entity and observable counts, entities per category, and observables per review status.

lookup_entity

One registry entity by id or name, with every observable, its provenance, and banking relationships (context only, never matched).

watch_counterparties

Register a counterparty book for monitoring and classify it now.

exposure_events

Read the monitoring event feed from a cursor.

registry_changes

The registry changelog, version by version, with provenance.

monitor_summary

Watch state: watched count, verdict counts, exposure count, feed cursor.

MCP client configuration

{
  "mcpServers": {
    "crypto-or-not": {
      "command": "uv",
      "args": [
        "run", "--directory",
        "/path/to/crypto-or-no-crypto",
        "cryptoornot-mcp"
      ]
    }
  }
}

The server runs from the service repository with uv run cryptoornot-mcp. Access comes with a design partner or early access engagement. Email contact@cryptoornot.com to set it up.