Documentation

Everything you need to measure your site the privacy-friendly way.

Overview

frapi.io is privacy-friendly web analytics: pageviews, visitors, referrers, browsers, operating systems, screen sizes and languages — with no cookies, no fingerprinting and no personal data. There is no consent banner to install and nothing to configure except your site code.

Everything is per-site, keyed by a public site code. You can track dozens of sites from one account and see them all on a single overview page.

Quick start

Sign up to create your account — a site code is generated for you from the site name. Then add this snippet to every page you want to track, just before </body>:

snippet.html
<script data-frapi="https://frapi.io/frapi"
        data-site="YOUR-SITE-CODE"
        data-frapi-fallback="https://frapi.io/count"
        async src="https://frapi.io/frapi.js"></script>

Replace YOUR-SITE-CODE with the code shown in your dashboard header. Open the dashboard and pageviews start appearing within seconds.

The site code is public. It identifies your site in tracking URLs — that's all. It is not a secret, and it is not an API credential.
Ad blockers. Some blockers drop requests to obvious tracker paths, so the snippet points at /frapi and falls back to /count if the first is blocked. If you see no pageviews, check your blocker and allow frapi.io.

How data gets in

frapi accepts hits through two independent paths. Both write to the same store, use the same rollups, and show up in the same dashboard — use whichever fits your product.

Browser snippetServer-side API
PurposePageviews & events from visitorsHits from your own code
AuthNone — public site codeBearer API token (secret)
How<script> tag in pagesPOST /api/v0/count
Best forPublic websites & frontendsLogs, cron, SSR, backfills, events

1. Browser snippet

Your visitors' browsers call the tracker directly. No authentication is involved and no personal data is transmitted.

snippet.html
<script data-frapi="https://frapi.io/frapi"
        data-site="your-code"
        data-frapi-fallback="https://frapi.io/count"
        async src="https://frapi.io/frapi.js"></script>

The snippet reports automatically on page load, prefers fetch with keepalive (falling back to sendBeacon and then a 1×1 image), and retries the secondary endpoint if the first request is blocked.

2. Server-side API

Send hits from your backend — great for server-side events, log importers, cron jobs and backfills. Authenticate with an API token created in Dashboard → Settings → API tokens.

POST /api/v0/count
curl https://frapi.io/api/v0/count \
  -X POST \
  -H 'Authorization: Bearer frapi_…' \
  -H 'Content-Type: application/json' \
  -d '{"hits":[{"path":"/checkout","title":"Checkout","event":false}]}'

→ {"counted": 1}
Never put an API token in a page. It is a secret credential. Anything you can do from browser JavaScript is covered by the public snippet.

Snippet options

Configure the snippet with attributes on the script tag, or at runtime via the window.frapi object.

AttributePurpose
data-frapiTracker endpoint (defaults to https://frapi.io/frapi)
data-frapi-fallbackSecond endpoint tried if the first is blocked (recommended: https://frapi.io/count)
data-siteYour site code (required)
data-frapi-settingsJSON object of options, applied at load
Options (window.frapi or data-frapi-settings)
{
  "no_onload": false,     // don't count on page load
  "no_events": true,      // don't auto-bind click handlers
  "allow_local": false,   // count hits from localhost / private IPs
  "allow_frame": false,   // count hits rendered in frames
  "path":  "/override",   // string or function
  "title": "My title",    // string or function  (defaults to document.title)
  "referrer": "…",        // string or function  (defaults to document.referrer)
  "event": false          // mark every count as an event
}

Set options via the attribute:

<script data-frapi="https://frapi.io/frapi"
        data-site="your-code"
        data-frapi-fallback="https://frapi.io/count"
        data-frapi-settings='{"no_onload":true}'
        async src="https://frapi.io/frapi.js"></script>

Or in your own code before the page fires the onload hit:

<script>window.frapi = { no_onload: true }</script>

Events

Track a click or any custom action with a data attribute — no JS required:

<a href="/signup" data-frapi-click="signup-click">Sign up</a>

Or call the JavaScript API directly:

frapi.count({ path: 'log:button', event: true })

Events are counted separately and appear with an event flag in your stats and exports.

Custom paths

Override the reported path before a hit is sent — useful for staging URLs or renaming pages:

<script>
  window.frapi = {
    path: function () { return '/custom-' + location.pathname }
  }
</script>

Opt-out & filters

Let your own team opt a browser out by visiting any page with #toggle-frapi in the URL. A confirmation is shown and the choice is stored in that browser only.

The snippet also skips hits that would be useless: prerendered pages, pages inside frames (unless allow_frame is on), localhost and file:// pages (unless allow_local is on), and browser automation flags like navigator.webdriver. Server-side, a bot heuristic filters known crawler user-agents from your counts.

HTTP API

All API calls are JSON, versioned under /api/v0, and authenticated with a bearer token. Create one in Dashboard → Settings → API tokens — the raw token is shown only once, so store it somewhere safe.

Everything below
curl https://frapi.io/api/v0/me \
  -H 'Authorization: Bearer frapi_…'

Endpoints

EndpointDescription
POST /countIngest a batch of hits
GET /meToken info + the site it belongs to
GET /sitesList of all your sites
GET /stats/totalTotal pageviews & visitors for a range
GET /stats/hitsTop pages for a range
GET /stats/{page}Top refs, browsers, systems, sizes, locations, languages
GET /pathsCatalog of every tracked path
GET /exportRaw hits as CSV

POST /count — ingest hits

Accepts a batch. Each hit becomes a pageview (or event).

{
  "hits": [
    { "path": "/welcome", "title": "Welcome", "event": false, "referrer": "" }
  ],
  "no_sessions": true
}

Returns {"counted": 1} with the number of hits accepted.

Backend hits are trusted. They are never bot-filtered and the caller's own User-Agent is ignored. Browsers and operating systems show as (unknown) unless you pass a visitor's user_agent per hit (useful when proxying real requests server-side). API hits don't count as visitors.

GET /stats/total

Query params: start, end (ISO dates or relative dates like 2006-01-02). Returns pageviews and visitors for the range.

GET /stats/{page}

page is one of refs (aliases referrers), browsers, systems, sizes, locations, languages. Optional limit (max 100).

GET /export — CSV

Dumps raw hits for a range as CSV with columns id, path, title, referrer, first_visit, width, language, location, created_at. Requires raw hit storage, which is on by default.

Visitor counter

Show a live "views" badge for a page. It's a public, cache-friendly image:

<img src="https://frapi.io/counter/read-me.svg?site=your-code" alt="Reads">

The path in the URL (here /read-me) is the page path to count. Use .html instead of .svg for a text-only badge. Optional start and end query params limit the date range.

Data & privacy

We never store: IP addresses, full user-agent strings, cookies, device IDs, fingerprint data, or any identifier that can be traced back to a person.

What we store per hit: page path, title, referrer, screen width, language and the browser/OS classifier. Visitor counts are derived from a salted hash of short-lived inputs that rotates daily — the raw inputs are discarded, so the hash can't be reversed.

Aggregation: hits roll up hourly and daily. The dashboard reads those rollups, so day-to-day usage stays fast even at scale.

Retention & deletion: raw hits and rollups are retained for the life of your account. Deleting a site (or your account) removes its data. Export via the API or CSV first if you want a copy.

FAQ

What's the difference between a site code and an API token?

A site code is public and identifies your site in tracking URLs and dashboard paths. An API token is secret and authenticates your backend to the JSON API. They authorize different things — see How data gets in.

Can I change my site code?

Your site code is baked into your installed snippets, so changing it would break tracking. Create a new site if you need a new code, and export the old data first.

Do you offer self-hosting?

No — frapi.io is a hosted service. That's how we keep the tracker one line and the pricing simple.

← Back to homepage