European insider trading API

One endpoint, four markets. Directors dealings and PDMR notifications from BaFin, the FCA, Finansinspektionen and the AFM, as JSON, with the ISIN, the native currency and a link to the regulator document on every row. No key is needed to read, at 60 requests per hour per IP. Refreshed hourly.

Tracefour reproduces what each regulator published, in the regulator's own units and wording. Not investment advice.

Quick start

curl "https://tracefour.com/v1/eu/se"

Swap se for de, uk or nl. Any other value returns 400. Rate limits and error codes are on the API overview.

The four markets

Every market returns the same 90-day publication window, capped at 2,500 rows, sorted newest publication first, filtered to acquisitions and disposals only. Awards, vestings, withholdings and dividend reinvestments are excluded across all four, which is the one thing they do share.

Path Regulator Currency Minimum value
/v1/eu/deBaFin
Directors Dealings database
EUR on essentially every row.EUR 25,000, applied to EUR rows only.
/v1/eu/ukFinancial Conduct Authority
National Storage Mechanism
GBP mostly, with USD and EUR rows for cross-listed issuers, and null where the unit could not be determined.GBP 25,000, applied to GBP rows only.
/v1/eu/seFinansinspektionen
Insynsregistret
SEK mostly, with EUR, USD and CAD rows for cross-listed issuers.SEK 50,000, applied to SEK rows only.
/v1/eu/nlAutoriteit Financiële Markten
Meldingenregister
Mixed. A live sample of 18 rows carried USD, EUR, GBP and NOK.EUR 25,000, applied to EUR rows only.

Each floor is applied only to rows already denominated in that market's own currency. A cross-listed row quoted in another currency is returned unfiltered, because comparing it against a threshold in the wrong unit would drop real filings and keep noise.

These four endpoints omit meta.sourceLicense on purpose

Every other Tracefour endpoint carries meta.sourceLicense pointing at usa.gov/government-works, because its upstream source is a U.S. federal filing and those are public-domain works. That statement is true of the SEC and it is not known to be true of BaFin, the FCA, Finansinspektionen or the AFM. Four regulators means four sets of publication terms, and none of them has been verified.

So the field is absent here rather than filled with a plausible guess. If your product reads meta.sourceLicense, handle it being undefined on these four paths, and check the regulator's own terms before republishing. meta.license, which covers the Tracefour compilation and nothing else, is present as usual.

Endpoints

One route, GET /v1/eu/{country}, documented once per market because the row shape genuinely differs. A single normalized schema would have to invent values the regulator never published.

GET /v1/eu/de

German Directors Dealings notifications published by BaFin, from the last 90 days, newest publication first.

Buys and sells only. Awards, option exercises, mergers and gifts are dropped by the direction mapper before the response is built, so every row here describes an acquisition or a disposal. transactionNature carries the regulator’s own wording, for example "Buy".

capacity says how the reporting party relates to the issuer, for example "Closely associated", which under Article 19 covers a spouse or a controlled company as well as the manager.

The EUR 25,000 floor applies only to rows denominated in EUR. A cross-listed row in another currency passes through unfiltered rather than being silently compared against a floor in the wrong unit.

Parameter Type Accepts Default
countrystring (path)de, uk, se, or nl. Anything else returns 400.required

curl

curl "https://tracefour.com/v1/eu/de"

Python

import requests

response = requests.get("https://tracefour.com/v1/eu/de", timeout=30)
response.raise_for_status()

for row in response.json()["data"][:5]:
    print(row["transactionDate"], row["issuerName"], row["transactionNature"], row["totalValue"])

JavaScript

const { data, meta } = await fetch("https://tracefour.com/v1/eu/de").then((r) => r.json())
const buys = data.filter((row) => row.transactionNature === "Buy")
console.log(`${meta.count} German filings, ${buys.length} acquisitions`)

Response

Captured from the curl above, trimmed from 71 rows to 1. The meta block is shown in full here so you can see what it does and does not contain: there is a license, and there is deliberately no sourceLicense. Later blocks on this page trim meta to its first three keys.

{
  "data": [
    {
      "filingId": "1b0c10f1665414ab",
      "publicationAt": "2026-07-01T00:00:00Z",
      "issuerName": "Meta Wolf AG",
      "isin": "DE000A254203",
      "pdmrName": "LUBANCO PTE. LTD.",
      "capacity": "Closely associated",
      "transactionNature": "Buy",
      "price": 4.5,
      "currency": "EUR",
      "totalValue": 54948.3,
      "transactionDate": "2026-06-29"
    }
  ],
  "meta": {
    "count": 71,
    "fetchedAt": "2026-09-09T18:35:55.092Z",
    "source": "bafin-germany",
    "attribution": {
      "text": "Underlying filings are public-domain government works. Tracefour compilation licensed CC BY 4.0: when you display it, link to the Tracefour page in meta.attribution.page, or the url on each record. Not investment advice.",
      "site": "https://tracefour.com",
      "page": "https://tracefour.com/de"
    },
    "license": "https://creativecommons.org/licenses/by/4.0/"
  }
}

GET /v1/eu/uk

UK PDMR notifications from the FCA National Storage Mechanism, from the last 90 days, newest publication first.

This is the market where you must handle missing values. UK notifications are free-text RNS announcements, and the price cell frequently arrives as a bare number with no unit. Where the unit can be inferred with confidence it is filled in and currency is set. Where it cannot, the row is kept with currency null, totalValue null and valueUnknown true. In one live sample, 30 of 88 rows were in that state.

Prices are published as the market quotes them, which for a London main-market ordinary share means pence, not pounds. The 201.5 in the sample below is 201.5 pence. Do not multiply a pence price by a share count and label the result GBP.

transactionDate can be null when the announcement did not state one. publicationAt is always present, so sort on that.

Rows carrying valueUnknown are exempt from the GBP 25,000 floor, because there is no value to compare it against. Filter them out yourself if your product needs a value on every row.

Parameter Type Accepts Default
countrystring (path)de, uk, se, or nl. Anything else returns 400.required

curl

curl "https://tracefour.com/v1/eu/uk"

Python

import requests

response = requests.get("https://tracefour.com/v1/eu/uk", timeout=30)
response.raise_for_status()

rows = response.json()["data"]
priced = [row for row in rows if not row.get("valueUnknown")]
print(f"{len(priced)} of {len(rows)} UK rows carry a value")

JavaScript

const { data } = await fetch("https://tracefour.com/v1/eu/uk").then((r) => r.json())
const priced = data.filter((row) => row.valueUnknown !== true)
console.log(`${priced.length} of ${data.length} rows have a currency and a value`)

Response

Captured from the curl above, trimmed from 88 rows to 1. This row is deliberately one of the 30 that could not be priced: note currency null, totalValue null, valueUnknown true, and a pricePerShare of 201.5 that is quoted in pence. meta trimmed to its first three keys.

{
  "data": [
    {
      "filingId": "49fcd780-41a5-42e8-b938-02e1e7ef2c57|1",
      "publicationAt": "2026-07-01T16:12:37Z",
      "issuerName": "CAKE BOX HOLDINGS PLC",
      "pdmrName": "Sukh Chamdal",
      "capacity": "Chief Executive Officer",
      "isin": "GB00BDZWB751",
      "transactionNature": "Purchase of Ordinary Shares",
      "transactionDate": null,
      "volume": 15000,
      "pricePerShare": 201.5,
      "currency": null,
      "totalValue": null,
      "valueUnknown": true,
      "rawArtefactUrl": "https://data.fca.org.uk/artefacts/NSM/RNS/49fcd780-41a5-42e8-b938-02e1e7ef2c57.html"
    }
  ],
  "meta": {
    "count": 88,
    "fetchedAt": "2026-09-09T18:35:55.123Z",
    "source": "fca-nsm"
  }
}

GET /v1/eu/se

Swedish PDMR entries from the Finansinspektionen Insynsregistret, from the last 90 days, newest publication first. The densest of the four markets.

The transaction type arrives in Swedish and is passed through untranslated in characterSe: "Förvärv" is an acquisition and "Avyttring" is a disposal. position is the person’s role, also in Swedish, for example "Styrelseledamot" for a board member. Translating them here would put a Tracefour word where the regulator put its own.

The value field is named totalValueNative to make the unit question explicit: it is denominated in whatever currency the row carries, which is SEK on most rows but EUR, USD or CAD on cross-listed issuers. There is no converted figure, because a conversion needs a rate and a rate date that the regulator did not publish.

Rows whose value is an obvious keying error, such as a misplaced price decimal that puts one trade above the whole market, are dropped before the response is built.

Parameter Type Accepts Default
countrystring (path)de, uk, se, or nl. Anything else returns 400.required

curl

curl "https://tracefour.com/v1/eu/se"

Python

import requests

response = requests.get("https://tracefour.com/v1/eu/se", timeout=30)
response.raise_for_status()

for row in response.json()["data"][:5]:
    direction = "buy" if row["characterSe"] == "Förvärv" else "sell"
    print(row["transactionDate"], row["issuerName"], direction, row["totalValueNative"], row["currency"])

JavaScript

const { data } = await fetch("https://tracefour.com/v1/eu/se").then((r) => r.json())
const sek = data.filter((row) => row.currency === "SEK")
console.log(`${sek.length} of ${data.length} Swedish rows are quoted in SEK`)

Response

Captured from the curl above, trimmed from 409 rows to 1. filingId is a composite of publication time, ISIN, person, date, volume, price and character, because Insynsregistret publishes no stable per-entry identifier. meta trimmed to its first three keys.

{
  "data": [
    {
      "filingId": "2026-07-01T22:46:38Z|SE0006260865|Per Ekstrand|2026-06-30|6600|32.91|Avyttring",
      "publicationAt": "2026-07-01T22:46:38Z",
      "issuerName": "Premium Snacks Nordic AB",
      "pdmrName": "Per Ekstrand",
      "position": "Styrelseledamot",
      "characterSe": "Avyttring",
      "isin": "SE0006260865",
      "transactionDate": "2026-06-30",
      "volume": 6600,
      "price": 32.91,
      "currency": "SEK",
      "totalValueNative": 217205.99999999997
    }
  ],
  "meta": {
    "count": 409,
    "fetchedAt": "2026-09-09T18:35:55.155Z",
    "source": "fi-insynsregistret"
  }
}

GET /v1/eu/nl

Dutch PDMR filings from the AFM Meldingenregister, from the last 90 days, newest publication first. The smallest of the four markets by volume.

transactionCategory arrives in Dutch and is passed through: "Verwerving" is an acquisition and "Vervreemding" is a disposal. stockOptionProgram flags a filing made under an option programme.

Do not assume euros. The AFM publishes in the issuer’s quotation currency, and a live sample of 18 rows carried USD, EUR, GBP and NOK. Always read currency before doing arithmetic on price or totalValue.

isin can be null. The share count is named quantity here, not volume, which is one of the reasons this page documents the four markets separately instead of promising one schema.

Parameter Type Accepts Default
countrystring (path)de, uk, se, or nl. Anything else returns 400.required

curl

curl "https://tracefour.com/v1/eu/nl"

Python

import requests
from collections import Counter

response = requests.get("https://tracefour.com/v1/eu/nl", timeout=30)
response.raise_for_status()

rows = response.json()["data"]
print(Counter(row["currency"] for row in rows))

JavaScript

const { data } = await fetch("https://tracefour.com/v1/eu/nl").then((r) => r.json())
for (const row of data) {
  console.log(row.transactionDate, row.issuerName, row.quantity, row.currency)
}

Response

Captured from the curl above, trimmed from 18 rows to 1. The row shown is quoted in NOK, which is why the euro assumption is called out above. meta trimmed to its first three keys.

{
  "data": [
    {
      "filingId": "202606249E32D89F-5CFC-4D60-950C-65CCAF648EAC|0",
      "publicationAt": "2026-06-24T00:00:00Z",
      "issuerName": "Envipco Holding N.V.",
      "pdmrName": "Garvey G.",
      "capacity": "Chairman of the Board of Directors",
      "isin": null,
      "transactionCategory": "Verwerving",
      "stockOptionProgram": false,
      "price": 41.5,
      "quantity": 200000,
      "currency": "NOK",
      "totalValue": 8300000,
      "transactionDate": "2026-06-24",
      "rawArtefactUrl": "https://www.afm.nl/en/sector/registers/meldingenregisters/transacties-leidinggevenden-mar19-/details?id=202606249E32D89F-5CFC-4D60-950C-65CCAF648EAC"
    }
  ],
  "meta": {
    "count": 18,
    "fetchedAt": "2026-09-09T18:35:55.181Z",
    "source": "afm-netherlands"
  }
}

License and attribution

The Tracefour compilation, meaning the parsing, deduplication, currency inference and normalization applied to these registers, is licensed CC BY 4.0, whose one condition is attribution with a link. That licence covers the compilation only. The underlying notifications belong to the regulators that published them, and their terms are theirs to state. Link to the page named in meta.attribution.page, and to rawArtefactUrl where the row carries one.

Related

Frequently asked questions

Is there a free API for European insider trading data?

Yes. GET https://tracefour.com/v1/eu/{country} returns recent PDMR and directors dealings disclosures as JSON for Germany, the United Kingdom, Sweden and the Netherlands. No key and no sign-up are needed, at 60 requests per hour per IP address, or 600 per hour with a free key. Equivalent European coverage is usually a paid subscription on commercial platforms.

Which regulators does the European endpoint cover?

Four. Germany is BaFin, from its Directors Dealings database. The United Kingdom is the Financial Conduct Authority, from the National Storage Mechanism. Sweden is Finansinspektionen, from Insynsregistret. The Netherlands is the Autoriteit Financiële Markten, from the Meldingenregister. Each row links back to the regulator document it came from.

What is a PDMR filing?

PDMR stands for person discharging managerial responsibilities. Article 19 of the EU Market Abuse Regulation, which the United Kingdom retained after leaving the EU, requires such a person, and anyone closely associated with them such as a spouse or a company they control, to notify the issuer and the regulator of their own dealings in that issuer’s shares. Germany publishes these as Directors Dealings. It is the European counterpart to a U.S. SEC Form 4.

Why do some UK rows have no transaction value?

UK notifications are free-text announcements published through the FCA National Storage Mechanism, and the price is often stated as a bare number with no unit. Where the unit can be inferred with confidence, currency and totalValue are filled in. Where it cannot, the row is returned with currency null, totalValue null and valueUnknown true, rather than a guessed figure. In one live sample, 30 of 88 rows were in that state.

Are UK prices in pounds or pence?

London-quoted ordinary shares are conventionally priced in pence, and the API returns pricePerShare as the announcement stated it. A pricePerShare of 201.5 on a UK row means 201.5 pence, not 201.5 pounds. Read currency before converting, and treat a null currency as a row whose unit is unknown.

Why does the European endpoint omit meta.sourceLicense?

Because the publication terms of BaFin, the FCA, Finansinspektionen and the AFM have not been verified, and stating an unverified licence would be worse than stating none. Every other Tracefour endpoint carries meta.sourceLicense because its upstream source is a public-domain U.S. government work. Here the field is absent by design, not by oversight. meta.license, which covers the Tracefour compilation only, is present as usual.

Data sourced from public regulatory filings. Not investment advice.