Crypto Tax CSV Validator
Before you import a year of transactions into a tax tool, check the CSV for the errors that quietly break it — malformed rows, duplicate or missing transaction ids, timezone-ambiguous timestamps, impossible amounts, unknown transaction types and balances that will not reconcile. Everything runs on your device. This is a data-quality check, not tax advice.
Your file is validated in your browser. It is never uploaded, stored or logged. Nothing happens until you pick a file.
Your file never leaves your device
- Your CSV is read and checked entirely in your browser. It is never uploaded to a server, never stored, and never logged.
- We do not send your filename, rows, amounts, wallet addresses or transaction details to anyone — not to us, not to analytics, not to a third party.
- Analytics records only that a validation happened and coarse buckets (roughly how many rows and issues), never a single value from your file.
- The normalised CSV is generated on your device and downloaded straight to your computer. Your original file is kept locally and left untouched until you choose to download.
This is a data-quality tool, not tax software. It does not calculate gains, losses, cost basis or any tax figure, and it is not tax, accounting or legal advice.
A clean result means the file is internally consistent and well-formed — it does not mean your records are complete or correct, and it is not a substitute for a qualified tax professional.
We do not claim compatibility with any specific tax software. We validate a documented canonical schema plus a few exchange exports we ship a sample for; always check the output against your own provider and filing tool.
A clean result is not a guarantee that Koinly, CoinTracker, CoinLedger or any other tool will accept or correctly import your file. Each product has its own template, row limits and rules; always check the output against your own provider.
How it works
- Parse in the browser. Your file is parsed with a strict RFC-4180 reader that handles quoted commas, quoted line breaks, escaped quotes and a UTF-8 byte-order mark. An unterminated quote or an unreadable file is reported rather than silently mangled.
- Detect the format. We match the header against a documented canonical schema and a small set of exchange exports we ship a verified sample for. An unrecognised header is reported as unrecognised — we never guess what a column means.
- Check every row. Each row is checked for a missing or duplicate transaction id, exact-duplicate rows, non-ISO or timezone-less timestamps, impossible amounts and fees, a missing asset, an unknown transaction type, inconsistent buy/sell/trade fields, and spreadsheet-formula injection.
- Reconcile the totals. We replay the file per asset to find balances that cannot be reconciled — where more of an asset is disposed of than was ever acquired — and flag probable internal transfers between your own accounts that could otherwise be double-counted.
- Explain and normalise. You get a transparent report with a severity, the exact row numbers, a plain-language explanation and a suggested correction. When you choose to, you can download a normalised canonical CSV with formula-injection cells neutralised — your original data is preserved.
What we check
Exactly the data-quality problems the browser tool tests for — nothing more is claimed. Duplicate detection is exact-match only; it does not do fuzzy, hash-based or API-vs-CSV overlap matching.
- Parses as real CSV. A strict RFC-4180 read: quoted commas and line breaks, escaped quotes and a UTF-8 byte-order mark are handled; an unterminated quote or an empty file is reported, not silently mangled.
- Recognises the format. The header is matched against a documented canonical schema and a small set of exchange exports we ship a verified sample for. An unrecognised header is reported as unrecognised — we never guess a column's meaning.
- Flags exact-duplicate rows and ids. A transaction id that repeats, or a row that is byte-for-byte identical to another, is flagged as a likely double-count. This is exact-match detection only — it will not catch two records of one event that differ in rounding, timestamp or source.
- Checks timestamps for a timezone. A non-ISO timestamp is flagged as unreadable; an ISO timestamp with no timezone is read as UTC and flagged as ambiguous, because near a year boundary the assumed zone can move a transaction into a different tax year.
- Rejects impossible amounts. A blank, non-numeric or infinite amount, a zero or negative quantity (direction is carried by the type, not a sign), a negative fee, a missing asset, or a fee with no fee currency.
- Checks type and trade consistency. A transaction type outside the canonical set is flagged; a buy or sell missing its price and quote currency, or a crypto-to-crypto trade missing its counter asset and amount, is flagged as inconsistent.
- Neutralises spreadsheet-formula injection. A cell that a spreadsheet could execute as a formula (leading =, +, -, @ or tab) is flagged, and neutralised with a leading apostrophe in the normalised download.
- Reconciles balances and spots transfers. The file is replayed per asset to find balances that cannot be reconciled — more of an asset disposed of than was ever acquired — and to flag probable internal transfers between your own accounts that could otherwise be double-counted.
Why these checks matter — from the official docs
- Duplicates can survive automatic detection. Koinly documents that its duplicate detector only catches sufficiently exact matches and can miss duplicates when you mix API and CSV imports, import trade and order files covering the same activity, or have different rounding or timestamps — so validating a single, authoritative export before import matters. Koinly: How duplicate detection works in Koinly (accessed 2026-07-18).
- A file with no timezone is ambiguous. Koinly documents that if a CSV does not identify its timezone it is assumed to be UTC, and that you must choose the source timezone during import — which is why we flag timezone-less timestamps rather than guess. Koinly: CSV import timestamps are not in UTC timezone (accessed 2026-07-18).
Seeing a “missing purchase history”, duplicate or timezone warning after import? Read the missing-cost-basis troubleshooting guide →
What we support
A documented canonical schema first, plus exchange exports whose columns we verify against an included sample. We do not claim universal tax-software compatibility.
- Crypto Market Lens canonical schema. Matched every required canonical column by name.
- Coinbase transaction history. Matched the Coinbase transaction-history column signature (see the included coinbase sample). Coinbase changes this format over time — check your export against the sample.
- Kraken ledgers export. Matched the Kraken ledgers column signature (see the included kraken sample). Kraken's signed amount is folded into the transaction type; the canonical amount is the absolute value.
Canonical columns
| Column | Required | Meaning |
|---|---|---|
| transaction_id | required | Unique id for the row. Must be present and unique across the file. |
| timestamp | required | ISO-8601 datetime WITH a timezone, e.g. 2026-01-15T14:30:00Z or 2026-01-15T09:30:00-05:00. |
| type | required | One of the canonical transaction types (buy, sell, trade, deposit, withdrawal, transfer_in, transfer_out, reward, income, fee, staking, airdrop). |
| asset | required | Ticker of the primary asset moved, e.g. BTC. |
| amount | required | Positive quantity of `asset`. Direction comes from `type`, not a sign. |
| price | optional | Unit price of `asset` in `quote_currency` at the time of a buy/sell. |
| quote_currency | optional | Currency the price is denominated in, e.g. USD. |
| fee | optional | Positive fee amount, if any. |
| fee_currency | optional | Ticker the fee was paid in. |
| counter_asset | optional | For a crypto-to-crypto trade, the asset received. |
| counter_amount | optional | For a trade, the positive quantity of counter_asset. |
| account | optional | Wallet or account label — used to pair internal transfers. |
| notes | optional | Free-text note. Never used in reconciliation. |
Canonical transaction types: buy, sell, trade, deposit, withdrawal, transfer_in, transfer_out, reward, income, fee, staking, airdrop. Amounts are positive quantities — direction comes from the type, never a negative sign. Timestamps are ISO-8601 with an explicit timezone.
Crypto Market Lens is free. We may one day show a clearly-labelled link to a relevant, independently-approved tax tool; if we do, it will be disclosed and will never change what this validator reports. Right now this surface shows no offer at all — that is by design. Affiliate disclosure & methodology →
Questions people ask
Does this file my taxes or calculate what I owe?
No. It does not calculate gains, losses or any tax figure, and it is not tax advice. It only checks whether your transaction CSV is well-formed and internally consistent, so an import into your own tax tool does not fail or silently mis-count.
Is my file uploaded anywhere?
No. The file is read and validated entirely in your browser using a local file reader. It is never uploaded, stored or logged. We record only that a validation happened and coarse issue buckets — never a value, filename or address from your file.
Which formats do you support?
A documented canonical schema first, plus a small set of exchange exports we ship a verified sample for. We do not claim universal tax-software compatibility. If your header is not recognised, download the canonical template, map your columns onto it, and re-run.
What does a clean result actually mean?
That the file parses, every row is well-formed, and the totals reconcile. It does not mean your records are complete or your taxes are correct — missing transactions cannot be detected from a file that does not contain them. Treat it as a data check, not a clearance.
Why does it warn about timestamps without a timezone?
A timestamp with no timezone is ambiguous — the same wall-clock time is a different moment in different zones, and near a year boundary that can move a transaction into a different tax year. We read such times as UTC and flag them so you can add an explicit timezone.
What is CSV formula injection and why flag it?
A cell starting with =, +, - or @ can be executed as a formula when the file is opened in a spreadsheet, which is a known attack vector. We flag any such cell and, in the normalised download, neutralise it with a leading apostrophe so it is treated as text.