Sharing & public pages
Two sharing modes — share links and public site pages — their APIs and fields, the current data status, and security notes.
Overview#
metricsKit offers two outward sharing modes:
- Share links: `/share/{token}` — a read-only link for a single site with a scope and optional password / expiry.
- Public site pages: `/{domain}` — exposes a site as a page that can be indexed (or explicitly noindexed) by search engines.
Note: Neither mode is gated by plan; both are available on Free.
Share links#
Share links are API only — there is no frontend create/manage UI.
- Create: `POST /api/v1/sites/:siteId/share-links`, body `{ scope?, expires_at?, password? }`.
- List: `GET /api/v1/sites/:siteId/share-links`; revoke: `DELETE /api/v1/share-links/:id`; stats: `GET /api/v1/share-links/:id/stats`.
- `scope` only allows `overview | metrics | full`, defaulting to `overview`.
- `expires_at` is optional; no value means permanent (`null`), otherwise it must be a valid ISO time in the future.
- The optional `password` protects access; once revoked, resolution fails and returns 404.
- The token is a 32-character base62 random string; the URL is `${PUBLIC_URL}/share/${token}`.
Note: Share pages are always `noindex, nofollow` and will not be indexed by search engines.
Public site pages#
A public site page exposes a site; data is served by `GET /public/site/:domain` and configuration goes through `GET/PUT /api/v1/sites/:siteId/public-config`.
It is public only when all of these hold, otherwise it always returns 404 (with no distinction between missing / disabled / deleted): `sites.status='active'`, `public_sites.enabled=1`, and `sites.domain` matching the requested domain.
| Field | Meaning |
|---|---|
| enabled | Whether the public page is enabled (0/1), default 0 |
| show_in_search | Whether indexing is allowed (0/1), default 1 |
| custom_title | Custom title (≤ 200) |
| custom_description | Custom description (≤ 500) |
| og_image_url | Open Graph image URL (≤ 2000) |
Note: `show_in_search=1` emits `index,follow`, otherwise `noindex,follow`; setting `enabled` to 0 makes the public page return 404 immediately.
Current status#
Note: Honest note: the backend public response shape does not match the frontend contract, and the aggregation logic ends up discarding results and returning empty data. So today the share and public pages are only a shell with placeholders — metrics show `—`, and trends and lists are empty.
- The backend returns `{ ok, data: { ... } }`, but the frontend `getPublic()` does not unwrap it and reads top-level fields, so the contracts do not match.
- The share aggregation returns only `{ site_id, scope, expires_at, site }`, with no `view_count` and no metric data.
- The public site `getAggregatedData` calls `DataService` but ultimately discards the result and returns `EMPTY_DATA`.
- There is no frontend create/config UI: both share links and public-page config can only be managed via the API.
Security note#
Note: The optional share password is stored as an unsalted SHA-256 hash, which is weak; a wrong password and a non-existent link both return 404 without distinguishing the cause, so the existence of a link is not leaked.
- The public access password can come from the query `password` or the header `x-share-password`.
- Neither the create nor the list endpoint returns `password_hash`.
Related docs#
- Plans & feature gating: /plans
- Data sources overview: /data-sources
- Site connections & authorization: /connections