API Documentation

Welcome! This is the documentation for the NeatStats API, which lets you pull your own site’s view statistics programmatically, and send it new views and updoots.

Authentication

All requests require authentication with an API key, which you can find on your account page. You can use the key with an Authorization: Bearer <token> header (where the token is your API key), or as the password in an HTTP Basic authentication request (the username can be anything, as long as the password is your API key).

Every request is scoped to a single site, specified in the URL path. Your API key must belong to the account that owns that site (you can only retrieve stats for, and send data to, sites you own).

The API supports cross-origin requests (CORS), so it's safe to call directly from browser-based JavaScript.

Common parameters

Most endpoints accept a range parameter, and several also accept a limit parameter. There’s also a sort parameter that can be used where it makes sense. Here’s how these work:

Errors

When something goes wrong, you'll get a non-200 http_response_code along with an error field describing what happened:

{
	"http_response_code": 400,
	"http_response_text": "Bad Request",
	"error": "Invalid range. Must be one of: 24h, 7d, 30d, 6m, 1y."
}

Endpoints

The API currently offers endpoints in a single category: Sites.

Sites

Site endpoints support retrieving stats for your sites (and sending them new views and updoots), identified by the site domain in the URL path.

Get site info

GET /api/v1/sites/site/info

Fetches basic info about a site, like its verification status and a lifetime summary of its views.

Sample response

{
	"http_response_code": 200,
	"http_response_text": "OK",
	"site": {
		"domain": "example.com",
		"verified": true,
		"verified_at": "2026-01-19 03:12:07 UTC",
		"verified_by": "dns",
		"summary": {
			"view_count": 303908,
			"first_view_at": 1768863455,
			"last_view_at": 1788524730,
			"updoot_count": 42,
			"executive_summary": "Your site has a total of 303,908 views since 2026-01-19. The most recent view was 9 minutes ago. It’s received 42 total Updoots."
		}
	}
}

Get page views

GET /api/v1/sites/site/views

Fetches a time-series of view counts, bucketed automatically based on the requested range (15-minute buckets for 24h, hourly for 7d, every six hours for 30d, daily for 6m, and weekly for 1y). Also returns an SVG line chart of the same data. And the total number of views recorded for the requested range.

Accepts: range

Sample response

{
	"http_response_code": 200,
	"http_response_text": "OK",
	"site": "example.com",
	"range": "24h",
	"bucket_seconds": 900,
	"start": 1788490800,
	"end": 1788577200,
	"total_views": 13,
	"data": [
		{ "ts": 1788490800, "views": 4 },
		{ "ts": 1788491700, "views": 7 },
		{ "ts": 1788492600, "views": 2 }
	],
	"chart_svg": "…"
}

Get browser breakdown

GET /api/v1/sites/site/browsers

Fetches a breakdown of views by browser, along with an SVG donut chart of the same data.

Accepts: range, limit

Sample response

{
	"http_response_code": 200,
	"http_response_text": "OK",
	"site": "example.com",
	"range": "7d",
	"data": {
		"Chrome": 412,
		"Safari": 298,
		"Firefox": 54,
		"Edge": 22,
		"Yandex": 9
	},
	"chart_svg": "…"
}

Get platform breakdown

GET /api/v1/sites/site/platforms

Fetches a breakdown of views by platform (operating system), along with an SVG donut chart of the same data.

Accepts: range, limit

Sample response

{
	"http_response_code": 200,
	"http_response_text": "OK",
	"site": "example.com",
	"range": "7d",
	"data": {
		"Macintosh": 301,
		"Windows": 187,
		"Android": 122,
		"iPhone": 98,
		"Linux": 41
	},
	"chart_svg": "…"
}

Get top pages

GET /api/v1/sites/site/pages

Fetches the most viewed pages on your site, ranked by view count.

Accepts: range, limit

Sample response

{
	"http_response_code": 200,
	"http_response_text": "OK",
	"site": "example.com",
	"range": "7d",
	"total_paths": 47,
	"data": {
		"/": 1204,
		"/about": 389,
		"/blog/hello-world": 201
	}
}

Get top referrers

GET /api/v1/sites/site/referrers

Fetches the sites and pages that sent you the most traffic, ranked by view count.

Accepts: range, limit

Sample response

{
	"http_response_code": 200,
	"http_response_text": "OK",
	"site": "example.com",
	"range": "7d",
	"total_referrers": 132,
	"data": {
		"https://google.com/": 1204,
		"https://reddit.com/": 897,
		"https://neatnik.net/": 213
	}
}

Get updoots

GET /api/v1/sites/site/updoots

Fetches every page on your site whose updoots have been clicked, with each page’s total and its most recent activity.

Accepts: sort (count, the default, for most updoots first; recent for latest activity first; or page for alphabetical by path)

Sample response

{
	"http_response_code": 200,
	"http_response_text": "OK",
	"site": "example.com",
	"sort": "count",
	"total_pages": 2,
	"total_updoots": 61,
	"data": [
		{
			"path": "/blog/hello",
			"url": "https://example.com/blog/hello",
			"count": 55,
			"last_activity": 1794690000,
			"updoots": [
				{ "key": "⭐️", "emoji": "⭐️", "count": 50, "last_activity": 1794680000 },
				{ "key": "❤️", "emoji": "❤️", "count": 5, "last_activity": 1794690000 }
			]
		},
		{
			"path": "/about",
			"url": "https://example.com/about",
			"count": 6,
			"last_activity": 1794600000,
			"updoots": [
				{ "key": "⭐️", "emoji": "⭐️", "count": 6, "last_activity": 1794600000 }
			]
		}
	]
}

Get map data

GET /api/v1/sites/site/map

Fetches per-location view counts, with coordinates suitable for plotting on a map.

Accepts: range, limit

Sample response

{
	"http_response_code": 200,
	"http_response_text": "OK",
	"site": "example.com",
	"range": "24h",
	"total_locations": 62,
	"data": [
		{
			"geonameid": 5128581,
			"name": "New York City",
			"country_iso": "US",
			"latitude": 40.71427,
			"longitude": -74.00597,
			"views": 214
		},
		{
			"geonameid": 2643743,
			"name": "London",
			"country_iso": "GB",
			"latitude": 51.50853,
			"longitude": -0.12574,
			"views": 88
		}
	]
}

Get country totals

GET /api/v1/sites/site/countries

Fetches view totals grouped by country, using ISO 3166-1 alpha-2 country codes.

Accepts: range, limit

Sample response

{
	"http_response_code": 200,
	"http_response_text": "OK",
	"site": "example.com",
	"range": "24h",
	"total_countries": 18,
	"data": {
		"US": 512,
		"GB": 88,
		"DE": 41
	}
}

Sending data

You can also send data to NeatStats, so you can count things that aren’t page views in a browser, like downloads, app launches, form signups, or anything else you can make an HTTP request from. These requests need your API key (signing in on the website isn’t enough).

Send the request body as JSON (with a Content-Type: application/json header) or as regular form fields. Paths are always on the site in the URL, and query strings and #fragments are removed. Instead of a path, you can send a full url on your site.

Record views

POST /api/v1/sites/site/views

Records one or more views, just like the view counter would. Views show up in your stats within a few seconds, timestamped when we receive them.

Accepts:

To record several views at once (up to 100), send {"views": [ … ]} with one object per view.

Sample request

curl https://neatstats.net/api/v1/sites/example.com/views \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d "path=/downloads/app.zip"

Sample response

{
	"http_response_code": 202,
	"http_response_text": "Accepted",
	"site": "example.com",
	"recorded": 1,
	"message": "Thanks! The view will show up in a few seconds."
}

Add updoots

POST /api/v1/sites/site/updoots

Adds to an updoot’s count, creating the updoot if it doesn’t exist yet. Updoots make handy counters for anything you like.

Accepts: path (required), emoji (defaults to ⭐️), key (defaults to the emoji), and count (how many to add, from 1 to 1,000,000; defaults to 1)

Sample request

curl https://neatstats.net/api/v1/sites/example.com/updoots \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d "path=/blog/hello" -d "emoji=🎉" -d "count=5"

Sample response

{
	"http_response_code": 200,
	"http_response_text": "OK",
	"site": "example.com",
	"path": "/blog/hello",
	"url": "https://example.com/blog/hello",
	"updoot": {
		"key": "🎉",
		"emoji": "🎉",
		"count": 12,
		"last_activity": 1794700000
	}
}

Set an updoot count

PUT /api/v1/sites/site/updoots

Sets an updoot’s count to an exact number, creating the updoot if it doesn’t exist yet. It’s great for importing counts from somewhere else, or for fixing a count.

Accepts: path (required), count (required, from 0 to 1,000,000,000), emoji, and key

The response is the same as for Add updoots. An updoot with a count of 0 isn’t shown on your page until it’s clicked again.

Delete an updoot

DELETE /api/v1/sites/site/updoots

Deletes an updoot and its count. This can’t be undone.

Accepts: path (required), and emoji or key (at least one is required)

Sample response

{
	"http_response_code": 200,
	"http_response_text": "OK",
	"site": "example.com",
	"path": "/blog/hello",
	"key": "🎉",
	"deleted": true
}

You've reached the end of our API documentation.

We hope this was helpful. If you have questions, or there's an endpoint you'd find useful that isn't here yet, please let us know.