DAC8 Live: Crypto-Asset Tax Reporting From 1 January 2026 in Code

DAC8’s first crypto-asset reports are not due until 2027 — which is exactly why a lot of service providers will start building the reporting engine about a year too late.

The filing deadline is the wrong thing to plan around. Under Council Directive (EU) 2023/2226 — DAC8, adopted on 17 October 2023 — the rules apply from 1 January 2026, with Member States required to transpose by 31 December 2025. The first fiscal year covered is 2026, and competent authorities exchange the data within nine months of year-end, so the inter-authority exchange happens by 30 September 2027. What that timeline hides is the obligation that actually bites first: from 1 January 2026 you have to be collecting and structuring the data. The report you file in 2027 is only as good as the records you started keeping eighteen months earlier, and clean data is far cheaper to capture at source than to reconstruct from a year of production transactions.

What DAC8 actually turns on

DAC8 inserts Article 8ad and Annex VI into the Directive on Administrative Cooperation (2011/16/EU). Annex VI is, in substance, the OECD Crypto-Asset Reporting Framework rendered into EU law. The population it captures is broad: crypto-asset service providers authorised under MiCA, and any other crypto-asset operator that provides services to reportable EU users. Size is irrelevant, and so, largely, is geography — a non-EU operator with EU reportable users is in scope. If you have read our note on what MiCA demands of crypto-asset businesses in Cyprus, this is the tax-transparency layer that sits on top of the same authorisation.

Free · 4 minutes

When two of your systems disagree, do you know which one to believe?

Fourteen questions on ownership, lineage, and quality — the difference between a number on a dashboard and a number you could defend. Banded finding on screen, full sheet by email.

The reporting object is a per-user, per-asset annual aggregate. For each reportable user — with a look-through to controlling persons for entities — and for each type of reportable crypto-asset, you report the acquisitions and disposals against fiat, the crypto-to-crypto exchanges valued at fair market value on both legs, transfers including those to and from unhosted wallets, and reportable retail payment transactions above USD 50,000. This is not a transaction log. It is a set of annual totals that have to reconcile, per user and per asset, against a year of underlying activity. That reconciliation is the engineering problem.

The CARF schema is the real specification

The legal text tells you what to report. The OECD’s CARF XML schema — published in October 2024 and updated in July 2025 — tells you how, and it is the specification your engine must actually satisfy. It defines a message header (transmitting and receiving country, message type, a message reference identifier, the reporting period, a timestamp), a body of reportable users, and the transaction buckets each user’s activity has to be sorted into. Every record carries a DocSpec with a DocTypeIndic: OECD1 for new data, OECD2 for a correction, OECD3 for a deletion. Get the reference identifiers and the correction codes wrong and you cannot amend a filing without resubmitting the lot.

Treating the schema as the specification, rather than the prose, is what separates a reporting engine that works from one that passes a demo. The buckets are fixed; your ledger’s internal categories are not the same as CARF’s, and the mapping between them is where the defensible determinations live — which transfers count, which assets are reportable, how a crypto-to-crypto swap is valued. Build that mapping as code you can test, not a spreadsheet a person maintains.

Generating and validating the report

The shape of a generator is straightforward: build the tree, populate the header, add one reportable-user block per user with their aggregated transactions, then validate against the official XSD before anything leaves the building. The discipline that matters is the last step — fail closed. A report the schema rejects should never reach the tax authority’s portal, because a rejected filing is a missed deadline dressed up as a submission.

"""Generate a DAC8 / CARF crypto-asset report and validate it before filing.

Aligned to the OECD Crypto-Asset Reporting Framework XML Schema (published
October 2024, updated July 2025), which Annex VI of Directive (EU) 2023/2226
adopts into EU law. The namespace, version and shared-types URI below are
illustrative: always validate against the exact XSD your tax authority
publishes before every submission.
"""
from lxml import etree

CRF_NS = "urn:oecd:ties:crf:v1"          # must match the published schema
NSMAP = {None: CRF_NS}


def q(tag):
    return f"{{{CRF_NS}}}{tag}"


def build_report(msg_ref_id, reporting_period, user, transactions):
    root = etree.Element(q("CRF_OECD"), nsmap=NSMAP, version="1.0")

    # --- Message header ---
    header = etree.SubElement(root, q("MessageSpec"))
    etree.SubElement(header, q("TransmittingCountry")).text = "CY"
    etree.SubElement(header, q("ReceivingCountry")).text = "CY"
    etree.SubElement(header, q("MessageType")).text = "CARF"
    etree.SubElement(header, q("MessageRefId")).text = msg_ref_id
    etree.SubElement(header, q("ReportingPeriod")).text = reporting_period  # yyyy-12-31
    etree.SubElement(header, q("Timestamp")).text = "2027-05-31T09:00:00"

    body = etree.SubElement(root, q("CryptoAssetReport"))

    # --- One reportable user; DocTypeIndic OECD1 = new data ---
    ru = etree.SubElement(body, q("ReportableUser"))
    doc = etree.SubElement(ru, q("DocSpec"))
    etree.SubElement(doc, q("DocTypeIndic")).text = "OECD1"
    etree.SubElement(doc, q("DocRefId")).text = user["doc_ref_id"]

    person = etree.SubElement(ru, q("Individual"))
    etree.SubElement(person, q("ResCountryCode")).text = user["res_country"]
    tin = etree.SubElement(person, q("TIN"), issuedBy=user["res_country"])
    tin.text = user["tin"]

    # --- Annual aggregates, one block per reportable crypto-asset ---
    for tx in transactions:
        rt = etree.SubElement(ru, q("RelevantTransactions"))
        etree.SubElement(rt, q("AssetType")).text = tx["asset"]
        for bucket in ("CryptoToFiatIn", "CryptoToFiatOut",
                       "CryptoToCryptoIn", "CryptoToCryptoOut",
                       "CryptoTransferIn", "CryptoTransferOut"):
            leg = tx.get(bucket)
            if leg is None:
                continue
            el = etree.SubElement(rt, q(bucket))
            etree.SubElement(el, q("NumberOfUnits")).text = str(leg["units"])
            fmv = etree.SubElement(el, q("FMV"), currCode="EUR")
            fmv.text = f"{leg['value']:.2f}"
    return root


def validate(root, xsd_path):
    schema = etree.XMLSchema(etree.parse(xsd_path))
    if not schema.validate(root):
        # Fail closed: never file a report the schema rejects.
        raise ValueError(str(schema.error_log))
    return etree.tostring(root, pretty_print=True,
                          xml_declaration=True, encoding="UTF-8")

Validation against the published XSD is necessary but not sufficient. A file can be schema-valid and still wrong: a missing tax identification number, a residence code that does not match the self-certification, aggregates that do not tie back to the ledger. Build a business-rules layer above schema validation — TIN presence and format, reconciliation of reported totals against source transactions, look-through completeness — and run it before you generate the XML, not after a portal rejects it.

Where the retrofit pain lives

The reason to stand this up now, in 2026, is that the expensive failures are all data-lineage failures, and they compound with time. If you cannot trace a reported aggregate back to the transactions that produced it, you cannot defend the figure, correct it cleanly, or answer a supervisor’s question about it. This is the same problem in a different regime as the one firms face reconciling DAC8, CARF and CRS 2.0 in a single reporting engine — the reporting layer is cheap; the lineage underneath it is what you are really building.

Correction handling is where a year of neglect becomes visible. DAC8 filings will need amending — a late self-certification, a reclassified asset, a user whose residence changed. If your DocRefId scheme is not stable and unique from the first filing, an OECD2 correction cannot reliably point at the record it replaces, and you are left resubmitting whole reports and hoping the authority’s system reconciles them. Design the reference-identifier scheme before the first report, not after the first correction. The data you must capture for first reporting is fixed now; the cost of capturing it badly is not.

One national caveat worth stating plainly: the 30 September 2027 date is the deadline for authorities to exchange with each other. The date on which you file to your own tax authority is set nationally by the transposing law, and is earlier. Confirm your filing deadline against your Member State’s implementation rather than the directive-level exchange date — in Cyprus, that means the Tax Department, not CySEC, and a filing window that closes well before the September exchange.

The organisations that struggle with DAC8 will not be the ones that misread Annex VI. They will be the ones that read it perfectly in 2026, filed a schema-valid report in 2027, and then could not explain, correct or defend a single figure in it.

Most technology problems are not technology problems. They are control problems.

The systems exist. The investment has been made. The question is whether leadership can understand, direct, evidence, and sustain what those systems produce. Find out where control exists — and where it only appears to.

Full Governance by Sixteen Pillars

Govern your business. Prove your compliance.

A board assurance cockpit for EU-regulated financial firms — tamper-evident, hash-chained proof of governance across DORA, GDPR, NIS2, ISO 27001, the EU AI Act and MiCA. In development.

See what's coming