Guides
Troubleshooting
Resolve common integration, script delivery, and API authorization issues.
Diagnostic checklist
If pageviews or events do not appear in your dashboard, check these common causes:
1. Verify hostname registration
The collector normally ignores unregistered hostnames, even when it returns HTTP 200. Register the hostname before testing. Collector instances can cache a missing hostname for about a minute.
Verify that the hostname configured in your script matches the hostname in /app:
- Hostnames are case-insensitive and trimmed, but
www.example.comandexample.comare treated as distinct domains. - To combine apex and
wwwtraffic, set the same explicitly registereddata-hostnameon both versions of your site. Without that setting, the script useslocation.host.
2. Verify script source
Ensure your script loads from https://analytics.bitgate.dev/latest.js. Stale scripts cached from third-party mirrors or legacy Simple Analytics endpoints will not reach your Totallytics collector.
3. Duplicate script guards
The default tracker sets window.sa_loaded = true when executed. If your page loads a second copy of the script, or retains an old analytics tag, the secondary script halts immediately. Remove duplicate tracking tags.
4. Ad blockers, DNT, and automated browsers
- Do Not Track: The script checks
navigator.doNotTrack. By default,"1"stops normal collection. - Browser extensions: Content blockers may intercept
latest.jsorsimple.gif. - Headless automation: The script marks
navigator.webdriver = truetraffic as automated; matching user-agents are also classified as bots. Bot rows do not appear in normal visitor and pageview totals.
Always test using a real, unblocked browser profile.
5. Content Security Policy (CSP)
Check your browser console for CSP violation notices. Ensure https://analytics.bitgate.dev is included in script-src, img-src, and connect-src directives. If you use script-src-elem, add the origin there as well.
6. Processing delays
An HTTP 200 response from the collector signals successful receipt, not instantaneous persistence. Aggregated dashboard metrics update asynchronously. Check GET /api/sites/<hostname>/breakdown?dim=pages across an active date range rather than expecting zero-latency counters.
HTTP status codes
400 Bad Request
- Collector (
/events): Returns plain text errors such asinvalid JSON,hostname is required,event name is required, orunsupported type. A non-POST request also returns400, withPOST required. - API (
/api/*): Check the response’serrorfield. Invalid ranges, invalid hostnames on registration, and unknown breakdown dimensions return400; unknown query keys are generally ignored. Send valid JSON for all writes: an unreadable PATCH body is currently treated as no change rather than rejected. Statistics ranges are at most 400 days and use Unix seconds, not milliseconds. Import requests instead use date-only strings and their own range rules. Use one of the 10 supported dimensions. Supply a whole-number breakdownlimit.
401 Unauthorized
The request lacks a valid Firebase ID token in the Authorization: Bearer <TOKEN> header. Refresh your token in /docs/authentication.
403 Forbidden
The token is valid, but the account does not own the requested private site.
404 Not Found
The requested site or import job was not found. Verify the exact hostname and job ID.
409 Conflict
Returned when attempting to add a hostname via POST /api/sites that is already claimed. Check your site list via GET /api/sites before attempting registration. Imports also return 409 for an already-active import on that site or an invalid cancel/retry transition; see Imports.
429 Too Many Requests
A user may create at most 20 new import jobs in a rolling 24-hour window. Wait before creating more; do not submit duplicate jobs to retry existing work.
500 Internal Server Error
An unexpected server-side error occurred. Retry safe read requests with a short, bounded backoff. Do not blindly repeat event POSTs or site mutations after an ambiguous failure; inspect the current state first.
Operational behavior
- Rate limiting: Statistics and collection have no documented request quota or rate-limit headers. Historical imports have the job limit above. This is not an unlimited-throughput guarantee. Bound concurrency and handle upstream/network errors.
- Health check: The
/healthzendpoint confirms worker process responsiveness. It does not check database connectivity or collector readiness. - Overview vs Breakdown rollups: Overview totals read hourly rollups; its series uses hourly buckets for ranges up to four days and daily buckets for longer ranges, with the requested
tz. Breakdown filters raw rows by the supplied timestamps. Refer to the Stats Reference for timestamp alignment details. - Site capacity: Each account can register a maximum of 50 sites.
- Deleted sites: Deletion removes the site registration, not its historical rows. Collection and visibility changes may take several minutes to propagate through cached site lookups. See Sites.
- Discrepancies with Simple Analytics: Totallytics uses unique daily visitor hashes. Calculated visitor counts will not match Simple Analytics figures exactly. To import historical records, consult the Migration Guide.
Import completed with zero rows
Check the source hostname, including www, the selected UTC dates, and whether Simple Analytics returns data for that range. Inspect chunks_done, chunks_total, and rows_skipped; a completed status alone is not proof that expected history arrived.
Keep your Simple Analytics account and exports until actual report counts are verified. Record the job ID, requested range, and source hostname if the result is unexpected. Do not start repeated overlapping jobs as a diagnostic step: new jobs and interrupted-chunk retries can duplicate records.