API reference
The classification API
Classify counterparty names and raw payment text from wires, ACH, and card settlement against a provenance-backed registry of crypto businesses. Every verdict carries its evidence chain and a calibrated confidence. This page documents the implemented service, endpoint by endpoint.
Not yet publicly deployed
The API is built and tested but not yet hosted at a public URL. The base URL used in the examples below, https://api.cryptoornot.com, is reserved and pending deployment; requests to it will not resolve today. For early access, a hosted trial, or to run an assessment on your own data, email contact@cryptoornot.com.
Authentication
One header, one key
Every endpoint under /v1 requires an API key, sent in the X-API-Key header. A missing or invalid key returns 401. GET /health is open so that probes and load balancers need no key. Keys are issued per institution when access is granted; there is no self-serve signup yet.
Request header
X-API-Key: YOUR_API_KEYRate limits
120 requests per minute per key
Requests are counted per API key over a sliding 60 second window, with a default limit of 120 per minute. Over the limit, the API returns 429 with a Retry-After header giving the seconds to wait. Limits are configurable per deployment; if the default does not fit your batch volume, ask.
Every response carries an X-Request-ID header. If you send one, it is echoed back; otherwise one is generated. Quote it when reporting an issue and we can trace the exact request in the service logs.
Over-limit response
HTTP/1.1 429 Too Many Requests
Retry-After: 12
{"detail": "Rate limit exceeded; retry after the indicated interval."}Endpoints
Six endpoints, one job
/healthNo authService liveness plus the versions that determine what a classification means: the registry version and the calibration table version, including whether the calibration is synthetic.
Response 200
{
"status": "ok",
"registry_version": "0.2.0",
"calibration_version": "0.1.0",
"calibration_dataset_kind": "synthetic"
}/v1/entitiesThe registry, summarized: every entity with its id, display name, categories, and observable count. Use it to see what coverage a verdict was measured against.
Response 200
[
{
"id": "abra",
"display_name": "Abra",
"categories": ["broker", "otc_desk"],
"observable_count": 7
},
...
]/v1/calibrationThe full calibration table behind every confidence the API reports: per-bin sample counts and precision, the verdict thresholds and the policy choices they derive from, holdout metrics, and the caveats. Nothing about how a confidence is produced is hidden; this endpoint exists so the number can be audited rather than trusted.
/v1/classifyClassify 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 just a name, and the rail if you know it: the rail selects the descriptor grammar used to parse the text before matching.
| Parameter | In | Required | Description |
|---|---|---|---|
| text | body | yes | Counterparty name or raw payment text. |
| rail | body | no | One of "wire", "ach", "card", or "unknown" (default). Selects the descriptor grammar. |
Request
curl -X POST "https://api.cryptoornot.com/v1/classify" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Payward, Inc.", "rail": "wire"}'Response 200 · crypto verdict
{
"verdict": "crypto",
"confidence": 0.978334,
"score": 100.0,
"strategy": "exact",
"entity_id": "kraken",
"entity_name": "Kraken",
"categories": ["exchange"],
"evidence": {
"observable_type": "legal_name",
"observable_value": "Payward, Inc.",
"observable_confidence": 0.98,
"source_url": "https://www.kraken.com/legal",
"observed_at": "2026-08-01",
"method": "public-record review",
"review_status": "reviewed"
},
"matched_field": {
"field": "raw_text",
"source": "raw",
"text": "PAYWARD, INC."
},
"calibration": {
"bin_key": "exact|all",
"bin_samples": 575,
"bin_precision": 0.9983,
"observable_confidence": 0.98,
"table_version": "0.1.0",
"dataset": "datasets/labeled_descriptors.csv",
"dataset_kind": "synthetic",
"synthetic": true
},
"rail": "wire",
"grammar": "wire.untagged",
"normalized_input": "PAYWARD INC",
"note": ""
}Response 200 · no_match verdict
{
"verdict": "no_match",
"confidence": 0.0,
"score": 0.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."
}/v1/classify/batchClassify 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, each with its full evidence chain.
| Parameter | In | Required | Description |
|---|---|---|---|
| items | body | yes | Array of {text, rail} objects, at most 1,000 per request. |
Request
curl -X POST "https://api.cryptoornot.com/v1/classify/batch" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{"text": "Coinbase, Inc.", "rail": "wire"},
{"text": "HARBORVIEW LOGISTICS INC", "rail": "ach"}
]
}'/v1/assessmentThe Batch Exposure Assessment: a CSV of transactions in, a classified ledger and an assessment report out. Upload is multipart form data, UTF-8 CSV, at most 25 MB and 500,000 data rows. Columns are mapped by common aliases, so the usual core banking export shapes work without renaming; the mapping used is reported back in the report. Rows that cannot be read become recorded issues rather than silent drops.
| Parameter | In | Required | Description |
|---|---|---|---|
| file | form | yes | The transactions CSV. UTF-8, at most 25 MB and 500,000 data rows. |
| rail | form | no | Default rail for rows without their own channel column. Same values as classify. |
| format | query | no | "json" (default) returns the report and ledger; "markdown" returns the report document; "csv" returns the classified ledger. |
Request
curl -X POST "https://api.cryptoornot.com/v1/assessment?format=json" \
-H "X-API-Key: YOUR_API_KEY" \
-F "file=@transactions.csv" \
-F "rail=unknown"Response 200 · format=json, abbreviated
{
"report": {
"source": { "transactions_classified": 2, ... },
"totals": { "crypto": { "transactions": 1, ... }, ... },
...
},
"ledger": [
{
"transaction_id": "T1",
"classification": {
"verdict": "crypto",
"entity_name": "Coinbase",
"evidence": { "source_url": "...", ... },
...
}
},
...
]
}Errors
Error responses
Errors are JSON with a detail field describing what went wrong. Malformed input is always a 4xx with a reason, never a 500.
| Status | When |
|---|---|
| 400 | The uploaded assessment file is not UTF-8 encoded CSV. |
| 401 | The X-API-Key header is missing or does not match a configured key. Applies to every /v1 endpoint when the deployment has keys configured. |
| 413 | The assessment upload exceeds 25 MB. Split the file and submit the parts separately. |
| 422 | The request body fails validation, or the assessment CSV cannot be ingested: no recognizable payment text column, more than 500,000 data rows, or a CSV parse error. The detail field says which. |
| 429 | The caller is over its rate limit. The response carries a Retry-After header with the number of seconds to wait. |
Reading a verdict
What the three verdicts mean
crypto
A registry entity matched with calibrated confidence above the acting threshold. The response always includes the matched observable and its provenance: source URL, observed date, method, and review status.
needs_review
The evidence is real but not strong enough to act on unreviewed. This is part of the method, not a defect in it: route these to an analyst with the evidence chain in the response.
no_match
No registry entity matched. This is not proof the counterparty is unrelated to crypto; the registry does not claim complete coverage, and the response says so.
One caveat travels with every confidence the API reports: the current calibration table is fitted on synthetic payment descriptors, because no institution has labeled a real set with us yet. Confidences are a working prior, not a field accuracy claim, and GET /v1/calibration publishes the entire basis. Correcting verdicts on real records is what a design partnership is.
Agent tools
The classifier as an MCP server
For AML platforms whose agents triage alerts, the classifier is also exposed as a Model Context Protocol server over stdio. The tools wrap the same library code the HTTP API serves, so an agent gets the identical classification, evidence chain, and calibrated confidence without running the HTTP service. Every confidence-bearing payload carries the synthetic-calibration caveat, the same disclosure the API and the assessment report make.
classify_descriptor
Classify one counterparty name or payment descriptor. Returns the verdict with the full evidence chain: matched observable, provenance, descriptor field, and calibration bin. Same result as POST /v1/classify.
classify_batch
Classify up to 1,000 descriptors in one call, results in input order, each with its evidence chain. Same cap as the HTTP batch endpoint.
registry_summary
Registry version, generation date, entity and observable counts, entities per category, and the review-status breakdown of observables. Tells an agent what coverage a no_match verdict was measured against.
lookup_entity
Fetch one registry entity by id or by name, with every observable and its provenance, plus banking relationships, which are analyst context and never classification signals.
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 to the repository is part of a design partner or early access engagement; email contact@cryptoornot.com to set it up.