Levend document. Wordt in dezelfde commit bijgewerkt als elke wijziging aan de API. Afwijking tussen dit contract en de implementatie = bug.
Versie:
v1· Status: Fase 11 · Laatst bijgewerkt: 2026-06-19
De EbbenHub-API ontsluit de gepubliceerde content (bomen, assets, artikelen, taxonomie en relaties) read-only voor externe afnemers, bijv. de Ebben-website. EbbenHub blijft de bron van waarheid; deze API is puur leesbaar.
Basis
- Base URL:
/api/v1 - Methode: uitsluitend
GET - Formaat: JSON (
Content-Type: application/json) - Zichtbaarheid: alleen
gepubliceerdebomen en artikelen. Concepten lekken nooit naar buiten — niet als lijstitem, niet als detail (404), en niet als gekoppeld item in een relatie of "waar gebruikt".
Authenticatie
Elke request moet een geldige, actieve API-sleutel meesturen via één van:
Authorization: Bearer <sleutel>X-Api-Key: <sleutel>
De sleutel wordt in het admin-panel beheerd (Systeem → API-sleutels), bij
aanmaken éénmalig in plaintext getoond en daarna alleen als SHA-256-hash bewaard.
Eén toegangsniveau: een geldige sleutel geeft toegang tot alle gepubliceerde
data (geen per-sleutel scopes). Bij elk geslaagd verzoek wordt last_used_at
bijgewerkt.
| Situatie | Respons |
|---|---|
| Geen / ongeldige / ingetrokken | 401 Unauthorized — {"message": "Ongeldige of ontbrekende API-sleutel."} |
| Geldige, actieve sleutel | 200 OK |
Taal (meertaligheid)
Vertaalbare velden (name, description, title, …) worden in één taal
geleverd, bepaald door — in volgorde:
- de query-parameter
?lang=nlof?lang=en; - anders de
Accept-Language-header (nl/en); - anders de standaardtaal
nl.
Paginatie
Lijst-endpoints pagineren via Laravel's standaard paginator:
?page=<n>— paginanummer (default 1)?per_page=<n>— items per pagina (default 25, max 100)
De respons bevat data, links en meta (met o.a. current_page, per_page,
total, last_page).
Endpoints
GET /api/v1/trees
Gepagineerde lijst van gepubliceerde bomen (compacte vorm).
Query-parameters
| Parameter | Type | Omschrijving |
|---|---|---|
search |
string | Zoekt op latijnse naam, sorteernaam en NL/EN naam. |
filter[<filter_id>] |
string | Eén of meer value_id's (komma-gescheiden) van een kenmerk; een boom matcht áls hij een van die opties draagt (OR binnen het facet). |
range[<filter_id>] |
string | van,tot voor een range-kenmerk (hoogte/breedte/maand/zone); matcht op overlap (lege grens = onbegrensd). |
lang, page, per_page |
Zie hierboven. |
filter_id en value_id komen 1-op-1 uit GET /api/v1/taxonomy. Meerdere
facetten combineren met AND.
Voorbeeld
GET /api/v1/trees?search=quercus&filter[2]=30,31&range[10]=4,8&lang=nl
Authorization: Bearer ebbh_xxxxxxxx
{
"data": [
{
"id": 12,
"latin_name": "Quercus robur",
"keyword": "QUERROB",
"sort_name": "Quercus robur",
"synonym": null,
"type": "Loofboom",
"name": "Zomereik",
"description": "…",
"marketing_text": "…",
"status": "gepubliceerd",
"hero": { "type": "asset", "id": 5, "title": "Quercus robur", "url": "https://…", "mime_type": "image/jpeg", "is_image": true }
}
],
"links": { "first": "…", "last": "…", "prev": null, "next": "…" },
"meta": { "current_page": 1, "per_page": 25, "total": 1, "last_page": 1 }
}
GET /api/v1/trees/{id}
Volledig boomdetail. Een niet-gepubliceerde boom geeft 404.
Bovenop de lijstvelden:
| Veld | Omschrijving |
|---|---|
attributes |
Kenmerken gegroepeerd per kenmerkgroep. Per kenmerk: group, kenmerk, filter_id, type, en óf options (value_id + name) óf min/max. |
media |
Gekoppelde DAM-assets (compacte asset-referenties), in de gekozen volgorde. |
categories |
Eigen collecties (namen). |
tags |
Eigen labels (namen). |
relations |
Bidirectioneel relatie-web: per relatie label + een compacte referentie naar het gekoppelde (gepubliceerde) item (type = tree / article / asset). |
{
"data": {
"id": 12,
"latin_name": "Quercus robur",
"name": "Zomereik",
"status": "gepubliceerd",
"hero": { "type": "asset", "id": 5, "url": "https://…", "is_image": true },
"attributes": [
{ "group": "Standplaats", "kenmerk": "Locatie", "filter_id": 2, "type": "enumselect", "options": [ { "value_id": 30, "name": "Park" } ] },
{ "group": "Maatvoering", "kenmerk": "Hoogte", "filter_id": 10, "type": "rangeslider", "min": 18.0, "max": 25.0 }
],
"media": [ { "type": "asset", "id": 5, "url": "https://…" } ],
"categories": ["Favorieten"],
"tags": ["Inheems"],
"relations": [
{ "label": "bestuiver", "type": "tree", "id": 18, "latin_name": "Quercus petraea", "keyword": "QUERPET", "name": "Wintereik" },
{ "label": "gerelateerd", "type": "article", "id": 3, "article_type": "kennisbank", "slug": "eiken-snoeien", "title": "Eiken snoeien" }
]
}
}
GET /api/v1/articles
Gepagineerde lijst van gepubliceerde artikelen (blog + kennisbank).
Query-parameters: type (blog | kennisbank), lang, page, per_page.
{
"data": [
{
"id": 3,
"type": "kennisbank",
"slug": "eiken-snoeien",
"title": "Eiken snoeien",
"status": "gepubliceerd",
"featured_image": { "type": "asset", "id": 9, "url": "https://…", "is_image": true }
}
],
"meta": { "current_page": 1, "per_page": 25, "total": 1, "last_page": 1 }
}
GET /api/v1/articles/{id}
Volledig artikeldetail; niet-gepubliceerd geeft 404. Bovenop de lijstvelden:
content (rijke HTML, gekozen taal), media, categories, tags, en
relations — inclusief de gekoppelde bomen (type: "tree").
GET /api/v1/assets
Gepagineerde lijst van DAM-assets (URL's + metadata).
Query-parameters: search (titel/bestandsnaam/alt-tekst), page, per_page.
{
"data": [
{
"id": 5,
"title": "Quercus robur",
"alt_text": "Zomereik in het park",
"description": null,
"mime_type": "image/jpeg",
"size": 248173,
"is_image": true,
"url": "https://…/storage/assets/quercus-robur.jpg",
"folder": { "id": 2, "name": "Bomen" }
}
],
"meta": { "current_page": 1, "per_page": 25, "total": 1, "last_page": 1 }
}
GET /api/v1/assets/{id}
Volledig assetdetail. Bovenop de lijstvelden:
| Veld | Omschrijving |
|---|---|
used_by |
"Waar gebruikt": trees en articles (compacte referenties) die deze asset gebruiken — alleen gepubliceerde items. |
relations |
Polymorf relatie-web rond de asset (bidirectioneel, gepubliceerde items). |
GET /api/v1/taxonomy
De volledige facet-taxonomie (groepen → filters → opties) waarop de bomen-lijst
gefilterd kan worden. Levert de stabiele filter_id en value_id die je in
filter[...] / range[...] op /trees gebruikt.
{
"data": [
{
"group_id": 1,
"name": "Standplaats",
"filters": [
{
"filter_id": 2,
"name": "Locatie",
"type": "enumselect",
"is_range": false,
"options": [
{ "value_id": 30, "name": "Park" },
{ "value_id": 31, "name": "Straat" }
]
},
{
"filter_id": 10,
"name": "Hoogte",
"type": "rangeslider",
"is_range": true,
"options": []
}
]
}
]
}
Compacte referenties
Cross-links (hero, media, relations, used_by) gebruiken een korte,
type-getagde vorm:
- tree:
{ "type": "tree", "id", "latin_name", "keyword", "name" } - article:
{ "type": "article", "id", "article_type", "slug", "title" } - asset:
{ "type": "asset", "id", "title", "url", "mime_type", "is_image" }
In relations staat daarnaast een label (bijv. verwant, alternatief,
bestuiver, gerelateerd).