Query API endpoints
The query API lives at api.leadmaps.nl. All endpoints accept and return
JSON. This page documents every header, status code, and error envelope you
will deal with as a caller.
Authentication
Section titled “Authentication”Two modes, both via Authorization: Bearer <...>:
admin- an admin token for cross-workspace operator access.apiKey- a workspace API key, with one of the scopes:ingest:write,query:read,admin.
A non-admin caller probing a workspace it has no access to gets a 404 with
the same envelope as “not found”. leadmaps never confirms the existence of a
workspace you cannot see through a status-code difference.
Endpoint table
Section titled “Endpoint table”| Method | Path | Auth |
|---|---|---|
GET | /healthz | none |
GET | /workspaces | admin |
POST | /workspaces | admin |
GET | /workspaces/:slug | admin |
PATCH | /workspaces/:slug | admin |
GET | /workspaces/:slug/sites | admin |
POST | /workspaces/:slug/sites | admin |
DELETE | /workspaces/:slug/sites/:id | admin |
GET | /workspaces/:slug/keys | admin or admin-scoped api_key |
POST | /workspaces/:slug/keys | admin or admin-scoped api_key |
DELETE | /workspaces/:slug/keys/:keyId | admin or admin-scoped api_key |
GET | /workspaces/:slug/audit | admin or admin-scoped api_key |
GET | /sites/:siteId/events | admin or query:read api_key |
GET | /sites/:siteId/pageviews | admin or query:read api_key |
GET | /sites/:siteId/sessions | admin or query:read api_key |
GET | /sites/:siteId/reports/summary-v2 | admin or query:read api_key |
GET | /sites/:siteId/reports/pages-v2 | admin or query:read api_key |
GET | /sites/:siteId/reports/sources-v2 | admin or query:read api_key |
GET | /sites/:siteId/reports/campaigns-v2 | admin or query:read api_key |
GET | /sites/:siteId/events/list-v2 | admin or query:read api_key |
GET | /sites/:siteId/events/overview-v2 | admin or query:read api_key |
GET | /sites/:siteId/events/activity-v2 | admin or query:read api_key |
GET | /sites/:siteId/events/stream-v2 | admin or query:read api_key |
GET | /sites/:siteId/users/:userId/timeline | admin or query:read api_key |
POST | /workspaces/:slug/analysis/funnels-v2 | admin or query:read api_key |
POST | /workspaces/:slug/analysis/funnels-v2/correlation | admin or query:read api_key |
POST | /workspaces/:slug/analysis/funnels-v2/explanation | admin or query:read api_key |
GET | /workspaces/:slug/attribution/results | admin or query:read api_key |
GET | /workspaces/:slug/roi | admin or query:read api_key |
GET | /workspaces/:slug/roi/compare | admin or query:read api_key |
GET | /workspaces/:slug/spend | admin or query:read api_key |
POST | /workspaces/:slug/spend | admin or admin-scoped api_key |
POST | /workspaces/:slug/spend/csv | admin or admin-scoped api_key |
PATCH | /workspaces/:slug/spend/:id | admin or admin-scoped api_key |
DELETE | /workspaces/:slug/spend | admin or admin-scoped api_key |
GET | /v2/sites/:siteId/consent-banner/config | admin or query:read api_key |
PUT | /v2/sites/:siteId/consent-banner/config | admin or admin-scoped api_key |
POST | /sites/:siteId/sourcemaps | site bearer |
GET | /sites/:siteId/dlq | admin or admin-scoped api_key |
POST | /sites/:siteId/dlq/:eventId/replay | admin or admin-scoped api_key |
POST | /sites/:siteId/gdpr/export | admin or admin-scoped api_key |
POST | /sites/:siteId/gdpr/delete | admin or admin-scoped api_key |
POST | /webhooks/:adapter/:siteId | per-adapter signature |
Analytics
Section titled “Analytics”All analytics endpoints sit under /sites/:siteId/. A request for a site
that does not exist or that you cannot access returns 404. from and to
are YYYY-MM-DD and inclusive on both ends.
Exact V2 reports
Section titled “Exact V2 reports”The dashboard uses the V2 report and Events endpoints listed above. Every JSON
report has a strict { meta, data } envelope. meta records the exact scope,
measure definitions, freshness watermark, completeness, warnings, truncation,
and cursor state applied by the server. A successful response that does not
match the shared runtime schema is rejected by the dashboard instead of being
rendered as plausible data.
Common report filters are:
fromandto, required UTC dates. Exact report and historical Events ranges support at most 367 inclusive days and reject a start date after the end date.scope=all-sitesfor a workspace-wide result. Without it,:siteIdis the population.sourcefor one acquisition or referrer source.device,browser,os,country,region,city, andpath. Each is a comma-separated list with at most 20 values.internal=includeto include traffic tagged as internal. The default isexclude.timezone=UTCandbots=excludeare the only accepted values. Customer analytics cannot opt bot traffic back in.
pages-v2 accepts include_params, limit from 1 to 200, and an opaque
cursor. sources-v2 and campaigns-v2 accept limit from 1 to 200. The
response metadata tells you whether rows were truncated or another cursor page
exists.
Events V2 separates three read models:
list-v2is a date-bounded, ingest-ordered event list withlimitfrom 1 to 200 and an opaque cursor.overview-v2returns exact event totals and the top event types for a date range.activity-v2returns one dense 60-minute window and twelve five-minute buckets per top path.stream-v2is an SSE tail. It accepts an opaque resume cursor plus optionalnameandanon_idfilters. The initial cursor frame is valid even when no new events are waiting.
Workspace analysis
Section titled “Workspace analysis”Funnel V2 accepts one complete query object with 2 to 12 ordered steps, a completion window, date mode, site scope, source and dimension filters, internal-traffic choice, and an optional breakdown. The correlation and explanation endpoints take that same query plus the selected transition and execution identity, so auxiliary panels cannot analyze a different population.
Attribution results require model, from, and to ISO dates or
timezone-qualified timestamps. The inclusive range can span at most 366 days.
They accept conversion_id, an opaque cursor, and limit from 1 to 10,000.
ROI and spend reads use inclusive YYYY-MM-DD ranges of at most 366 days. ROI
stays workspace-wide, keeps totals separated by currency, and returns ratio
fields only when spend and revenue have one compatible currency.
Legacy rollups
Section titled “Legacy rollups”GET /sites/:siteId/pageviews?from=&to=
Section titled “GET /sites/:siteId/pageviews?from=&to=”{ "count": 12345, "by_day": [{ "date": "2026-04-01", "count": 412 }]}400 invalid_date_param when missing/malformed; 400 invalid_date_range
when from > to.
GET /sites/:siteId/events?from=&to=
Section titled “GET /sites/:siteId/events?from=&to=”{ "by_name": [{ "name": "pageview", "count": 12345 }]}Sorted by count desc, then name asc.
GET /sites/:siteId/sessions?from=&to=
Section titled “GET /sites/:siteId/sessions?from=&to=”{ "count": 1024, "avg_duration_s": 187.4, "avg_events": 4.2, "bounce_rate": 0.314}Empty range yields all zeros (never null or NaN).
GET /sites/:siteId/users/:userId/timeline?limit=&before=
Section titled “GET /sites/:siteId/users/:userId/timeline?limit=&before=”Per-user event timeline. Resolves cross-device merges so a row appears for every anonymous id ever bound to the user.
{ "events": [ { "id": "<uuid>", "type": "<event-type>", "url": "…" | null, "ts": "<iso>", "anon_id": "…", "referrer": "…" | null, "country": "NL" | null, "city": "Amsterdam" | null, "browser": "Chrome" | null, "os": "macOS" | null, "device_type": "desktop" | "mobile" | "tablet" | "other" | null } ], "next_before": "<iso>" | null}404 user_not_found when no known user matches (site_id, user_id).
The raw event payload and the parsed user-agent string are deliberately omitted from this response, because they may carry PII you never intended to expose at a per-user surface.
Consent banner configuration
Section titled “Consent banner configuration”The authenticated control-plane endpoints at
/v2/sites/:siteId/consent-banner/config read and save the safe banner model
used by the dashboard. A save includes the last config_version, a unique
idempotency_key, and an explicit request_reconsent choice. Stale saves
return 409 so one browser cannot silently overwrite another browser’s work.
Requests made with the platform administrator credential also include
X-Workspace-Id; the API compares it with the site’s resolved workspace.
Workspace API keys are already pinned to their own workspace by authentication.
The endpoint accepts only the bounded content and design controls described in Customize the consent banner. It rejects raw HTML, CSS, scripts, and unknown fields. This is not a browser bootstrap endpoint: generated installation code embeds the validated settings so a visitor’s browser does not contact leadmaps to fetch a banner before consent.
Sourcemaps
Section titled “Sourcemaps”POST /sites/:siteId/sourcemaps
Section titled “POST /sites/:siteId/sourcemaps”Sourcemap upload. Authenticate with the site bearer for that site. A request
for a site you cannot access returns 401, not 403, so a probing caller
cannot confirm a site exists.
// request{ "release": "<git-sha>", "file": "<basename.js>", "map": "<base64>" }
// 201{ "size": 12345 }Body cap: 25 MB decoded. Idempotent upsert on (site_id, release, file).
GET /sites/:siteId/dlq?limit=&cursor=
Section titled “GET /sites/:siteId/dlq?limit=&cursor=”Cursor-paginated DLQ rows.
{ "events": [ { "id": "<uuid>", "site_id": "site_marketing", "payload": { /* original wire JSON */ }, "error": [{ "path": "/total_cents", "message": "must be >= 0" }], "received_at": "<iso>" } ], "next_cursor": "<iso>" | null}POST /sites/:siteId/dlq/:eventId/replay
Section titled “POST /sites/:siteId/dlq/:eventId/replay”Re-submits the row’s payload to the ingest endpoint.
200 { id, replayed_at }on accept. The DLQ row is deleted.502 ingest_rejectedon any other status. The DLQ row stays in place.502 ingest_unreachableon network error.
The replay re-enters the full ingest pipeline by design, so consent, rate-limit, and dedup gates are re-evaluated.
See GDPR endpoints for the full request / response
walkthrough. Both export and delete are admin or admin-scoped
api_key.
See Audit log. Cursor-paginated read at
GET /workspaces/:slug/audit.
Workspaces, sites, keys
Section titled “Workspaces, sites, keys”See Workspaces + API keys for the lifecycle walkthrough. The CRUD endpoints follow REST conventions. Check the error compatibility note below when one client calls both site-level V2 routes and older workspace routes.
Webhooks
Section titled “Webhooks”See Webhooks.
Error envelopes
Section titled “Error envelopes”Site-level V2 report and Events endpoints use the structured envelope:
{ "error": { "code": "<stable_string>", "message": "<human-readable>", "details": [/* optional */] }}code is part of the V2 contract. message may evolve. details carries
structured per-field errors, such as schema validation failures.
Some older workspace endpoints, including attribution, ROI, and spend, retain their compatibility envelope:
{ "error": "<stable_string>", "message": "<human-readable>"}Treat the nested error.code and the flat error value as the stable machine
codes for their respective endpoint families. Do not parse human-readable
messages. A future versioned migration can make the shapes uniform without
silently breaking existing clients.