Guides
Set up with an AI agent
Give your agent a hostname, repository access, and permission to open a pull request or deploy. This workflow covers site registration, a single tracking tag, and proof that data reached your dashboard.
Machine-readable references: documentation index, full documentation, OpenAPI specification, and this guide as Markdown.
Copy a setup prompt
Replace the bracketed values. Supply the access token separately, following the authentication step below.
Install Totallytics in this project.
Hostname: [example.com]
Repository or workspace: [project location]
Deployment permission: [open a PR only / deploy after checks pass]
Replace an existing Simple Analytics install: [yes / no]
One clearly named verification event is permitted: [yes / no]
Read these first:
https://analytics.bitgate.dev/llms.txt
https://analytics.bitgate.dev/llms-full.txt
https://analytics.bitgate.dev/openapi.json
https://analytics.bitgate.dev/docs/agents.md
1. Inspect the actual app root, generated-page templates, existing analytics,
event calls, script settings, CSP, and any first-party analytics proxy.
Do not remove unrelated analytics or change the site's privacy settings.
2. Use TT_TOKEN from the execution environment for owner API calls. Check
GET /api/sites and reuse the exact owned hostname, or register it with
POST /api/sites. A 409 is not proof of ownership. Do not enable public
dashboards or invent service keys. If sign-in is missing, finish safe
repository work and report the account step as blocked.
3. Add https://analytics.bitgate.dev/latest.js once in each shared HTML
document/root, with data-hostname matching the registered hostname.
Keep production collection out of local/preview builds. Update all
relevant entrypoints and generator templates, not only the homepage.
4. Preserve supported settings and event calls. Do not combine automatic
pageviews with extra route tracking. If replacing Simple Analytics,
swap its core tag and optional pixel rather than adding a second
default tag. Audit auto-events helpers and proxy destinations separately.
5. Merge the analytics origin into script-src, connect-src and img-src;
also update script-src-elem when present. Preserve the existing policy.
6. Run the project's build/checks. Deploy only with the permission above;
otherwise open a PR and leave production verification explicitly pending.
7. On the deployed registered hostname, verify a real browser's pageview
request and the matching stored data. If permitted, send one unique
verification event and poll authenticated breakdown reads for its name.
Do not repeatedly send events, fake a non-bot result, or treat HTTP 200
as proof of ingestion. Report a missing real-browser check as a blocker.
8. Report hostname/ownership, changed files, build result, PR or deployment,
request destination, stored-data evidence and any unfinished steps.
Treat historical migration as a separate task, not a result of adding a tag.
If history is requested, read /docs/imports and the migration guide first.
Confirm a non-overlapping UTC range, source credentials and existing data
before creating a job. Verify stored counts, not just its completed status.1. Sign in and confirm the site
Sign in to Totallytics, reopen Authentication, and click Copy access token. Pass that short-lived Firebase user ID token to the agent's server-side environment as TT_TOKEN. Copy a fresh token if it expires. There is no permanent service-key setup to substitute for user sign-in.
The token is for site management and private stats reads, not the tracking tag. Keep it out of source files, browser bundles, and public environment variables such as VITE_* or NEXT_PUBLIC_*.
Choose the exact hostname before editing. Use example.com, not https://example.com/path or a hostname with a port. Registration trims whitespace and lowercases, but does not remove www. To group www.example.com into example.com, register example.com and use that value in data-hostname. Register subdomains separately when you want separate reporting.
These terminal examples use Bash, curl, and jq:
EA_ORIGIN='https://analytics.bitgate.dev'
EA_HOSTNAME='example.com'
: "${TT_TOKEN:?Copy an access token from the authentication guide first}"
curl --fail-with-body --silent --show-error --retry 2 --max-time 30 \
"$EA_ORIGIN/api/sites" \
--header "Authorization: Bearer $TT_TOKEN"Look for an exact match in the returned sites array. This list is owner-scoped; a publicly readable dashboard is not proof of ownership. If the hostname is absent, create it:
curl --fail-with-body --silent --show-error --max-time 30 \
"$EA_ORIGIN/api/sites" \
--header "Authorization: Bearer $TT_TOKEN" \
--header 'Content-Type: application/json' \
--data "$(jq -n --arg hostname "$EA_HOSTNAME" '{hostname: $hostname}')"A successful create returns 201 with {"hostname":"example.com","verified_at":null,"verify_token":"tt-verify-…"}. On 409, re-read the owned list and proceed only if the hostname is now there. After an uncertain network failure, read before retrying the create. Resolve ownership conflicts rather than choosing a different hostname that your tag will never send.
Ownership must be verified before the site collects data or can be made public. The collector discards traffic for unverified sites. If you control the site's DNS or web root, complete verification now: publish the returned verify_token as a TXT record at _totallytics-verify.{hostname} or as the body of https://{hostname}/.well-known/totallytics-verify.txt, then call POST /api/sites/{hostname}/verify. If you cannot publish either proof from this environment, leave the site unverified and report verification as a pending manual step. See Site management — verify ownership.
Unauthorized private reads and mutations return a uniform 404 unknown site whether the hostname is unregistered or owned by someone else. See Site management for limits and settings. Do not make a dashboard public to bypass an authentication problem.
2. Install once in the shared document
Use the registered hostname in the tag:
<script
async
data-hostname="example.com"
src="https://analytics.bitgate.dev/latest.js"
></script>Put it in the shared document or root layout, not a component that remounts on navigation.
- Static sites and Vite: inspect every HTML entrypoint, including separate marketing pages. Update generators as well as generated output.
- React and SSR frameworks: use the persistent document/root layout, such as Next.js
app/layout.tsxor Remixapp/root.tsx. Preserve attributes when using a framework script component. Avoid inserting the tag on every render or effect. - Client-side routing: the tag already tracks distinct paths on
pushStateandpopstate. Hash routing needsdata-mode="hash". It does not directly listen toreplaceState, and query-only changes are not new page paths. - Manual pageviews:
window.sa_pageview()exists only withdata-auto-collect="false". Wait for load, then handle both initial and subsequent views yourself. Repeated consecutive calls for the same path are suppressed. See Installation.
If you need a JavaScript-disabled fallback, put this optional pixel in the server-rendered body:
<noscript>
<img
src="https://analytics.bitgate.dev/noscript.gif?hostname=example.com"
alt=""
referrerpolicy="no-referrer-when-downgrade"
/>
</noscript>The pixel does not inherit data-hostname; its query parameter sets the hostname separately. The page path normally comes from the request's Referer. A stricter referrer policy can remove that information; supply the required values from the server-rendered template or omit the fallback. JSX spells the attribute referrerPolicy. A client-only component cannot provide a useful no-JavaScript fallback.
Keep the production tag out of local and preview builds, or use a separate registered staging hostname. The current script can send from localhost, especially with a hostname override. Local requests are not proof that the deployed site works and can contaminate production counts.
3. Update CSP without replacing it
Merge the analytics origin into the site's existing directives:
script-src 'self' https://analytics.bitgate.dev;
connect-src 'self' https://analytics.bitgate.dev;
img-src 'self' https://analytics.bitgate.dev;These are policy fragments, not a replacement for your whole CSP. If script-src-elem is defined separately, update that too. Preserve existing nonces, hashes, and other sources; adding 'unsafe-inline' is not required for the external tag.
script-src covers /latest.js. img-src covers pageviews and events sent through /simple.gif, fallback append requests, and /noscript.gif. connect-src is required for navigator.sendBeacon() to /append, which carries duration and scroll updates. Inspect policies set by the app, server, CDN, and HTML, not just one config file.
If replacing Simple Analytics or using a proxy, follow the migration guide. Two default core tags share sa_loaded and sa_event; whichever loads first can prevent the other from running.
4. Verify stored data, not just a request
Run the project's checks and deploy only when authorized. Open a real route on the registered deployed site in a normal browser, then check:
/latest.jsreturns JavaScript, and the document contains one intended core tag./simple.gifsendstype=pageview, the registeredhostname, and the expectedpath.- Navigation produces one pageview per intended distinct path. Preserve the network log and leave the page to inspect
/appendor an append fallback. - The corresponding page row increases in the dashboard or
dim=pagesbreakdown, within the recorded test window.
Check CSP errors, blockers, and Do Not Track when a request is missing. Headless automation and bot user agents can be excluded from normal stats, so an automated network check alone cannot prove visible ingestion. Do not spoof a human visit to make a test pass.
The collector acknowledges before background storage finishes. A 200, an event callback, a healthy /healthz, or a nonzero live count does not prove your test was stored.
Confirm an identifiable test event
If the owner permits one test event, capture a timestamp and print a unique event call:
EA_FROM="$(date -u +%s)"
EA_CHECK="ea_setup_${EA_FROM}"
printf 'window.sa_event("%s")\n' "$EA_CHECK"After the tracking script loads, run the printed call once in that site's real-browser console. It leaves a labelled test event in the site's data. Then query from the terminal:
set -o pipefail
curl --fail-with-body --silent --show-error --retry 2 --max-time 30 \
--get "$EA_ORIGIN/api/sites/$EA_HOSTNAME/breakdown" \
--header "Authorization: Bearer $TT_TOKEN" \
--data-urlencode 'dim=events' \
--data-urlencode "from=$EA_FROM" \
--data-urlencode "to=$(($(date -u +%s) + 1))" \
--data-urlencode 'limit=100' \
| jq -e --arg name "$EA_CHECK" \
'.rows[] | select(.name == $name and .value > 0)'The matching {name, value, visitors} row is evidence of receipt. If it is absent, retry the read, not the event, for a bounded period such as one minute. The breakdown returns at most the top 100 names; keep the test window narrow on busy sites. If you cannot establish receipt, report ingestion as unverified and use Troubleshooting. See Stats for range semantics.
5. Hand back an exact outcome
Report the registered hostname and ownership check, changed files and root placement, CSP changes, build result, PR or deployment URL, and the actual request and stored-row evidence with its time window.
If sign-in, deployment permission, or a real-browser check is missing, say code ready, ingestion unverified and name the remaining step. Do not claim historical data was migrated: historical backfill is a separate workflow.