A PHP REST API that enriches OpenParliamentTV platform data from Wikidata, Wikipedia, Wikimedia Commons, Abgeordnetenwatch, and the German parliament document database (DIP).
- PHP 8.1+
- No Composer required — plain
require_onceonly
cp config.sample.php config.php
# Edit config.php and fill in your API keys| Key | Description |
|---|---|
$config["accessNeedsKey"] |
Set to true to require API key auth |
$config["keys"] |
Map of API keys (only needed when access control is on) |
$config["optvAPI"] |
OpenParliamentTV platform API base URL — used to resolve OPTV internal document IDs to DIP IDs |
$config["dip-key"] |
DIP Bundestag API key — apply at dip.bundestag.de |
$config["thumb"]["defaultWidth"] |
Default thumbnail width in pixels (default: 300) |
$config["thumb"]["defaultLanguage"] |
Default language code (default: de) |
$config["cache"]["enabled"] |
Set to true to enable SQLite response caching |
$config["cache"]["path"] |
Path to the SQLite cache file (created automatically) |
$config["cache"]["bypassParam"] |
Request param to force a fresh fetch (default: nocache) |
$config["cache"]["ttl"][type] |
TTL per type in seconds; 0 = never expires |
Drop the repository root into your web root. index.php is the entry point and must remain at the root.
On Apache, cache/.htaccess (committed to the repository) blocks direct HTTP access to the SQLite cache file.
GET /index.php
| Parameter | Required | Description | Example |
|---|---|---|---|
type |
Yes | Data type | memberOfParliament, person, organisation, legalDocument, officialDocument, term |
language |
No | Language code (default: de) |
de, en, fr |
wikidataID |
Conditional | Wikidata Q-ID | Q567 |
id |
Conditional | OPTV internal document ID | 12345 |
dipID |
Conditional | DIP Bundestag document ID | 278452 |
sourceURI |
Conditional | PDF source URL | https://dserver.bundestag.de/btd/19/12345.pdf |
parliament |
No | Parliament shortcode for faction mapping | de |
thumbWidth |
No | Thumbnail width in pixels (default: 300) |
200 |
key |
Conditional | API key (if access control enabled) | abc123 |
nocache |
No | Force a fresh fetch and refresh the cache (requires a valid key when access control is on) |
1 |
Success:
{
"meta": {
"api": {
"version": "1.0",
"documentation": "https://github.com/OpenParliamentTV/OpenParliamentTV-Additional-Data-Service",
"license": { "label": "ODC Open Database License (ODbL) v1.0", "link": "https://opendatacommons.org/licenses/odbl/1-0/" }
},
"requestStatus": "success"
},
"data": { ... }
}Error:
{
"meta": { "api": { ... }, "requestStatus": "error" },
"errors": [{ "info": "wrong or missing parameter", "field": "wikidataID" }]
}Requires: wikidataID
{
"type": "memberOfParliament",
"id": "Q567",
"label": "Angela Merkel",
"labelAlternative": ["Angie"],
"firstName": "Angela",
"lastName": "Merkel",
"degree": "Dr.",
"degreeFull": "Doktor der Naturwissenschaften",
"gender": "female",
"birthDate": "+1954-07-17T00:00:00Z",
"deathDate": null,
"abstract": "...",
"websiteURI": "https://www.bundeskanzlerin.de",
"thumbnailURI": "https://upload.wikimedia.org/...",
"thumbnailCreator": "EU2017EE Estonian Presidency",
"thumbnailLicense": "CC-BY 2.0",
"socialMediaIDs": [{"label": "Instagram", "id": "bundeskanzlerin"}],
"additionalInformation": {
"abgeordnetenwatchID": "79137",
"wikipedia": { "title": "Angela Merkel", "url": "https://de.wikipedia.org/wiki/Angela_Merkel" }
},
"partyID": "Q49762",
"party": "Christlich Demokratische Union Deutschlands",
"factionID": "Q1023134",
"factionLabel": "CDU/CSU-Fraktion"
}partyID, party, factionID, factionLabel are only present for memberOfParliament.
Requires: wikidataID
{
"type": "organisation",
"id": "Q49762",
"label": "CDU",
"labelAlternative": ["Christlich Demokratische Union Deutschlands"],
"abstract": "...",
"websiteURI": "https://www.cdu.de",
"thumbnailURI": "...",
"thumbnailCreator": "...",
"thumbnailLicense": "...",
"socialMediaIDs": [...],
"additionalInformation": {
"wikipedia": { "title": "CDU", "url": "https://de.wikipedia.org/wiki/CDU" }
}
}sourceURI is added for legalDocument (built from Wikidata P7677 or P9696).
Requires: one of dipID, id, or sourceURI
{
"type": "officialDocument",
"id": "278452",
"label": "Drucksache 20/14748",
"labelAlternative": ["..."],
"sourceURI": "https://dserver.bundestag.de/btd/20/147/2014748.pdf",
"additionalInformation": {
"originID": "278452",
"subType": "Beschlussempfehlung",
"date": "2025-01-29",
"electoralPeriod": 20,
"creator": [...],
"procedureIDs": [...]
},
"_sourceItem": { ... }
}| Source | Used for |
|---|---|
| Wikidata REST API | Primary entity data (person, org, term, legalDocument) |
| Wikidata Action API | Batch label resolution (given name, family name, degree, party) |
| Wikipedia REST API | Text abstracts |
| Wikimedia Commons API | Thumbnail URLs, creator attribution, license |
| Abgeordnetenwatch API | Bundestag faction membership |
| DIP Bundestag API | Official parliament documents |
/
├── index.php # Entry point
├── config.php # Local config (not in git)
├── config.sample.php # Config template
├── src/
│ ├── Api/
│ │ ├── WikidataRestClient.php # REST API client (primary entity fetches)
│ │ ├── WikidataActionClient.php # Action API client (batch label resolution)
│ │ ├── WikipediaClient.php
│ │ ├── WikimediaCommonsClient.php
│ │ ├── AbgeordnetenwatchClient.php
│ │ └── DipBundestagClient.php
│ ├── Handler/
│ │ ├── PersonHandler.php # person / memberOfParliament
│ │ ├── OrganisationHandler.php # organisation / term / legalDocument
│ │ └── OfficialDocumentHandler.php
│ ├── Response/
│ │ └── ApiResponse.php # Response builder
│ ├── Cache/
│ │ └── ResponseCache.php # SQLite response cache
│ └── Util/
│ ├── WikidataProperties.php # Property ID constants + gender map
│ ├── StringHelper.php # Creator/license string cleaning
│ └── FactionMapper.php # Faction label → Wikidata ID
├── cache/ # SQLite cache file (auto-created, not in git)
├── data/
│ ├── faction_to_wikidata_de.json
│ └── abgeordnetenwatch_party_to_wikidata.json
└── tests/
├── test_cases.php # Shared test case definitions
├── capture_fixtures.php # Capture API output snapshots
└── compare_fixtures.php # Compare output against snapshots
# Start a local dev server from the repo root
php -S localhost:8080 index.php
# Capture current output as fixtures (do this before making changes)
php tests/capture_fixtures.php http://localhost:8080/
# After making changes, compare against saved fixtures
php tests/compare_fixtures.php http://localhost:8080/
# Quick manual tests
curl "http://localhost:8080/?type=person&wikidataID=Q567&language=de"
curl "http://localhost:8080/?type=memberOfParliament&wikidataID=Q567&language=de"
curl "http://localhost:8080/?type=organisation&wikidataID=Q49762&language=de"
curl "http://localhost:8080/?type=officialDocument&dipID=278452"
curl "http://localhost:8080/?type=legalDocument&wikidataID=Q105994&language=de"AGPL-3.0 — see LICENSE