API reference
Every endpoint in the CountJar API, what it needs and what it returns. All URLs start with https://countjar.vercel.app/api/v2.
Basics
- Addresses. A counter's URL is
/api/v2/<workspace>/<counter>. Actions go on the end, like/upor/stats. - Methods. Counting actions accept
GETorPOST, so a plain link, an image tag or afetchcall all work. Reading endpoints areGETonly. - Parameters. Send them in the query string, as a form body, or as a JSON object with
Content-Type: application/json. - Auth. Public counters can be read and counted up without a token. Everything else needs one; see when you need a token.
- Responses. Always JSON, never cached (
Cache-Control: no-store). Errors use one shape with a stablecode. - Browsers. The API sends
Access-Control-Allow-Origin: *, so you can call it from any website. It never reads cookies. - Times. Timestamps are ISO 8601 in UTC, for example
2026-10-14T09:12:44Z.
The counter object
Most endpoints return the counter like this:
{
"workspace": "acme",
"name": "visits",
"type": "count",
"visibility": "public",
"description": "Homepage views",
"count": 1285,
"created_at": "2026-10-01T08:00:00Z",
"updated_at": "2026-10-14T09:12:44Z"
}
Poll counters add yes, no and yes_pct. Rating counters add ratings (how many people rated) and average (to two decimal places). For polls and ratings, count is the number of votes or ratings.
Counting actions also return "counted": true or false. It's false when nothing changed: the visitor was already counted, or the counter is at its minimum or maximum.
Read a counter
GET/api/v2/<workspace>/<counter>
Returns the counter object. Private counters need a read token.
| Parameter | Description |
|---|---|
window | Optional. Also return activity in a recent window: 15m, 6h, 7d and so on, up to 90d. |
curl "https://countjar.vercel.app/api/v2/acme/visits?window=24h"
{
"workspace": "acme",
"name": "visits",
"count": 1285,
"window": { "range": "24h", "count": 112, "events": 112, "unique": 87 },
...
}
In window, count is the net change in that window, events is how many changes there were, and unique is how many different visitors made them.
Count up
GETPOST/api/v2/<workspace>/<counter>/up
Adds the counter's step size (1 unless you changed it). Anyone can count up a public counter. Private counters need a write token.
| Parameter | Description |
|---|---|
by | Optional. Add this amount instead of the step size, from 1 to 1,000,000. Needs a write token, even on public counters. |
curl -X POST https://countjar.vercel.app/api/v2/acme/visits/up
If the counter has a maximum, it stops there and further calls return "counted": false.
Count down
GETPOST/api/v2/<workspace>/<counter>/down
Subtracts the step size, or by. Always needs a write token. Stops at the counter's minimum if it has one; counters without a minimum can go negative.
curl -H "Authorization: Bearer cj_your_token" \
"https://countjar.vercel.app/api/v2/acme/seats-left/down?by=2"
Set a value
GETPOST/api/v2/<workspace>/<counter>/set
Sets the counter to an exact number. Needs a write token.
| Parameter | Description |
|---|---|
value | Required. A whole number within the counter's minimum and maximum. |
curl -H "Authorization: Bearer cj_your_token" \
"https://countjar.vercel.app/api/v2/acme/stock/set?value=42"
A value outside the counter's range returns 400 with the code out_of_bounds.
Reset
GETPOST/api/v2/<workspace>/<counter>/reset
Puts the counter back to 0, or to its minimum if that's above 0. Polls and ratings go back to no votes. Needs a write token.
Reset also forgets who has been counted, so every visitor can count, like, vote or rate again. History, stats and events are kept.
Like
GETPOST/api/v2/<workspace>/<counter>/like
For like counters. Adds one like, once per visitor. A second like from the same visitor returns "counted": false. Like counters also accept up, down, set and reset, with the same rules as normal counters.
Vote
GETPOST/api/v2/<workspace>/<counter>/vote
For poll counters. One vote per visitor.
| Parameter | Description |
|---|---|
choice | Required. yes or no. |
curl -X POST "https://countjar.vercel.app/api/v2/acme/helpful/vote?choice=yes"
{ "name": "helpful", "type": "poll", "count": 41, "yes": 33, "no": 8, "yes_pct": 80.5, "counted": true, ... }
Rate
GETPOST/api/v2/<workspace>/<counter>/rate
For rating counters. One rating per visitor.
| Parameter | Description |
|---|---|
value | Required. Stars from 1 to 5. |
{ "name": "docs-rating", "type": "rating", "count": 18, "ratings": 18, "average": 4.39, "counted": true, ... }
Unique visitors
Set a counter's unique option to stop the same visitor from counting up again and again:
| Option | Effect on up |
|---|---|
none | Every call counts. This is the default. |
daily | Each visitor counts once per day (UTC). |
once | Each visitor counts once, ever, until the counter is reset. |
A visitor is recognised by a keyed fingerprint of their IP address and browser. Likes, votes and ratings are always one per visitor, whatever this option says.
none for server-side counters.Custom properties
Any extra query parameter on a counting action is saved with that event, so you can tell where counts came from later:
curl "https://countjar.vercel.app/api/v2/acme/downloads/up?version=2.1&platform=mac"
Names can use letters, digits and underscores, up to 32 characters. Values are kept as text, up to 200 characters, and up to 10 properties are saved per event. These names are reserved and never saved: token, by, value, choice, window, format, range and limit.
Filter events by a property with ?filter=platform=mac.
Visitor details
Every counting action also records who made it, for the counter's Visitors panel and Events log:
- Location: country, region, city, postal code, approximate coordinates and time zone, looked up from the IP address.
- Device: desktop, phone, tablet or bot, the model where the browser reports it, the browser and the operating system.
- Language: the visitor's preferred language.
- IP address: stored with the event, and shown only to the workspace owner.
The widget also sends the page address and screen size. To send them yourself, add _page (a full https:// address) and _screen (for example 1440x900):
fetch(`https://countjar.vercel.app/api/v2/api/v2/acme/visits/up?_page=${encodeURIComponent(location.href)}&_screen=${screen.width}x${screen.height}`)
Stats
GET/api/v2/<workspace>/<counter>/stats
A summary of recent activity. Private counters need a read token.
{
"workspace": "acme",
"name": "visits",
"count": 1285,
"total_ups": 1290,
"total_downs": 5,
"today": 64,
"last_24h": 112,
"last_7d": 803,
"unique_24h": 87,
"unique_7d": 512,
"events_24h": 112,
"created_at": "2026-10-01T08:00:00Z",
"updated_at": "2026-10-14T09:12:44Z"
}
today starts at 00:00 UTC.
History
GET/api/v2/<workspace>/<counter>/history
A time series for charts, with empty periods filled in as zero. Private counters need a read token.
| Parameter | Description |
|---|---|
range | 24h (hourly, the default), 7d, 30d or 90d (daily). |
format | csv to download a spreadsheet instead of JSON. |
{
"workspace": "acme",
"name": "visits",
"range": "7d",
"series": [
{ "t": "2026-10-08T00:00:00Z", "ups": 98, "downs": 0, "net": 98, "events": 98 },
{ "t": "2026-10-09T00:00:00Z", "ups": 121, "downs": 2, "net": 119, "events": 123 },
...
]
}
Events
GET/api/v2/<workspace>/<counter>/events
The individual changes, newest first. Needs a read token, even for public counters, because events include visitor details, referrers and custom properties.
| Parameter | Description |
|---|---|
limit | How many to return, from 1 to 1,000. Defaults to 100. |
filter | Only events with this custom property, written name=value. Also works with country, city, device, browser and os, for example country=IN. |
format | csv to download a spreadsheet instead of JSON. |
{
"workspace": "acme",
"name": "downloads",
"events": [
{
"ts": "2026-10-14T09:12:44Z",
"action": "up",
"delta": 1,
"value": null,
"visitor": "3f9a1c07be52",
"referrer": "github.com",
"props": { "version": "2.1", "platform": "mac" },
"device": "phone",
"model": "Pixel 8",
"browser": "Chrome 141",
"os": "Android 14",
"country": "IN",
"region": "KA",
"city": "Bengaluru",
"postal": "560001",
"lat": 12.972,
"lon": 77.594,
"tz": "Asia/Kolkata",
"lang": "en-IN",
"page": "https://example.com/blog/hello",
"screen": "412x915"
}
]
}
visitor is a shortened fingerprint, useful for spotting repeat visitors. referrer is only the host name of the page the request came from. The visitor details are null when unknown. IP addresses are left out of the API; the workspace owner sees them in the dashboard and its exports.
Workspace summary
GET/api/v2/<workspace>
Totals and every counter in a workspace. Without a token you only see public counters; with a read token you see them all.
{
"workspace": "acme",
"total": 4210,
"active_24h": 3,
"events_24h": 288,
"unique_24h": 140,
"top": [ { "name": "visits", "count": 1285 }, ... ],
"counters": [ { "name": "visits", "count": 1285, ... }, ... ]
}
total adds up counter and like counters only, since polls and ratings measure something different.
Create a counter
POST/api/v2/<workspace>/counters
Needs an admin token. Send a JSON object:
| Parameter | Description |
|---|---|
name | Required. 1 to 64 lowercase letters, digits, - or _. Unique in the workspace. |
type | count (default), like, poll or rating. Can't be changed later. |
visibility | public (default) or private. |
description | Up to 200 characters, shown in the dashboard. |
step | How much up and down change the count, from 1 to 1,000,000. Defaults to 1. |
min, max | Optional limits. Counting up stops at max and counting down stops at min. |
unique | none (default), daily or once. See unique visitors. |
start | The starting count. Defaults to 0 and must be within min and max. |
curl -X POST https://countjar.vercel.app/api/v2/acme/counters \
-H "Authorization: Bearer cj_your_admin_token" \
-H "Content-Type: application/json" \
-d '{"name": "seats-left", "start": 50, "min": 0, "max": 50, "visibility": "private"}'
Returns 201 with the counter object. You get 409 if the name is taken and 403 with the code counter_limit if you've used all 5 counters.
Change a counter
PATCH/api/v2/<workspace>/<counter>
Needs an admin token. Send only the fields you want to change: visibility, description, step, min, max or unique. Send null for min or max to remove the limit. Changing a limit doesn't move the current count, so if it's now out of range, set it.
curl -X PATCH https://countjar.vercel.app/api/v2/acme/visits \
-H "Authorization: Bearer cj_your_admin_token" \
-H "Content-Type: application/json" \
-d '{"unique": "daily"}'
Delete a counter
DELETE/api/v2/<workspace>/<counter>
Needs an admin token. Deletes the counter with all its history, events and milestones, and frees the slot for a new one. Returns 204 with no body.