{"owner":"electerious","repo":"Ackee","hasSkills":true,"totalSkillsCount":2,"totalTokensCount":2300,"categories":["root-instruction","project-spec"],"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md","docs/API.md"],"skills":{"AGENTS.md":"# Agent notes\n\n- `npm run build` only generates environment-specific HTML/custom tracker. After UI or tracker changes, run `npm run build:pre`; JS/CSS/tracker bundles in `dist/` are tracked. Never edit bundles by hand.\n- Tracker source lives in external `ackee-tracker` package, not this repo.\n- UI uses React `createElement as h`, not JSX; build disables Babel. `src/types/` contains GraphQL schemas, not TypeScript.\n- `src/utils/config.js` reads env variables lazily so tests can mock them. Don't replace it with import-time constants.\n- Keep API behavior aligned between `src/server.js` (Express) and `src/serverless.js` (Netlify/Vercel), including CORS, headers, and request context.\n- Compose protected resolvers with `pipe(requireAuth, ...)`; add `blockDemoMode` for domain/event/permanent-token writes. Tracking mutations stay public and work in demo mode. Use `KnownError` for expected user-facing failures.\n- Preserve privacy: salted client IDs rotate daily; new records anonymize older records for that client. Never persist raw IPs/user agents or expose client IDs.\n- Ignored visits/actions return success without writes; creation returns sentinel UUID `88888888-8888-8888-8888-888888888888`. Keep this tracker contract.\n- `npm test` includes lint + AVA; `npx ava test/path.js` runs focused tests. Tests need Node 24.12+ or 26+ and start in-memory MongoDB; no separate DB needed.\n- Reuse test helpers and serial tests for shared DB/env state. Teardown must stop MongoDB, close server, and cancel salt job to avoid hanging tests.\n","docs/API.md":"# API\n\nAckee features a GraphQL API that allows you to build custom tools upon Ackee. Everything you see in the UI is made from data delivered by the API.\n\nHere are a few resources to help you get started with GraphQL:\n\n- https://graphql.org/learn/\n- https://www.howtographql.com\n\n## Playground\n\nAckee uses Apollo Sandbox, an interactive, in-browser GraphQL IDE. It allows you to play and experiment with the API of Ackee.\n\nSimply run Ackee with `NODE_ENV` set to `development` and visit the `/api` playground in your browser. You can do so by adding `NODE_ENV=development` to the environment of your `docker-compose.yml` or by using `npm run dev`. Only use this mode on your local machine as Ackee runs slower when in development mode.\n\nTry the [🔮 live playground](https://demo.ackee.electerious.com/api) of the Ackee demo.\n\n## Authentication\n\nModifying domains or receiving aggregated data is only possible once you're authenticated. Only the creation of new data is possible without a token.\n\n### Creating a token\n\nThe following mutation returns a new token `id` that should be used for authentication. This is what happens in the UI when you submit your username and password.\n\n```graphql\nmutation createToken($input: CreateTokenInput!) {\n  createToken(input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"username\": \"admin\",\n    \"password\": \"123456\"\n  }\n}\n```\n\nThe token is valid for one day and will be renewed on every request made with it. You can modify the TTL (time to live) in [the options](Options.md#ttl). [Create a permanent token](#creating-a-permanent-token) if you need a token that doesn't expire.\n\n### Use a token\n\nProtected queries and mutations need to include the `Authorization` HTTP header. Replace `tokenId` with the token `id` from the previous step.\n\n```json\n{\n  \"Authorization\": \"Bearer tokenId\"\n}\n```\n\n### Creating a permanent token\n\nPermanent tokens don't expire and are perfect for tools that run periodically in the background. In this case you want a token that is always valid so you don't have to enter your credentials over and over again.\n\nYou can create permanent tokens in the settings of Ackee or via the API. Creating a permanent token requires a valid [Authorization header](#use-a-token). This means that you can only create permanent tokens with a valid (non-permanent) token.\n\n```graphql\nmutation createPermanentToken($input: CreatePermanentTokenInput!) {\n  createPermanentToken(input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"title\": \"iOS widget\"\n  }\n}\n```\n\n### Use a permanent token\n\nUsing a permanent token is the same as [using a (non-permanent) token](#use-a-token).\n\n## Time Zone\n\nIt's sometimes necessary to know the time zone of the user to accurately group stats by day, month or year. We therefore recommend to include a `Time-Zone` header when requesting data. The time zone should be formatted as a [tz database time zone string](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).\n\nThe time zone of the server will be used as a fallback.\n\n```json\n{\n  \"Time-Zone\": \"Europe/Berlin\"\n}\n```\n\n## Queries\n\nQueries are used to receive data. Here are a few examples.\n\n- [Get all domains](#get-all-domains)\n- [Get a specific domain](#get-a-specific-domain)\n- [Get facts of domains](#get-facts-of-domains)\n- [Get statistics of domains](#get-statistics-of-domains)\n- [Get events](#get-events)\n\n### Get all domains\n\n```graphql\nquery getDomains {\n  domains {\n    id\n    title\n  }\n}\n```\n\n### Get a specific domain\n\n```graphql\nquery getDomain($id: ID!) {\n  domain(id: $id) {\n    id\n    title\n  }\n}\n```\n\n```json\n{\n  \"id\": \"3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e\"\n}\n```\n\n### Get facts of domains\n\n```graphql\nquery getDomainsFacts {\n  domains {\n    facts {\n      activeVisitors\n      averageViews {\n        count\n        change\n      }\n      averageDuration {\n        count\n        change\n      }\n      viewsToday\n      viewsMonth\n      viewsYear\n    }\n  }\n}\n```\n\n### Get statistics of domains\n\n```graphql\nquery getDomainsStatistics {\n  domains {\n    statistics {\n      durations(interval: DAILY) {\n        id\n        value\n        count\n      }\n      views(interval: YEARLY, type: UNIQUE) {\n        id\n        value\n        count\n      }\n      languages(sorting: TOP) {\n        id\n        value\n        count\n        created\n      }\n      browsers(sorting: TOP, type: WITH_VERSION) {\n        id\n        value\n        count\n        created\n      }\n      devices(sorting: TOP, type: WITH_MODEL) {\n        id\n        value\n        count\n        created\n      }\n      pages(sorting: TOP) {\n        id\n        value\n        count\n        created\n      }\n      referrers(sorting: TOP, type: WITH_SOURCE) {\n        id\n        value\n        count\n        created\n      }\n      sizes(sorting: TOP, type: SCREEN_RESOLUTION) {\n        id\n        value\n        count\n        created\n      }\n      systems(sorting: TOP, type: NO_VERSION) {\n        id\n        value\n        count\n        created\n      }\n    }\n  }\n}\n```\n\n### Get events\n\n```graphql\nquery getEvents {\n  events {\n    id\n    title\n    statistics {\n      chart(interval: DAILY, type: TOTAL) {\n        id\n        value\n        count\n      }\n      list(sorting: TOP, type: TOTAL) {\n        id\n        value\n        count\n      }\n    }\n  }\n}\n```\n\n## Mutations\n\nMutations are used to add, update or delete data. Here are a few examples.\n\n- [Create a domain](#create-a-domain)\n- [Delete a domain](#delete-a-domain)\n- [Create a record](#create-a-record)\n- [Create an event](#create-an-event)\n- [Create an action](#create-an-action)\n- [Update an action](#update-an-action)\n\n### Create a domain\n\n```graphql\nmutation createDomain($input: CreateDomainInput!) {\n  createDomain(input: $input) {\n    payload {\n      id\n      title\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"title\": \"Domain Title\"\n  }\n}\n```\n\n### Delete a domain\n\n```graphql\nmutation deleteDomain($id: ID!) {\n  deleteDomain(id: $id) {\n    success\n  }\n}\n```\n\n```json\n{\n  \"id\": \"3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e\"\n}\n```\n\n### Create a record\n\n```graphql\nmutation createRecord($domainId: ID!, $input: CreateRecordInput!) {\n  createRecord(domainId: $domainId, input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"domainId\": \"3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e\",\n  \"input\": {\n    \"siteLocation\": \"https://example.com\"\n  }\n}\n```\n\n### Create an event\n\n```graphql\nmutation createEvent($input: CreateEventInput!) {\n  createEvent(input: $input) {\n    payload {\n      id\n      title\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"title\": \"Event Title\",\n    \"type\": \"TOTAL_CHART\"\n  }\n}\n```\n\n### Create an action\n\n```graphql\nmutation createAction($eventId: ID!, $input: CreateActionInput!) {\n  createAction(eventId: $eventId, input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"eventId\": \"c8865d94-9077-420f-86a0-32545bcbf61b\",\n  \"input\": {\n    \"key\": \"Action Key\",\n    \"value\": 1\n  }\n}\n```\n\n### Update an action\n\n```graphql\nmutation updateAction($id: ID!, $input: UpdateActionInput!) {\n  updateAction(id: $id, input: $input) {\n    success\n  }\n}\n```\n\n```json\n{\n  \"id\": \"34df5a09-498f-45c1-822c-6b1f80de5f8c\",\n  \"input\": {\n    \"key\": \"Action Key\",\n    \"value\": null\n  }\n}\n```\n"},"files":{"AGENTS.md":"# Agent notes\n\n- `npm run build` only generates environment-specific HTML/custom tracker. After UI or tracker changes, run `npm run build:pre`; JS/CSS/tracker bundles in `dist/` are tracked. Never edit bundles by hand.\n- Tracker source lives in external `ackee-tracker` package, not this repo.\n- UI uses React `createElement as h`, not JSX; build disables Babel. `src/types/` contains GraphQL schemas, not TypeScript.\n- `src/utils/config.js` reads env variables lazily so tests can mock them. Don't replace it with import-time constants.\n- Keep API behavior aligned between `src/server.js` (Express) and `src/serverless.js` (Netlify/Vercel), including CORS, headers, and request context.\n- Compose protected resolvers with `pipe(requireAuth, ...)`; add `blockDemoMode` for domain/event/permanent-token writes. Tracking mutations stay public and work in demo mode. Use `KnownError` for expected user-facing failures.\n- Preserve privacy: salted client IDs rotate daily; new records anonymize older records for that client. Never persist raw IPs/user agents or expose client IDs.\n- Ignored visits/actions return success without writes; creation returns sentinel UUID `88888888-8888-8888-8888-888888888888`. Keep this tracker contract.\n- `npm test` includes lint + AVA; `npx ava test/path.js` runs focused tests. Tests need Node 24.12+ or 26+ and start in-memory MongoDB; no separate DB needed.\n- Reuse test helpers and serial tests for shared DB/env state. Teardown must stop MongoDB, close server, and cancel salt job to avoid hanging tests.\n","docs/API.md":"# API\n\nAckee features a GraphQL API that allows you to build custom tools upon Ackee. Everything you see in the UI is made from data delivered by the API.\n\nHere are a few resources to help you get started with GraphQL:\n\n- https://graphql.org/learn/\n- https://www.howtographql.com\n\n## Playground\n\nAckee uses Apollo Sandbox, an interactive, in-browser GraphQL IDE. It allows you to play and experiment with the API of Ackee.\n\nSimply run Ackee with `NODE_ENV` set to `development` and visit the `/api` playground in your browser. You can do so by adding `NODE_ENV=development` to the environment of your `docker-compose.yml` or by using `npm run dev`. Only use this mode on your local machine as Ackee runs slower when in development mode.\n\nTry the [🔮 live playground](https://demo.ackee.electerious.com/api) of the Ackee demo.\n\n## Authentication\n\nModifying domains or receiving aggregated data is only possible once you're authenticated. Only the creation of new data is possible without a token.\n\n### Creating a token\n\nThe following mutation returns a new token `id` that should be used for authentication. This is what happens in the UI when you submit your username and password.\n\n```graphql\nmutation createToken($input: CreateTokenInput!) {\n  createToken(input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"username\": \"admin\",\n    \"password\": \"123456\"\n  }\n}\n```\n\nThe token is valid for one day and will be renewed on every request made with it. You can modify the TTL (time to live) in [the options](Options.md#ttl). [Create a permanent token](#creating-a-permanent-token) if you need a token that doesn't expire.\n\n### Use a token\n\nProtected queries and mutations need to include the `Authorization` HTTP header. Replace `tokenId` with the token `id` from the previous step.\n\n```json\n{\n  \"Authorization\": \"Bearer tokenId\"\n}\n```\n\n### Creating a permanent token\n\nPermanent tokens don't expire and are perfect for tools that run periodically in the background. In this case you want a token that is always valid so you don't have to enter your credentials over and over again.\n\nYou can create permanent tokens in the settings of Ackee or via the API. Creating a permanent token requires a valid [Authorization header](#use-a-token). This means that you can only create permanent tokens with a valid (non-permanent) token.\n\n```graphql\nmutation createPermanentToken($input: CreatePermanentTokenInput!) {\n  createPermanentToken(input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"title\": \"iOS widget\"\n  }\n}\n```\n\n### Use a permanent token\n\nUsing a permanent token is the same as [using a (non-permanent) token](#use-a-token).\n\n## Time Zone\n\nIt's sometimes necessary to know the time zone of the user to accurately group stats by day, month or year. We therefore recommend to include a `Time-Zone` header when requesting data. The time zone should be formatted as a [tz database time zone string](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).\n\nThe time zone of the server will be used as a fallback.\n\n```json\n{\n  \"Time-Zone\": \"Europe/Berlin\"\n}\n```\n\n## Queries\n\nQueries are used to receive data. Here are a few examples.\n\n- [Get all domains](#get-all-domains)\n- [Get a specific domain](#get-a-specific-domain)\n- [Get facts of domains](#get-facts-of-domains)\n- [Get statistics of domains](#get-statistics-of-domains)\n- [Get events](#get-events)\n\n### Get all domains\n\n```graphql\nquery getDomains {\n  domains {\n    id\n    title\n  }\n}\n```\n\n### Get a specific domain\n\n```graphql\nquery getDomain($id: ID!) {\n  domain(id: $id) {\n    id\n    title\n  }\n}\n```\n\n```json\n{\n  \"id\": \"3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e\"\n}\n```\n\n### Get facts of domains\n\n```graphql\nquery getDomainsFacts {\n  domains {\n    facts {\n      activeVisitors\n      averageViews {\n        count\n        change\n      }\n      averageDuration {\n        count\n        change\n      }\n      viewsToday\n      viewsMonth\n      viewsYear\n    }\n  }\n}\n```\n\n### Get statistics of domains\n\n```graphql\nquery getDomainsStatistics {\n  domains {\n    statistics {\n      durations(interval: DAILY) {\n        id\n        value\n        count\n      }\n      views(interval: YEARLY, type: UNIQUE) {\n        id\n        value\n        count\n      }\n      languages(sorting: TOP) {\n        id\n        value\n        count\n        created\n      }\n      browsers(sorting: TOP, type: WITH_VERSION) {\n        id\n        value\n        count\n        created\n      }\n      devices(sorting: TOP, type: WITH_MODEL) {\n        id\n        value\n        count\n        created\n      }\n      pages(sorting: TOP) {\n        id\n        value\n        count\n        created\n      }\n      referrers(sorting: TOP, type: WITH_SOURCE) {\n        id\n        value\n        count\n        created\n      }\n      sizes(sorting: TOP, type: SCREEN_RESOLUTION) {\n        id\n        value\n        count\n        created\n      }\n      systems(sorting: TOP, type: NO_VERSION) {\n        id\n        value\n        count\n        created\n      }\n    }\n  }\n}\n```\n\n### Get events\n\n```graphql\nquery getEvents {\n  events {\n    id\n    title\n    statistics {\n      chart(interval: DAILY, type: TOTAL) {\n        id\n        value\n        count\n      }\n      list(sorting: TOP, type: TOTAL) {\n        id\n        value\n        count\n      }\n    }\n  }\n}\n```\n\n## Mutations\n\nMutations are used to add, update or delete data. Here are a few examples.\n\n- [Create a domain](#create-a-domain)\n- [Delete a domain](#delete-a-domain)\n- [Create a record](#create-a-record)\n- [Create an event](#create-an-event)\n- [Create an action](#create-an-action)\n- [Update an action](#update-an-action)\n\n### Create a domain\n\n```graphql\nmutation createDomain($input: CreateDomainInput!) {\n  createDomain(input: $input) {\n    payload {\n      id\n      title\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"title\": \"Domain Title\"\n  }\n}\n```\n\n### Delete a domain\n\n```graphql\nmutation deleteDomain($id: ID!) {\n  deleteDomain(id: $id) {\n    success\n  }\n}\n```\n\n```json\n{\n  \"id\": \"3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e\"\n}\n```\n\n### Create a record\n\n```graphql\nmutation createRecord($domainId: ID!, $input: CreateRecordInput!) {\n  createRecord(domainId: $domainId, input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"domainId\": \"3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e\",\n  \"input\": {\n    \"siteLocation\": \"https://example.com\"\n  }\n}\n```\n\n### Create an event\n\n```graphql\nmutation createEvent($input: CreateEventInput!) {\n  createEvent(input: $input) {\n    payload {\n      id\n      title\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"title\": \"Event Title\",\n    \"type\": \"TOTAL_CHART\"\n  }\n}\n```\n\n### Create an action\n\n```graphql\nmutation createAction($eventId: ID!, $input: CreateActionInput!) {\n  createAction(eventId: $eventId, input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"eventId\": \"c8865d94-9077-420f-86a0-32545bcbf61b\",\n  \"input\": {\n    \"key\": \"Action Key\",\n    \"value\": 1\n  }\n}\n```\n\n### Update an action\n\n```graphql\nmutation updateAction($id: ID!, $input: UpdateActionInput!) {\n  updateAction(id: $id, input: $input) {\n    success\n  }\n}\n```\n\n```json\n{\n  \"id\": \"34df5a09-498f-45c1-822c-6b1f80de5f8c\",\n  \"input\": {\n    \"key\": \"Action Key\",\n    \"value\": null\n  }\n}\n```\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/electerious/Ackee/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Agent notes\n\n- `npm run build` only generates environment-specific HTML/custom tracker. After UI or tracker changes, run `npm run build:pre`; JS/CSS/tracker bundles in `dist/` are tracked. Never edit bundles by hand.\n- Tracker source lives in external `ackee-tracker` package, not this repo.\n- UI uses React `createElement as h`, not JSX; build disables Babel. `src/types/` contains GraphQL schemas, not TypeScript.\n- `src/utils/config.js` reads env variables lazily so tests can mock them. Don't replace it with import-time constants.\n- Keep API behavior aligned between `src/server.js` (Express) and `src/serverless.js` (Netlify/Vercel), including CORS, headers, and request context.\n- Compose protected resolvers with `pipe(requireAuth, ...)`; add `blockDemoMode` for domain/event/permanent-token writes. Tracking mutations stay public and work in demo mode. Use `KnownError` for expected user-facing failures.\n- Preserve privacy: salted client IDs rotate daily; new records anonymize older records for that client. Never persist raw IPs/user agents or expose client IDs.\n- Ignored visits/actions return success without writes; creation returns sentinel UUID `88888888-8888-8888-8888-888888888888`. Keep this tracker contract.\n- `npm test` includes lint + AVA; `npx ava test/path.js` runs focused tests. Tests need Node 24.12+ or 26+ and start in-memory MongoDB; no separate DB needed.\n- Reuse test helpers and serial tests for shared DB/env state. Teardown must stop MongoDB, close server, and cancel salt job to avoid hanging tests.\n","isInternal":false,"tokens":361,"sizeBytes":1540},{"name":"API.md","path":"docs/API.md","rawUrl":"https://raw.githubusercontent.com/electerious/Ackee/HEAD/docs/API.md","title":"docs - API Interface Contract & Specifications","category":"project-spec","format":"markdown","content":"# API\n\nAckee features a GraphQL API that allows you to build custom tools upon Ackee. Everything you see in the UI is made from data delivered by the API.\n\nHere are a few resources to help you get started with GraphQL:\n\n- https://graphql.org/learn/\n- https://www.howtographql.com\n\n## Playground\n\nAckee uses Apollo Sandbox, an interactive, in-browser GraphQL IDE. It allows you to play and experiment with the API of Ackee.\n\nSimply run Ackee with `NODE_ENV` set to `development` and visit the `/api` playground in your browser. You can do so by adding `NODE_ENV=development` to the environment of your `docker-compose.yml` or by using `npm run dev`. Only use this mode on your local machine as Ackee runs slower when in development mode.\n\nTry the [🔮 live playground](https://demo.ackee.electerious.com/api) of the Ackee demo.\n\n## Authentication\n\nModifying domains or receiving aggregated data is only possible once you're authenticated. Only the creation of new data is possible without a token.\n\n### Creating a token\n\nThe following mutation returns a new token `id` that should be used for authentication. This is what happens in the UI when you submit your username and password.\n\n```graphql\nmutation createToken($input: CreateTokenInput!) {\n  createToken(input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"username\": \"admin\",\n    \"password\": \"123456\"\n  }\n}\n```\n\nThe token is valid for one day and will be renewed on every request made with it. You can modify the TTL (time to live) in [the options](Options.md#ttl). [Create a permanent token](#creating-a-permanent-token) if you need a token that doesn't expire.\n\n### Use a token\n\nProtected queries and mutations need to include the `Authorization` HTTP header. Replace `tokenId` with the token `id` from the previous step.\n\n```json\n{\n  \"Authorization\": \"Bearer tokenId\"\n}\n```\n\n### Creating a permanent token\n\nPermanent tokens don't expire and are perfect for tools that run periodically in the background. In this case you want a token that is always valid so you don't have to enter your credentials over and over again.\n\nYou can create permanent tokens in the settings of Ackee or via the API. Creating a permanent token requires a valid [Authorization header](#use-a-token). This means that you can only create permanent tokens with a valid (non-permanent) token.\n\n```graphql\nmutation createPermanentToken($input: CreatePermanentTokenInput!) {\n  createPermanentToken(input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"title\": \"iOS widget\"\n  }\n}\n```\n\n### Use a permanent token\n\nUsing a permanent token is the same as [using a (non-permanent) token](#use-a-token).\n\n## Time Zone\n\nIt's sometimes necessary to know the time zone of the user to accurately group stats by day, month or year. We therefore recommend to include a `Time-Zone` header when requesting data. The time zone should be formatted as a [tz database time zone string](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).\n\nThe time zone of the server will be used as a fallback.\n\n```json\n{\n  \"Time-Zone\": \"Europe/Berlin\"\n}\n```\n\n## Queries\n\nQueries are used to receive data. Here are a few examples.\n\n- [Get all domains](#get-all-domains)\n- [Get a specific domain](#get-a-specific-domain)\n- [Get facts of domains](#get-facts-of-domains)\n- [Get statistics of domains](#get-statistics-of-domains)\n- [Get events](#get-events)\n\n### Get all domains\n\n```graphql\nquery getDomains {\n  domains {\n    id\n    title\n  }\n}\n```\n\n### Get a specific domain\n\n```graphql\nquery getDomain($id: ID!) {\n  domain(id: $id) {\n    id\n    title\n  }\n}\n```\n\n```json\n{\n  \"id\": \"3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e\"\n}\n```\n\n### Get facts of domains\n\n```graphql\nquery getDomainsFacts {\n  domains {\n    facts {\n      activeVisitors\n      averageViews {\n        count\n        change\n      }\n      averageDuration {\n        count\n        change\n      }\n      viewsToday\n      viewsMonth\n      viewsYear\n    }\n  }\n}\n```\n\n### Get statistics of domains\n\n```graphql\nquery getDomainsStatistics {\n  domains {\n    statistics {\n      durations(interval: DAILY) {\n        id\n        value\n        count\n      }\n      views(interval: YEARLY, type: UNIQUE) {\n        id\n        value\n        count\n      }\n      languages(sorting: TOP) {\n        id\n        value\n        count\n        created\n      }\n      browsers(sorting: TOP, type: WITH_VERSION) {\n        id\n        value\n        count\n        created\n      }\n      devices(sorting: TOP, type: WITH_MODEL) {\n        id\n        value\n        count\n        created\n      }\n      pages(sorting: TOP) {\n        id\n        value\n        count\n        created\n      }\n      referrers(sorting: TOP, type: WITH_SOURCE) {\n        id\n        value\n        count\n        created\n      }\n      sizes(sorting: TOP, type: SCREEN_RESOLUTION) {\n        id\n        value\n        count\n        created\n      }\n      systems(sorting: TOP, type: NO_VERSION) {\n        id\n        value\n        count\n        created\n      }\n    }\n  }\n}\n```\n\n### Get events\n\n```graphql\nquery getEvents {\n  events {\n    id\n    title\n    statistics {\n      chart(interval: DAILY, type: TOTAL) {\n        id\n        value\n        count\n      }\n      list(sorting: TOP, type: TOTAL) {\n        id\n        value\n        count\n      }\n    }\n  }\n}\n```\n\n## Mutations\n\nMutations are used to add, update or delete data. Here are a few examples.\n\n- [Create a domain](#create-a-domain)\n- [Delete a domain](#delete-a-domain)\n- [Create a record](#create-a-record)\n- [Create an event](#create-an-event)\n- [Create an action](#create-an-action)\n- [Update an action](#update-an-action)\n\n### Create a domain\n\n```graphql\nmutation createDomain($input: CreateDomainInput!) {\n  createDomain(input: $input) {\n    payload {\n      id\n      title\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"title\": \"Domain Title\"\n  }\n}\n```\n\n### Delete a domain\n\n```graphql\nmutation deleteDomain($id: ID!) {\n  deleteDomain(id: $id) {\n    success\n  }\n}\n```\n\n```json\n{\n  \"id\": \"3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e\"\n}\n```\n\n### Create a record\n\n```graphql\nmutation createRecord($domainId: ID!, $input: CreateRecordInput!) {\n  createRecord(domainId: $domainId, input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"domainId\": \"3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e\",\n  \"input\": {\n    \"siteLocation\": \"https://example.com\"\n  }\n}\n```\n\n### Create an event\n\n```graphql\nmutation createEvent($input: CreateEventInput!) {\n  createEvent(input: $input) {\n    payload {\n      id\n      title\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"title\": \"Event Title\",\n    \"type\": \"TOTAL_CHART\"\n  }\n}\n```\n\n### Create an action\n\n```graphql\nmutation createAction($eventId: ID!, $input: CreateActionInput!) {\n  createAction(eventId: $eventId, input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"eventId\": \"c8865d94-9077-420f-86a0-32545bcbf61b\",\n  \"input\": {\n    \"key\": \"Action Key\",\n    \"value\": 1\n  }\n}\n```\n\n### Update an action\n\n```graphql\nmutation updateAction($id: ID!, $input: UpdateActionInput!) {\n  updateAction(id: $id, input: $input) {\n    success\n  }\n}\n```\n\n```json\n{\n  \"id\": \"34df5a09-498f-45c1-822c-6b1f80de5f8c\",\n  \"input\": {\n    \"key\": \"Action Key\",\n    \"value\": null\n  }\n}\n```\n","isInternal":false,"tokens":1939,"sizeBytes":7223}],"systemPromptSnippet":"<yakaai_skills repo=\"electerious/Ackee\">\n<!-- File: AGENTS.md (Tokens: ~361 | Category: root-instruction) -->\n# Agent notes\n\n- `npm run build` only generates environment-specific HTML/custom tracker. After UI or tracker changes, run `npm run build:pre`; JS/CSS/tracker bundles in `dist/` are tracked. Never edit bundles by hand.\n- Tracker source lives in external `ackee-tracker` package, not this repo.\n- UI uses React `createElement as h`, not JSX; build disables Babel. `src/types/` contains GraphQL schemas, not TypeScript.\n- `src/utils/config.js` reads env variables lazily so tests can mock them. Don't replace it with import-time constants.\n- Keep API behavior aligned between `src/server.js` (Express) and `src/serverless.js` (Netlify/Vercel), including CORS, headers, and request context.\n- Compose protected resolvers with `pipe(requireAuth, ...)`; add `blockDemoMode` for domain/event/permanent-token writes. Tracking mutations stay public and work in demo mode. Use `KnownError` for expected user-facing failures.\n- Preserve privacy: salted client IDs rotate daily; new records anonymize older records for that client. Never persist raw IPs/user agents or expose client IDs.\n- Ignored visits/actions return success without writes; creation returns sentinel UUID `88888888-8888-8888-8888-888888888888`. Keep this tracker contract.\n- `npm test` includes lint + AVA; `npx ava test/path.js` runs focused tests. Tests need Node 24.12+ or 26+ and start in-memory MongoDB; no separate DB needed.\n- Reuse test helpers and serial tests for shared DB/env state. Teardown must stop MongoDB, close server, and cancel salt job to avoid hanging tests.\n\n<!-- File: docs/API.md (Tokens: ~1939 | Category: project-spec) -->\n# API\n\nAckee features a GraphQL API that allows you to build custom tools upon Ackee. Everything you see in the UI is made from data delivered by the API.\n\nHere are a few resources to help you get started with GraphQL:\n\n- https://graphql.org/learn/\n- https://www.howtographql.com\n\n## Playground\n\nAckee uses Apollo Sandbox, an interactive, in-browser GraphQL IDE. It allows you to play and experiment with the API of Ackee.\n\nSimply run Ackee with `NODE_ENV` set to `development` and visit the `/api` playground in your browser. You can do so by adding `NODE_ENV=development` to the environment of your `docker-compose.yml` or by using `npm run dev`. Only use this mode on your local machine as Ackee runs slower when in development mode.\n\nTry the [🔮 live playground](https://demo.ackee.electerious.com/api) of the Ackee demo.\n\n## Authentication\n\nModifying domains or receiving aggregated data is only possible once you're authenticated. Only the creation of new data is possible without a token.\n\n### Creating a token\n\nThe following mutation returns a new token `id` that should be used for authentication. This is what happens in the UI when you submit your username and password.\n\n```graphql\nmutation createToken($input: CreateTokenInput!) {\n  createToken(input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"username\": \"admin\",\n    \"password\": \"123456\"\n  }\n}\n```\n\nThe token is valid for one day and will be renewed on every request made with it. You can modify the TTL (time to live) in [the options](Options.md#ttl). [Create a permanent token](#creating-a-permanent-token) if you need a token that doesn't expire.\n\n### Use a token\n\nProtected queries and mutations need to include the `Authorization` HTTP header. Replace `tokenId` with the token `id` from the previous step.\n\n```json\n{\n  \"Authorization\": \"Bearer tokenId\"\n}\n```\n\n### Creating a permanent token\n\nPermanent tokens don't expire and are perfect for tools that run periodically in the background. In this case you want a token that is always valid so you don't have to enter your credentials over and over again.\n\nYou can create permanent tokens in the settings of Ackee or via the API. Creating a permanent token requires a valid [Authorization header](#use-a-token). This means that you can only create permanent tokens with a valid (non-permanent) token.\n\n```graphql\nmutation createPermanentToken($input: CreatePermanentTokenInput!) {\n  createPermanentToken(input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"title\": \"iOS widget\"\n  }\n}\n```\n\n### Use a permanent token\n\nUsing a permanent token is the same as [using a (non-permanent) token](#use-a-token).\n\n## Time Zone\n\nIt's sometimes necessary to know the time zone of the user to accurately group stats by day, month or year. We therefore recommend to include a `Time-Zone` header when requesting data. The time zone should be formatted as a [tz database time zone string](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).\n\nThe time zone of the server will be used as a fallback.\n\n```json\n{\n  \"Time-Zone\": \"Europe/Berlin\"\n}\n```\n\n## Queries\n\nQueries are used to receive data. Here are a few examples.\n\n- [Get all domains](#get-all-domains)\n- [Get a specific domain](#get-a-specific-domain)\n- [Get facts of domains](#get-facts-of-domains)\n- [Get statistics of domains](#get-statistics-of-domains)\n- [Get events](#get-events)\n\n### Get all domains\n\n```graphql\nquery getDomains {\n  domains {\n    id\n    title\n  }\n}\n```\n\n### Get a specific domain\n\n```graphql\nquery getDomain($id: ID!) {\n  domain(id: $id) {\n    id\n    title\n  }\n}\n```\n\n```json\n{\n  \"id\": \"3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e\"\n}\n```\n\n### Get facts of domains\n\n```graphql\nquery getDomainsFacts {\n  domains {\n    facts {\n      activeVisitors\n      averageViews {\n        count\n        change\n      }\n      averageDuration {\n        count\n        change\n      }\n      viewsToday\n      viewsMonth\n      viewsYear\n    }\n  }\n}\n```\n\n### Get statistics of domains\n\n```graphql\nquery getDomainsStatistics {\n  domains {\n    statistics {\n      durations(interval: DAILY) {\n        id\n        value\n        count\n      }\n      views(interval: YEARLY, type: UNIQUE) {\n        id\n        value\n        count\n      }\n      languages(sorting: TOP) {\n        id\n        value\n        count\n        created\n      }\n      browsers(sorting: TOP, type: WITH_VERSION) {\n        id\n        value\n        count\n        created\n      }\n      devices(sorting: TOP, type: WITH_MODEL) {\n        id\n        value\n        count\n        created\n      }\n      pages(sorting: TOP) {\n        id\n        value\n        count\n        created\n      }\n      referrers(sorting: TOP, type: WITH_SOURCE) {\n        id\n        value\n        count\n        created\n      }\n      sizes(sorting: TOP, type: SCREEN_RESOLUTION) {\n        id\n        value\n        count\n        created\n      }\n      systems(sorting: TOP, type: NO_VERSION) {\n        id\n        value\n        count\n        created\n      }\n    }\n  }\n}\n```\n\n### Get events\n\n```graphql\nquery getEvents {\n  events {\n    id\n    title\n    statistics {\n      chart(interval: DAILY, type: TOTAL) {\n        id\n        value\n        count\n      }\n      list(sorting: TOP, type: TOTAL) {\n        id\n        value\n        count\n      }\n    }\n  }\n}\n```\n\n## Mutations\n\nMutations are used to add, update or delete data. Here are a few examples.\n\n- [Create a domain](#create-a-domain)\n- [Delete a domain](#delete-a-domain)\n- [Create a record](#create-a-record)\n- [Create an event](#create-an-event)\n- [Create an action](#create-an-action)\n- [Update an action](#update-an-action)\n\n### Create a domain\n\n```graphql\nmutation createDomain($input: CreateDomainInput!) {\n  createDomain(input: $input) {\n    payload {\n      id\n      title\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"title\": \"Domain Title\"\n  }\n}\n```\n\n### Delete a domain\n\n```graphql\nmutation deleteDomain($id: ID!) {\n  deleteDomain(id: $id) {\n    success\n  }\n}\n```\n\n```json\n{\n  \"id\": \"3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e\"\n}\n```\n\n### Create a record\n\n```graphql\nmutation createRecord($domainId: ID!, $input: CreateRecordInput!) {\n  createRecord(domainId: $domainId, input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"domainId\": \"3b8bc3ed-cdcb-492a-bc6d-8d5b2746da0e\",\n  \"input\": {\n    \"siteLocation\": \"https://example.com\"\n  }\n}\n```\n\n### Create an event\n\n```graphql\nmutation createEvent($input: CreateEventInput!) {\n  createEvent(input: $input) {\n    payload {\n      id\n      title\n    }\n  }\n}\n```\n\n```json\n{\n  \"input\": {\n    \"title\": \"Event Title\",\n    \"type\": \"TOTAL_CHART\"\n  }\n}\n```\n\n### Create an action\n\n```graphql\nmutation createAction($eventId: ID!, $input: CreateActionInput!) {\n  createAction(eventId: $eventId, input: $input) {\n    payload {\n      id\n    }\n  }\n}\n```\n\n```json\n{\n  \"eventId\": \"c8865d94-9077-420f-86a0-32545bcbf61b\",\n  \"input\": {\n    \"key\": \"Action Key\",\n    \"value\": 1\n  }\n}\n```\n\n### Update an action\n\n```graphql\nmutation updateAction($id: ID!, $input: UpdateActionInput!) {\n  updateAction(id: $id, input: $input) {\n    success\n  }\n}\n```\n\n```json\n{\n  \"id\": \"34df5a09-498f-45c1-822c-6b1f80de5f8c\",\n  \"input\": {\n    \"key\": \"Action Key\",\n    \"value\": null\n  }\n}\n```\n</yakaai_skills>"}