diff --git a/.changeset/count-aggregator-api-initial-release.md b/.changeset/count-aggregator-api-initial-release.md
new file mode 100644
index 00000000..eec12823
--- /dev/null
+++ b/.changeset/count-aggregator-api-initial-release.md
@@ -0,0 +1,7 @@
+---
+"@mapsight/count-aggregator-api": major
+---
+
+Initial release of the count-aggregator API client: OpenAPI contract, generated Zod schemas, typed fetch client, URL helpers, and fixtures/tests.
+
+---
diff --git a/README.md b/README.md
index a25fd360..8e13642e 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,7 @@ Mapsight is a framework for building web applications with OpenLayers and React.
| 🌐 [**`lib-ol`**](packages/lib-ol/README.md)
 | **OpenLayers utilities**
This package contains utilities for working with OpenLayers. |
| ⚛️ [**`lib-redux`**](packages/lib-redux/README.md)
 | **Redux utilities**
This package contains utilities for working with Redux. |
| ⚙️ [**`lib-js`**](packages/lib-js/README.md)
 | **JavaScript utilities (Deprecated)**
This package contains utilities for working with JavaScript.
⚠️ **Warning:** Do not depend on this package. It is deprecated and will be removed in the future. |
+| 📊 [**`count-aggregator-api`**](packages/count-aggregator-api/README.md)
 | **Count aggregator API client**
OpenAPI contract, Zod schemas, and typed HTTP client for Mapsight count-aggregator station metadata and aggregated count data. |
## Applications
diff --git a/configs/vite-workspace-dev.mts b/configs/vite-workspace-dev.mts
index 876ccd5c..3aa956f8 100644
--- a/configs/vite-workspace-dev.mts
+++ b/configs/vite-workspace-dev.mts
@@ -12,6 +12,7 @@ export const repoRoot = path.resolve(configDir, "..");
/** Workspace packages resolved from source during Vite dev (serve). */
export const workspacePackages = [
"@mapsight/core",
+ "@mapsight/count-aggregator-api",
"@mapsight/ui",
"@mapsight/lib-js",
"@mapsight/lib-ol",
@@ -20,6 +21,10 @@ export const workspacePackages = [
const packageSourceRoots = {
"@mapsight/core": path.join(repoRoot, "packages/core/src/js"),
+ "@mapsight/count-aggregator-api": path.join(
+ repoRoot,
+ "packages/count-aggregator-api/src",
+ ),
"@mapsight/ui": path.join(repoRoot, "packages/ui/src/js"),
"@mapsight/lib-js": path.join(repoRoot, "packages/lib-js/src/js"),
"@mapsight/lib-ol": path.join(repoRoot, "packages/lib-ol/src/js"),
diff --git a/packages/count-aggregator-api/.env.example b/packages/count-aggregator-api/.env.example
new file mode 100644
index 00000000..33c70d55
--- /dev/null
+++ b/packages/count-aggregator-api/.env.example
@@ -0,0 +1,9 @@
+# Copy to `.env` for local maintenance tasks (not required for build or unit tests).
+
+# Live OpenAPI JSON used by `pnpm sync-openapi`
+# Example: https://.example.tld/msp/public/count-aggregator/openapi.json
+COUNT_AGGREGATOR_OPENAPI_URL=
+
+# Public API base URL used by opt-in smoke tests (`pnpm test:smoke`)
+# Example: https://.example.tld/msp/public/count-aggregator
+COUNT_AGGREGATOR_API_BASE=
diff --git a/packages/count-aggregator-api/.gitignore b/packages/count-aggregator-api/.gitignore
new file mode 100644
index 00000000..1c79342b
--- /dev/null
+++ b/packages/count-aggregator-api/.gitignore
@@ -0,0 +1,2 @@
+.env
+.env.local
diff --git a/packages/count-aggregator-api/PLAN.md b/packages/count-aggregator-api/PLAN.md
new file mode 100644
index 00000000..300dc324
--- /dev/null
+++ b/packages/count-aggregator-api/PLAN.md
@@ -0,0 +1,270 @@
+# Count Aggregator — Improvement Plan
+
+Step-by-step roadmap for `@mapsight/count-aggregator-api` and `@mapsight/count-aggregator-ui`.
+
+**Scope:** Make both packages understandable to newcomers on GitHub, tighten client ergonomics, reduce duplication, and close the test gap on the UI side.
+
+**Out of scope:** `private/docs/count-aggregator/` — treat as local scratch notes; do **not** commit or maintain them in public docs. All user-facing documentation lives in the package READMEs (and a root README table entry).
+
+**Documentation policy:** Package READMEs must not reference customer-specific or private apps. Use placeholder hosts (`https://.example.tld/...`) until a public Mapsight platform tenant exists. Runnable demos live in open-source apps only (see Phase 1b).
+
+**Companion doc:** [UI execution checklist](../count-aggregator-ui/PLAN.md) — same phases, UI-focused tasks and acceptance criteria.
+
+---
+
+## How to use this plan
+
+1. Work phases **in order** unless noted otherwise.
+2. Check off tasks as they land; keep acceptance criteria honest.
+3. One PR per phase is ideal (easier review); small phases may be combined.
+4. Update this file when scope changes — it is the source of truth.
+
+---
+
+## Phase 1 — Discoverability & package documentation
+
+**Goal:** A GitHub visitor can find the feature, understand what each package does, and copy working embed/client snippets without reading source first.
+
+**Depends on:** nothing.
+
+### Tasks
+
+- [x] **1.1** Add both packages to the root [README.md](../../README.md) package table (name, one-line description, npm badge, link to package README).
+- [x] **1.2** Write [README.md](./README.md) for `@mapsight/count-aggregator-api` (see [outline below](#readme-outline-count-aggregator-api)).
+- [x] **1.3** Write [README.md](../count-aggregator-ui/README.md) for `@mapsight/count-aggregator-ui` (see [outline below](#readme-outline-count-aggregator-ui)).
+- [x] **1.4** Cross-link the two package READMEs (no customer-specific app references).
+- [x] **1.5** Note in the UI README that a runnable local demo is planned in [Phase 1b](#phase-1b--reference-demo-mock-api) (mock API until a public platform tenant exists).
+
+### Acceptance criteria
+
+- Root README lists `@mapsight/count-aggregator-api` and `@mapsight/count-aggregator-ui` with npm version badges.
+- Each package README stands alone: purpose, install (workspace + npm), build, test, minimal usage, exports overview, placeholder live-API URL pattern.
+- No references to `private/docs/count-aggregator/` or private customer apps in committed docs.
+- A new contributor can copy the README code snippets without reading source first.
+
+### Estimated effort
+
+~0.5–1 day.
+
+---
+
+## Phase 1b — Reference demo (mock API)
+
+**Goal:** A contributor can run the count-aggregator UI locally against mocked platform data in under 10 minutes — without a live Mapsight tenant.
+
+**Depends on:** Phase 1 (READMEs describe the packages; demo section can be filled in here).
+
+### Tasks
+
+- [x] **1b.1** Add an open-source demo entry point in [`apps/showcase`](../../apps/showcase) — **not** a private customer app.
+- [x] **1b.2** Mock count-aggregator HTTP responses (station types, station list, time-series values) — reuse fixtures from `@mapsight/count-aggregator-api` where possible.
+- [x] **1b.3** Wire `CountAggregatorRoot` → `QueryClientProvider` → `CountAggregatorProvider` → `CountAggregatorWizard` with a stepped `bicycleCount` (or equivalent) config.
+- [x] **1b.4** Add **“Run the demo locally”** to the UI README: install, dev command, URL path.
+- [ ] **1b.5** (Optional) Mock platform-only endpoints (presets, events) if overview / preset UI needs them in the demo.
+
+### Acceptance criteria
+
+- Demo runs from documented commands with no `.env` secrets and no live tenant.
+- UI README “Run the demo locally” section is complete.
+- Demo code lives only in open-source `apps/` — no customer branding or private URLs in package sources.
+
+### Estimated effort
+
+~1 day.
+
+---
+
+## Phase 2 — Config & API surface cleanup
+
+**Goal:** One obvious way to configure apps; fewer legacy entry points; less duplicated constants.
+
+**Depends on:** Phase 1 (so deprecations are documented in README).
+
+### Tasks — shared
+
+- [ ] **2.1** Consolidate `DEFAULT_PUBLIC_API_BASE_URL` and `DEFAULT_SMART_CITY_API_BASE_URL` into a single exported constant (re-export alias if needed for compat).
+- [ ] **2.2** Document the **canonical config path**: `createStationTypeAppsConfig` + dynamic `station-types` from the API. Mark `createPlatformConfig` and `TrafficDataWizard` / `SmartCityWizard` / `WheelCounterWizard` as **deprecated** in JSDoc + README migration note.
+- [ ] **2.3** Fix `usePresets()` to accept `appId` (or derive from context) instead of hardcoding `"traffic-data"`.
+- [ ] **2.4** Align `isFeatureEnabled` fallback logic with the deprecated `showEvents` / `endpoints.presets` fields — document removal timeline in UI README.
+
+### Tasks — API package
+
+- [ ] **2.5** Export shared datetime validation from one module (`LOCAL_DATE_TIME_PATTERN` / `assertLocalDateTimeFields` used by `parseLocalDateTime` tests).
+- [ ] **2.6** Decide on **park-and-ride routes** in the OpenAPI contract: keep with explicit aliases, or strip from client spec. Document the decision in the API README either way.
+- [ ] **2.7** Post-process [scripts/generate.ts](./scripts/generate.ts) to fix the broken alias `count-aggregator.public.` on `/park-and-ride/export` (or strip unrelated routes in `sync-openapi`).
+
+### Acceptance criteria
+
+- No behavior regressions in published package APIs (existing integrators keep working).
+- README “Getting started” shows only the canonical config path; legacy path in a “Migration / deprecated” section.
+- No duplicate base-URL constants with different names unless one is a documented alias.
+
+### Estimated effort
+
+~1 day.
+
+---
+
+## Phase 3 — Client ergonomics & DRY
+
+**Goal:** Call sites are typed and readable; mappers do not re-parse already-validated JSON.
+
+**Depends on:** Phase 2.7 (codegen alias fix).
+
+### Tasks — API package
+
+- [ ] **3.1** Add **typed endpoint helpers** (thin wrappers), e.g. `listStations(client, type)`, `getValues(client, request)`, `getLastValues(...)`, returning inferred Zod types — keep generated `endpoints` for advanced use.
+- [ ] **3.2** Re-export helpers from [src/index.ts](./src/index.ts); document in README as the recommended API (bracket aliases = escape hatch).
+- [ ] **3.3** Add unit tests for each helper (mock fetch, assert URL + return type).
+- [ ] **3.4** Document in README: JSON client vs CSV URL builders (`buildCsvExportUrl` — never call CSV through `createCountAggregatorClient`).
+
+### Tasks — UI package
+
+- [ ] **3.5** Rewire [hooks.ts](../count-aggregator-ui/src/api/hooks.ts) and [fetch-metric-data.ts](../count-aggregator-ui/src/feature-details-metrics/lib/fetch-metric-data.ts) to use typed helpers — remove `as TimeSeriesResponse` casts.
+- [ ] **3.6** Simplify [mappers.ts](../count-aggregator-ui/src/api/mappers.ts): trust client/helper output; parse once at the boundary (helper or hook, not both).
+- [ ] **3.7** Extract shared **time series → chart points** mapping used by `mapTimeSeriesMap` and `fetchMetricTimeSeries` into one function (API package or UI `headless` export).
+
+### Tasks — headless export
+
+- [ ] **3.8** Export mappers / shared mapping from `@mapsight/count-aggregator-ui/headless` for custom UIs (document in UI README).
+
+### Acceptance criteria
+
+- No `as TimeSeriesResponse` (or similar) casts in UI production code.
+- README quickstart uses typed helpers, not bracket notation.
+- `pnpm test` green in both packages.
+
+### Estimated effort
+
+~1–1.5 days.
+
+---
+
+## Phase 4 — Testing & CI confidence
+
+**Goal:** UI test coverage matches its risk; typecheck covers test files; optional integration smoke exists.
+
+**Depends on:** Phase 3 (helpers stabilize hook tests).
+
+### Tasks — API package
+
+- [ ] **4.1** Add tests for any new typed helpers (Phase 3).
+- [ ] **4.2** Tighten [tsconfig.test.json](./tsconfig.test.json): match `core` pattern — include only `src/**/*.test.ts`, exclude fixtures from typecheck graph if not needed (today it includes all `src/**/*.ts`).
+- [ ] **4.3** (Optional) Scheduled CI job: `SMOKE_COUNT_AGGREGATOR_API=1 pnpm test:smoke` with repo secrets — document in API README, not required for merge.
+
+### Tasks — UI package
+
+- [ ] **4.4** Add [tsconfig.test.json](../count-aggregator-ui/tsconfig.test.json) (extend package tsconfig; include `src/**/*.test.ts(x)`; `types: ["node"]`).
+- [ ] **4.5** Add `typecheck:test` script + `"typecheck": "run-s typecheck:lib typecheck:test"` (mirror API package).
+- [ ] **4.6** **Hook tests** — `useAggregatedValues`, `useLastValues`, `useStations`: mock helpers/client; assert query keys, `enabled` in stepped mode, URL params.
+- [ ] **4.7** **Mapper tests** — `mapStationList`, `mapTimeSeriesMap` (dates, empty map, MSP id keys).
+- [ ] **4.8** **ResultStep tests** — validation messages, CSV href when valid/invalid, truncation warning copy when `showExport`.
+- [ ] **4.9** **prepareChartValues tests** — 5k cap behavior per station count.
+- [ ] **4.10** (Optional) **Playwright** smoke in the Phase 1b demo app: open wizard, select station, stepped “Weiter”, assert chart container visible.
+
+### Acceptance criteria
+
+- UI package: at least **20** meaningful unit tests (up from ~10), covering hooks + wizard result path.
+- `pnpm typecheck` passes in both packages including test tsconfigs.
+- Manual QA checklist (below) executed once against the mock demo (or a staging tenant when available) and results noted in UI README or PR description.
+
+### Manual QA checklist (`bicycleCount` stepped wizard)
+
+- [ ] Station list loads on hub + wizard routes.
+- [ ] Stepped wizard: selection → Weiter → chart renders.
+- [ ] Resolution change refetches / updates chart.
+- [ ] CSV download returns semicolon-separated data.
+- [ ] Overview page: preset charts + CSV links (when presets are mocked or enabled).
+- [ ] Embed: host-page global `button`/`input` rules do not break wizard controls inside `.msp-count-aggregator`.
+
+### Estimated effort
+
+~1.5–2 days.
+
+---
+
+## Phase 5 — Polish & long-term notes
+
+**Goal:** Close remaining nits; set expectations for i18n and platform-only features.
+
+**Depends on:** Phases 1–4.
+
+### Tasks
+
+- [ ] **5.1** README **“Non-goals / future work”** sections: i18n (German strings inline today), platform presets/events (not in OpenAPI), platform base URL mirror, public SaaS tenant docs when available.
+- [ ] **5.2** (Optional) Zod schemas for presets/events responses — replace hand-rolled asserts in [platform.ts](../count-aggregator-ui/src/config/platform.ts) when platform endpoints are specced.
+- [ ] **5.3** (Optional) Visual regression: Storybook or Playwright screenshot with fake CMS wrapper (aggressive global `button`/`input` rules).
+- [ ] **5.4** Remove deprecated exports in a **future major** (0.2.0+): `createPlatformConfig`, legacy wizard components — track in README changelog section.
+- [ ] **5.5** Mark this PLAN.md phases complete or archive completed sections to `PLAN.md` → “Completed” appendix.
+
+### Acceptance criteria
+
+- No open “known smell” from the review without a documented decision or ticket.
+- Deprecated APIs have JSDoc `@deprecated` + README migration path.
+
+### Estimated effort
+
+~0.5–1 day (optional items additional).
+
+---
+
+## README outline: count-aggregator-api
+
+Deliverable for Phase 1.2. Suggested sections:
+
+1. **Title + one paragraph** — typed client for the Mapsight count-aggregator HTTP API.
+2. **Install** — workspace / npm package name.
+3. **Quick start** — `createCountAggregatorClient` + typed helper (after Phase 3; stub with bracket call until then).
+4. **Scripts** — `generate`, `build`, `test`, `test:smoke`, `sync-openapi` + `.env.example`.
+5. **Exports** — client, schemas, URL builders, datetime helpers, types.
+6. **OpenAPI** — `@mapsight/count-aggregator-api/openapi.json`, sync from live, wheel-counter stripped.
+7. **CSV vs JSON** — use URL builders for CSV downloads.
+8. **Live API links** — placeholder URL pattern (`https://.example.tld/msp/public/count-aggregator`); note public tenant TBD.
+9. **Architecture** — generated vs hand-written files; do not edit `src/generated/client.ts`.
+10. **Related packages** — link to `@mapsight/count-aggregator-ui`.
+11. **Improvement plan** — link to this file.
+
+---
+
+## README outline: count-aggregator-ui
+
+Deliverable for Phase 1.3. Suggested sections:
+
+1. **Title + one paragraph** — embeddable React wizard/charts for count-aggregator data.
+2. **Peer dependencies** — `react`, `react-dom`, `@tanstack/react-query`, `react-select`, `@mapsight/ui`.
+3. **Required CSS** — `import "@mapsight/count-aggregator-ui/styles.css"`.
+4. **Minimal embed example** — `CountAggregatorRoot` → `QueryClientProvider` → `CountAggregatorProvider` → `CountAggregatorWizard`.
+5. **Config** — `createStationTypeAppsConfig`, `features`, `uiVariant`, resolutions.
+6. **Exports** — main vs `/headless` vs `/styles.css`.
+7. **Embedding** — root wrapper, `msca:` prefix, portals; link to theming (`createTheme`).
+8. **Run the demo locally** — Phase 1b; placeholder until mock demo lands.
+9. **Smart-city metrics** — brief note on `mountSmartCityMetrics` / feature-details placeholders.
+10. **Testing** — `pnpm test`, what is covered.
+11. **Deprecated** — legacy platform config (after Phase 2).
+12. **Related packages** — link to API package + this PLAN companion.
+
+---
+
+## Phase summary
+
+| Phase | Focus | Packages | Blocker |
+| ----- | ------------------------------------------ | ------------------- | ------- |
+| 1 | READMEs + root discovery | both | — |
+| 1b | Mock demo app | both + open `apps/` | Phase 1 |
+| 2 | Config deprecation, constants, codegen fix | both + API | Phase 1 |
+| 3 | Typed helpers, DRY mappers | both | Phase 2 |
+| 4 | Tests, typecheck:test, QA | both + demo app | Phase 3 |
+| 5 | Polish, optional hardening | both | Phase 4 |
+
+**Total estimate:** ~5.5–7 days of focused work (excluding optional Phase 5 items).
+
+---
+
+## Completed
+
+_Move finished phases here with date and PR link when done._
+
+
diff --git a/packages/count-aggregator-api/README.md b/packages/count-aggregator-api/README.md
new file mode 100644
index 00000000..0804e39f
--- /dev/null
+++ b/packages/count-aggregator-api/README.md
@@ -0,0 +1,150 @@
+# @mapsight/count-aggregator-api
+
+Typed HTTP client, Zod schemas, and OpenAPI contract for the Mapsight **count-aggregator** public API — station metadata and aggregated count time series (bicycle counters, traffic sensors, and other station types).
+
+Pair with [`@mapsight/count-aggregator-ui`](../count-aggregator-ui/README.md) for an embeddable React wizard and charts, or use this package alone in your own UI.
+
+## Install
+
+**npm** (when published):
+
+```bash
+npm install @mapsight/count-aggregator-api
+```
+
+**pnpm workspace** (monorepo):
+
+```bash
+pnpm --filter @mapsight/count-aggregator-api build
+```
+
+## Quick start
+
+Create a client pointed at your tenant’s public count-aggregator base URL, then call generated endpoint aliases. Typed helper wrappers are planned ([Phase 3](./PLAN.md#phase-3--client-ergonomics--dry)); until then, use bracket notation:
+
+```ts
+import {
+ createCountAggregatorClient,
+ schemas,
+} from "@mapsight/count-aggregator-api";
+
+const baseUrl = "https://.example.tld/msp/public/count-aggregator";
+const client = createCountAggregatorClient(baseUrl);
+
+// List stations for a type
+const stationList = schemas.StationListResponse.parse(
+ await client["count-aggregator.public.type.stations"]({
+ params: {type: "bicycleCount"},
+ }),
+);
+
+// Aggregated values for one or more stations
+const valuesMap = await client["count-aggregator.public.type.values"]({
+ params: {
+ type: "bicycleCount",
+ from: "2025-06-01",
+ to: "2025-06-07",
+ resolution: "daily",
+ },
+ queries: {
+ stationIds: "150",
+ },
+});
+```
+
+Inject a custom `fetch` (tests, SSR, proxies):
+
+```ts
+const client = createCountAggregatorClient(baseUrl, {
+ fetch: myFetch,
+});
+```
+
+## Scripts
+
+From this package directory (or via `pnpm --filter @mapsight/count-aggregator-api