Getting started
Updated on: April 2, 2026
Introduction
Page Views API is an open-source, serverless solution to track how many times a specific page has been visited – without any dashboards, accounts, or complex setup.
It provides two things:
- A tracking script you drop into any website to count visits
- A REST API to read those counts and display them anywhere
The model is Precision Tracking – you explicitly tell the script exactly which path to track. This means no accidental tracking of every route and no noisy data.
Usage
Add the Tracking Script
Drop a single <script> tag into any HTML page. That's it.
<script
src="https://page-views-api.ratneshc.com/script"
data-site="example.com"
data-path="/blog/my-post"
defer
></script>Script Attributes
| Attribute | Required | Description |
|---|---|---|
src | Yes | Always https://page-views-api.ratneshc.com/script. |
data-site | Yes | Your unique site identifier. Use your domain (e.g. example.com). Protocol and trailing slashes are stripped automatically. |
data-path | Yes | The specific page path to track (e.g. /, /blog/my-post). The script only fires when the current URL matches this value. |
data-debug | No | Set to "true" to enable console logging. Useful during local development and integration testing. |
The script uses strict path matching. It compares data-path against the
current window.location.pathname. If they don't match, no request is made.
This gives you full control over what gets counted.
How the script works
When a page loads, the script:
- Reads
data-siteanddata-pathfrom its own<script>tag - Normalizes the current URL path (strips trailing slashes, collapses double slashes)
- Compares it to
data-path– if they don't match it exits silently - If they match, it calls
GET /api/v1/track?site=…&path=…usingfetchwithkeepalive: true - The API deduplicates the visit and increments the counter
SPA & Client-side Navigation
The script is fully compatible with Single Page Applications (Next.js, React, Vue, etc.) where the browser never fully reloads on navigation.
It hooks into the browser's history API to detect route changes:
history.pushState– intercepted so the script fires on every client-side navigationpopstate– listened to handle browser Back / Forward buttons
Each navigation triggers
track(), which re-checks the path match. The counter only increments when the new URL matchesdata-path.
Using data-debug
<script
src="https://page-views-api.ratneshc.com/script"
data-site="example.com"
data-path="/"
data-debug="true"
defer
></script>With data-debug="true", you'll see logs like:
[PV] Tracking visit: /
[PV] Path mismatch, skipping: { current: "/about", target: "/" }
The global window.__PV__ object is also available for debugging:
| Property | Description |
|---|---|
window.__PV__.status | Current state: loading, ready, missing-script-tag, missing-parameters, or error |
window.__PV__.track | The tracking function – call manually to trigger a track |
Fetch View Counts
Once views are being tracked, you can read the count for any path using the views endpoint.
Endpoint
GET https://page-views-api.ratneshc.com/api/v1/views?site=example.com&path=/Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
site | string | Yes | Your site identifier (same value used in data-site). |
path | string | Yes | The page path to fetch view count for. |
Response
{
"views": 1234
}Examples
const res = await fetch(
"https://page-views-api.ratneshc.com/api/v1/views?site=example.com&path=/"
);
const data = await res.json();
// { views: 1234 }
console.log(`Total views: ${data.views}`);How It Works
Here's what happens end-to-end when a visitor lands on a tracked page:
- The browser loads your
<script>tag and executes the tracking script - The script normalizes the path and checks it matches
data-path - A
GET /api/v1/trackrequest is made withsiteandpath - The API generates a short-lived visitor ID – a 16-character SHA-256 hash derived from the visitor's IP address and User-Agent string
- The visitor ID is checked against a Redis deduplication key for that
site+path - If the visitor is new within the window, the counter is incremented and the key is set with a TTL
- If the visitor already exists in the window, the request is acknowledged but the counter is not incremented
Deduplication & Privacy
To keep view counts accurate and respect visitor privacy:
- Deduplication window: A visitor is counted once every 30 minutes per
site+pathcombination - No IP storage: IP addresses are never stored. They are combined with the User-Agent and immediately hashed (SHA-256), producing a short-lived token that expires with the TTL
- No cookies: The script sets no cookies and reads no local storage
- No personal data: Nothing that can identify an individual is persisted
API Input Normalization
Both endpoints silently normalize the site and path values before processing, so minor formatting inconsistencies don't create duplicate counters.
site normalization
| Input | Normalized |
|---|---|
https://example.com | example.com |
http://example.com/ | example.com |
EXAMPLE.COM | example.com |
path normalization
| Input | Normalized |
|---|---|
/blog/ | /blog |
blog | /blog |
//blog//post | /blog/post |
| (empty) | / |
Rate Limiting
To protect the API from abuse:
- Limit: 60 requests per 60-second window
- Scope: Per IP address, scoped to the
site+pathbeing tracked - Response on limit exceeded:
429 Too Many Requests
If you receive a 429, wait 60 seconds before retrying. The window resets
automatically.
Error Handling
Both endpoints return standard HTTP status codes and a consistent JSON error body.
Error Response Format
{
"error": "Missing or empty required query parameter: site"
}Status Codes
| Status | Meaning | When it happens |
|---|---|---|
200 | OK | Request succeeded. |
400 | Bad Request | A required query parameter (site or path) is missing or empty. |
429 | Too Many Requests | Rate limit exceeded for this IP + site + path. |
500 | Internal Server Error | Unexpected server-side error. |
CORS
Both endpoints support cross-origin requests out of the box.
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
You can call the views endpoint directly from a browser, a React component, or any client-side code without needing a proxy or server-side wrapper.