# Tinkerer Analytics
> Privacy-first analytics for websites, apps and games, by Tinkerer Labs. It stores daily totals only: no cookies, no visitor profiles, no raw event log. Integration is a single HTTPS endpoint. A small JavaScript tracker is provided, but any language or runtime that can send an HTTPS POST with a JSON body can integrate directly. The service is in closed beta: accounts are created by invitation, and each tester signs in to a personal panel to create sites and read their numbers.
## Pages
- [Product home](https://tinkererlabs.co/analytics): what the product is.
- [Closed beta notes](https://tinkererlabs.co/analytics/beta): how the beta works, what is counted, what is never collected.
- [Panel](https://tinkererlabs.co/analyticspanel): sign in here to create a site, declare its properties, manage its tokens, and copy its install snippet.
- [Stats dashboard](https://tinkererlabs.co/analyticsdashboard/): sign in here to read daily counts, funnels and breakdowns.
## How integration works
1. Sign in to the panel and create a site. Site names live in your account's own namespace, so a name only has to be free among your own sites. Note its site token: the token is public by design and only identifies the site.
2. Declare the site's allowed domains. Events are accepted only from those domains.
3. If the site sends properties, declare each property's shape in the panel first. Undeclared or out-of-shape properties cause the whole event to be rejected.
4. Copy the install snippet from the panel's Install tab, or send events directly as described below. The panel shows a waiting state until the first event arrives.
## Sending events
- Endpoint: POST https://tinkerer-prod.guneykayim.workers.dev/api/event
- Body: JSON with `site_token` (string), `event` (string), optional `properties` (object), and `game_id` (string, lifecycle events only).
- Required: an `Origin` header (or `Referer`) whose host is one of the site's allowed domains. A request with no origin is rejected with reason `missing_origin`; a wrong origin is rejected with `disallowed_origin`. Browsers send this header automatically. Server-side and non-JavaScript clients must set it themselves.
- Events: `pageview` and `link_click` are stateless. `game_started`, `game_first_move` and `game_completed` form one lifecycle per game: send them in that order, all carrying the same `game_id`.
- Property shapes a site may declare: an enum (`{ "values": ["a", "b"] }`), an integer range (`{ "type": "integer", "min": 1, "max": 4 }`), a boolean (`{ "type": "boolean" }`), or a bounded string (`{ "type": "string", "maxLength": 40 }`).
- The response is a JSON receipt: `status` is `accepted`, `filtered` (recognised bots are counted separately, never in the main totals) or `rejected` with a `reason`. Accepted receipts carry an `event_id`; resending the same event is safe and never double-counts.
- Rate limit: 300 events per minute per IP per site.
- Service version: the signed-in pages (home, panel, stats) show the current Tinkerer Analytics version in their top bar; releases are tagged with that version. Quote it when reporting a problem.
## JavaScript tracker
The panel's snippet imports the tracker and fires a pageview; the same calls work for every event:
```html
```
The tracker never throws and never blocks the page: failed sends are dropped silently.
Zero-code link clicks: when a site's declared spec includes a `link` property, the tracker automatically fires `link_click` for clicks on any element carrying `data-tinkerer-link=""` (clicks on children of the element count too). Values outside a declared enum are ignored. Pass `autoLinkClicks: false` to `createTracker` to turn this off.
## Any other language
The tracker is a convenience, not a requirement. This complete integration is a single request:
```bash
curl -X POST https://tinkerer-prod.guneykayim.workers.dev/api/event \
-H 'Content-Type: application/json' \
-H 'Origin: https://example.com' \
-d '{"site_token":"YOUR_SITE_TOKEN","event":"pageview"}'
```
The same shape works from Python, Go, Rust, C#, PHP, Unity, or a game engine: one HTTPS POST, one JSON body, one Origin header naming your own allowed domain. Send events from the visitor's browser or app where you can: traffic that looks automated may land in the filtered bucket instead of the main counts.
## Modes
- Strict mode: daily totals only. No visitor identifier is ever created.
- Standard mode: daily totals plus unique visitors per day. A visitor is recognised within one day by a one-way salted hash; the salt rotates daily and the hashes are deleted once the day is finalised.
## Reading data back
- The stats dashboard shows today, the last 7 days and the last 30 days, with a day-by-day table and per-property breakdowns.
- CSV export: GET https://tinkererlabs.co/analyticspanelapi/export.csv?site_id=&from=&to= returns the range as one CSV file: a daily table (date, one column per event, plus unique_players for standard-mode sites only), a totals row, then headed sections for each property breakdown and for the filtered and quarantined summaries. With no from/to it defaults to the last 30 days. The site dashboard's Download CSV button exports the view on screen. Reads are scoped like the stats API: an account can only export its own sites.
- A management API covers the same ground for automation. Ask support for the current API notes during the beta.
- Command-line client: prebuilt binaries for macOS, Linux and Windows are published at https://github.com/tinkererlabs/tinkerer-cli/releases. A tester signs in to the panel, opens CLI access, and issues their own account credential there (shown once, stored hash-only, revocable on the same page; new credentials cannot be issued from the CLI itself). Set it as TINKERER_API_TOKEN and run `tinkerer sites list`. `tinkerer schema` prints the machine-readable contract and `tinkerer version` the build version.
- Build-step projects can download a generated spec file (JSON, JavaScript or TypeScript) from the panel, matching the site's declared properties exactly.
## Contact
- Support: support@tinkererlabs.co
- Privacy: privacy@tinkererlabs.co