The canonical response shape
Every paginated endpoint returns the same JSON envelope. Code once against it and swap datasets by changing the path.
Shape
{
"total": 139,
"returned": 20,
"pages": 7,
"page": 1,
"limit": 50,
"has_more": true,
"next_page": 2,
"remaining_pages": 6,
"coverage_complete": true,
"published_from": "2026-01-01",
"published_to": "2026-01-31",
"published_end": "2026-01-31",
"updated_from": null,
"updated_to": null,
"updated_end": null,
"detail_level": "Full",
"data": [ ... dataset-specific items ... ]
}
Attribution lives on response headers, not in the body:
X-Powered-By: Brubru
X-Source: brubru.beresol.eu
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Tier: free
Pagination fields
| Field | Type | Meaning |
|---|---|---|
total | int | Total matching items across all pages. For live-fetched endpoints where total isn't knowable (MEPs), this is a best-effort hint: offset + items + 1 if the page is full. |
returned | int | Items in this page. Always equals len(data). |
pages | int | Total number of pages given the current limit. |
page | int | Current page, 1-indexed. |
limit | int | Items per page. Default 50, max 100. |
has_more | bool | True if there's at least one more page. |
next_page | int | null | Next page number, or null if on last page. |
remaining_pages | int | Pages left after this one. |
coverage_complete | bool | true for DB-backed endpoints (we've seen everything). false for live-scraped endpoints where upstream pagination is unbounded. |
Filter-echo fields
Date-range filters you sent come back in the envelope so you can verify the API interpreted them correctly:
| Field | Comes from |
|---|---|
published_from | Your ?published_from= query param |
published_to | Your ?published_to= query param |
published_end | Alias of published_to (GovClipping-compatible). Identical value. |
updated_from | Your ?updated_from= query param |
updated_to | Your ?updated_to= query param |
updated_end | Alias of updated_to. Identical value. |
Date params: semantics. published_to and published_end are aliases of the same upper bound. They mean the same thing. If you send both with different values, the API returns 422 conflicting_params. Prefer published_to; published_end exists for GovClipping partners.
detail_level
Accepted values: Minimal, Summary (default), Full. Lowercase is also accepted (full, summary). The envelope echoes whatever the client sent.
- Summary (default): core fields only: id, title, date, url, key identifiers.
- Full: all available fields. For
/laws: adds text excerpts, legal basis, citations, defined-terms count. For/procedures: adds OEIL key events, shadow rapporteurs, AI summary. - Minimal: just id + title + source_url. Useful for typeahead / autocomplete.
Attribution: response headers, not envelope
Provenance information is on response headers, not in the JSON body. Removed from the envelope on 30 April 2026 after partner feedback: payloads are now leaner and the same data is one HEAD away.
| Header | Meaning |
|---|---|
X-Powered-By: Brubru | Always present on /api/v2/* responses. |
X-Source: brubru.beresol.eu | Origin attribution. |
X-RateLimit-Limit | Per-minute ceiling for your tier (60 / 300 / 6000). |
X-RateLimit-Remaining | Calls left in the current 60-second sliding window. |
X-RateLimit-Tier | Your tier: free, pro, or enterprise. |
If you display Brubru data in your UI, we ask that you honour the X-Powered-By attribution either visually (a "Powered by Brubru" badge) or structurally in the raw metadata of your storage.
Dataset-specific aggregates
Some endpoints (e.g. /consultations/.../feedback) include a sibling aggregates object alongside data, giving counts by country and user_type in the same response so you don't need a separate summary call.
Specialised EC databases (May 2026): body_threshold + has_body
The specialised database endpoints: the Commission registers (/api/v2/commission/financial-sanctions, /comitology, /trade-defence, /state-aid, /dma-cases, /taric-tariffs), the Council sanctions regimes, and the geographical-indications registers (/api/v2/geographical-indications/*): all return the same envelope, plus a per-row contract that documents whether the full body of each item has been fetched into our local cache:
has_body(bool):trueifbody_text/body_htmlis present for this row.falseif only the metadata has been ingested.body_threshold(query param): pass?body_threshold=200to include only rows wherebody_textis at least 200 characters; useful for ranking, full-text scoring, or RAG ingest.body_source: provenance of the body when fetched:cellar_xhtml(preferred),cellar_pdf(fallback for proposals),scrape(legacy), ornull.
The live pass-through endpoints (DG COMP state aid and DMA cases, the Combined Nomenclature tariff lookup) are fetched from the upstream registry at request time: on these, has_body is meaningless and the envelope's coverage_complete will be false for the same reason.
Sort order
All list endpoints are sorted most-recent-first by date (adoption, publication, event, or last-updated depending on dataset). Within ties, secondary sort is id descending. Custom sort via ?sort= is on the June 2026 roadmap.
Non-envelope responses
A few endpoints return a flat object instead of the paginated envelope:
/health: status + time only (public, no key required).- Folder directory endpoints (e.g.
/api/v2/ecb,/api/v2/acer): a short index of what the folder carries. /api/v2/legislative/eurovoc/authority/{table}: valid filter values / lookup tables./api/v2/legislative/eur-lex/laws/{celex}/text: full-text response./api/v2/legislative/eur-lex/laws/{celex}/recital-article-mapand/defined-terms: derived objects./api/v2/legislative/eur-lex/resolve-referencesand/resolve-aliases: plain object withrefsoraliases.
Next
Read about Errors next.