Förderkatalog - German Federal Grants & R&D Funding Data
Pricing
$0.20 / 1,000 per grant record returneds
Förderkatalog - German Federal Grants & R&D Funding Data
From $0.20 per 1,000 records, no start fee. Germany's official Förderkatalog register: 301,058 federal grants and research contracts with recipient, executing body, ministry, project title, municipality key, start/end dates and the euro amount. No contact data - the register publishes none.
Pricing
$0.20 / 1,000 per grant record returneds
Rating
0.0
(0)
Developer
Scrapers Delight
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
11 days ago
Last modified
Categories
Share
Förderkatalog — German federal grants & R&D funding data
Germany's Förderkatalog (FöKat) is the federal government's statutory transparency register of grants and research contracts. This Actor exports it as structured rows.
Every number below was counted on 2026-09-16 against the complete register, not sampled and not estimated.
| Measured | |
|---|---|
| Records in the register | 301,058 |
| Records still running today | 40,784 |
| Distinct recipient organisations | 53,128 |
| Distinct executing bodies | 88,584 |
| Fields per record | 26 from the register + 15 derived |
| Emails in the whole register | 0 |
| Phone numbers | 0 |
| Street addresses | 0 |
Read this first: there is no contact data
This is the most important thing to know before you buy a run.
The Förderkatalog publishes who got what money for which project, where — by legal design. It is not a contact directory, and it has no email, phone or street column in its schema at all.
I ran an email regex over all 301,058 records. It matched 11 times. I read all 11 in context:
FDM@HAW.rlp,ODH@Bauhaus.MobilityLab,work4woman@www.de— project acronyms inside a project titlertteleg@leg-bw.deleg— a corrupted organisation-name string
Zero are contact fields. A phone regex matched 4 rows; all four are machine model numbers or an
astronomical object id inside a title (Kienzle 6000/6100/3000, eRASS J084850-420035). A street
regex matched 220 rows; every one names the building being renovated inside a project title.
So this Actor does not emit an email, phone or postal-address field, and the store listing does not claim one. Geography resolves to the municipality and the 8-digit Gemeindekennziffer, never to a street.
What it is good for: an organisation name plus a municipality is an enrichment seed, and the grant record itself is the intelligence — who is funded, by whom, for how much, and when it runs out. Enrichment is a separate build, not this scrape.
What one row is
One row is one Förderkennzeichen (federal grant number). Grant numbers are unique: 301,058 records carry 301,058 distinct numbers, so there is nothing to deduplicate and you are never charged twice for the same grant.
Fields, with fill counted over the whole register
| Field | Fill | |
|---|---|---|
grantNumber | 100.00% | Förderkennzeichen — unique, and the key back to the register page |
registerUrl | 100.00% | Link to this record on foerderportal.bund.de |
ministry | 100.00% | BMFTR / BMWE / BMUKN / BMV / BMLEH |
ministryUnit | 99.99% | Referat inside the ministry |
projectAgency | 100.00% | Projektträger administering the grant (PT-J, PT-DLR, PT-VDI…) |
projectAgencyUnit | 93.00% | Arbeitseinheit inside the agency |
recipientName | 100.00% | Zuwendungsempfänger — see the redaction note below |
recipientIsRedacted | 100.00% | true on the 1.489% where the name is withheld by privacy law |
recipientMunicipality / recipientCity | 99.98% / 99.97% | Official municipality, and the postal place name |
recipientState | 99.79% | Bundesland |
recipientCountry | 99.98% | 300,517 of 301,058 are Deutschland |
recipientMunicipalityKey | 99.98% | 8-digit Gemeindekennziffer — the stable join key |
recipientStateKey / recipientDistrictKey | 99.98% | First 2 and first 5 digits of that key |
executingBody | 100.00% | Ausführende Stelle — the institute actually doing the work |
executingBodyIsRedacted | 100.00% | true on 1.526% |
executing* geography | 99.69–99.98% | Same shape as the recipient side |
projectTitle | 100.00% | Thema des geförderten Vorhabens |
projectName | 51.86% | The short "Projekt" label. The one genuinely sparse column |
programmeCode / programmeName | 100.00% / 99.57% | Leistungsplansystematik and its plain-text name |
fundingProfile | 99.03% | Förderprofil |
fundingType | 100.00% | Förderart |
startDate / endDate | 100.00% | ISO. startDateDe / endDateDe keep the register's DD.MM.YYYY |
durationDays / daysUntilEnd / isRunning | 100.00% | Derived from those dates |
fundingAmountEur | 100.00% | Federal share as a number. 797 records carry a genuine 0 |
fundingAmountEurRaw | 100.00% | The register's own 133.222,00 string, for audit |
A literal "N/A", "-" or "" is not counted as filled anywhere above.
The privacy redaction
German privacy law makes the register withhold the name when the funded party is a natural person. It does that by publishing the sentence "Keine Anzeige aufgrund datenschutzrechtlicher Regelungen." in the name column — so the column reads as 100% filled while some of its values are a notice rather than a name.
Counted: 4,484 of 301,058 records (1.489%) for the recipient, 4,593 (1.526%) for the
executing body. Those grants are otherwise complete — amount, topic, ministry and municipality are
all there — so they are delivered and flagged with recipientIsRedacted /
executingBodyIsRedacted rather than quietly counted as a name.
Filters
Everything the register's own Detailsuche can do, with the measured hit count for each example:
| Input | Example | Records (running projects) |
|---|---|---|
grantNumbers | 13BDC% | 40 |
recipientNames | Fraunhofer% · Helmholtz% · both | 2,178 · 316 · 2,494 |
topicKeywords | Wasserstoff (wrapped to %Wasserstoff%) | 941 |
ministries | BMWE · BMFTR | 10,029 · 21,544 |
states | Bremen · Saarland · both · Thüringen | 583 · 452 · 1,035 · 1,149 |
cities | Bremen | 483 |
entityScope | Bremen as recipient vs as executing body | 583 vs 788 |
startsOnOrAfter | 01.01.2025 · 15.03.2025 | 19,666 · 17,025 |
endsOnOrBefore | 31.12.2026 · 31.12.2027 | 15,507 · 26,972 |
| both dates | starts ≥ 01.01.2025 and ends ≤ 31.12.2027 | 7,450 |
minAmountEur | 10000000 | 370 |
includeCompleted | off vs on | 40,784 vs 301,058 |
Multiple values in one field are OR-ed; different fields are AND-ed. Verified arithmetically, twice: Bremen 583 + Saarland 452 = 1,035 for both, and Fraunhofer% 2,178 + Helmholtz% 316 = 2,494.
The wildcard rule, which will bite you if you turn it off
The register matches these fields whole. Fraunhofer returns nothing; Fraunhofer% returns
2,178 — and the "nothing" arrives as HTTP 200 with a polite German "keine Ergebnisse", which looks
exactly like a real empty result.
autoWildcard is on by default and wraps a plain term as %term%. It matters more than it sounds:
Wasserstoff% (a prefix search) matches 8 running projects, %Wasserstoff% matches 941. Values
picked from a drop-down (ministry, state, funding type) are always sent verbatim and never wrapped.
BMBF no longer exists
The 2025 ministry reorganisation renamed the research ministry. Grants that older sources label
BMBF are published here as BMFTR, and the literal string BMBF returns zero records. The
five ministries in the register today are BMFTR (193,892), BMWE (48,211), BMUKN (34,290), BMV
(16,036) and BMLEH (8,629) — which sum to exactly 301,058.
How it works, and what it refuses to do
The register has a bulk CSV export that is not documented anywhere:
SucheAction.do?actionMode=print&presentationType=csv. It returns the entire result set of the
session's last search in one response — measured: the whole register came back as 190,101,939
bytes, parsed to exactly 301,058 rows.
That makes the flow session-stateful and strictly serial: GET the search mask to be issued a
JSESSIONID → POST the Detailsuche → GET the export on the same cookie jar.
The export is streamed, never buffered. A 40,784-record / 26 MB uncapped run through this code
peaked at 23 MB of heap. A capped run hangs up as soon as your cap is met, so maxRecords=25
really does read about 20 KB of a potentially 190 MB body.
Things that would silently ship a wrong answer, and what stops them
- A body that dies mid-stream. Nothing on this host sends
Content-Length— every response isTransfer-Encoding: chunked— so a truncated body is indistinguishable from a complete one at the HTTP layer. Worse, on the register-wide search the hit count sits at byte 1,540,717 of 1,588,610 (97% of the way in, behind a pager dropdown with 30,111<option>elements), so a truncated page loses the count entirely and reads like "no results". Guards: the page must end in</html>, and the parsed row count must reconcile against the hit count the register itself reported. If it comes up short, the run fails rather than reporting a partial register as complete. - A stalled socket. If the export delivers no bytes for 180s, the run fails with what it had read so far reported as incomplete.
- UTF-8. The site is ISO-8859-15 in both directions. Sending a search as UTF-8 returns a
convincing empty result:
ThüringenasTh%C3%BCringen→ "keine Ergebnisse", asTh%FCringen→ 1,149 records. Reading a response as UTF-8 mangles the two markers that identify an empty result and a rejected input. Both directions are handled explicitly. - A
+for a space.submitAction=Detailsuche+startenmakes the servlet miss its dispatch and return the unfiltered result set — 40,784 instead of Bremen's 583. A wrong answer that looks right. Spaces are always%20. - The Excel armour cliff. Cells are wrapped as
="value"— but only up to 254 characters. Measured: longest wrapped cell 254 chars, shortest bare cell 255. 18,007 cells are over the line. Stripping two leading and one trailing character unconditionally corrupts every one of them. - A session with no search. The export URL answers HTTP 200,
text/plain, 59 bytes of German error text. That is recognised as a transport failure, not an empty register. - A bad date. The mask re-renders itself with an inline German message and HTTP 200 — no result list at all. Dates are validated before anything is sent, and the failure names the field.
Very large exports
Above 60,000 matching records an uncapped run splits the export on the first character of the
Förderkennzeichen, and pulls the chunks concurrently. That partition is lossless because
Förderkennzeichen is 100% filled — verified live: the 36 per-prefix hit counts summed to exactly
40,784, the same number the unpartitioned search reported. A bucket still over the threshold (only
0, which holds 166,411 records) is split again on the second character.
The Actor re-runs that arithmetic every time and refuses to use a split whose children do not sum
to the parent — which is not ceremony: 4 records start with M (a space), so a second-character
split of the M bucket would drop them. That bucket is left whole instead.
Honest limits
- No contact data. Stated at the top, restated here. If you need emails, this register cannot give them to you at any price, and no scraper of it can.
- Geography stops at the municipality. There is no street, no postcode, no coordinate.
projectNameis 51.86% filled and skews modern — older records rarely carry one.- The whole register is slow to pull. Throughput on this host varies a lot: the same 26 MB export took 90s once and 158s another time; the 190 MB whole-register export took 294s on a quiet line and 947s when a second download shared it. A whole-register run is minutes, not seconds.
- I could not test Apify's own egress IPs from the build machine. Measured from a plain
Windows box with no proxy: HTTP 200 everywhere, no CAPTCHA, no user-agent gate, no rate limit
(15 rapid sequential requests, all 200, flat ~1.25s), and the host serves no
robots.txtat all (/robots.txtitself returns 403). The default is Apify's automatic proxy pool. If a whole-register pull fails on a short export, switchproxyConfigurationtoRESIDENTIAL. - A capped run is a sample, not a ranking. Records arrive in the register's own order. Narrow the filters when you want a specific slice.
- The detail page adds nothing. Checked on a real record:
SucheAction.do?actionMode=viewshows less than the CSV export (no Gemeindekennziffer, no Projekt) and carries no contact data either. The Actor does not fetch it;registerUrljust links to it.
Pricing
$0.20 per 1,000 records, no start fee. You are charged once per record delivered to your dataset. Records your filters exclude are never delivered and never charged, and a search that matches nothing costs nothing. If the export comes back short of the count the register reported, the run fails.
Validating this build
node offline_validate.mjs
273 assertions, no network and no node_modules, against bytes captured from the live register on
2026-09-16 in fixtures/ — including the 59-byte error document, the search mask the register
re-renders when it rejects a date, real rows whose cells are over the armour cliff, real rows with a
redacted recipient, and a 163,840-byte prefix of the real 1,588,610-byte register-wide result page,
which is what a body that dies mid-stream actually looks like.
Source
Förderkatalog des Bundes (FöKat), https://foerderportal.bund.de/foekat/, site version 3.5.0.2 — the federal government's statutory transparency register of grants and research contracts.