Skip to content
CountJar
Docs Pricing The counter API for developers A hit counter for any website A view counter badge for your GitHub README An embeddable like button A yes/no poll you can embed anywhere Five-star ratings for any page Start free

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 /up or /stats.
  • Methods. Counting actions accept GET or POST, so a plain link, an image tag or a fetch call all work. Reading endpoints are GET only.
  • 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 stable code.
  • 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.

ParameterDescription
windowOptional. 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.

ParameterDescription
byOptional. 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.

ParameterDescription
valueRequired. 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.

ParameterDescription
choiceRequired. 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.

ParameterDescription
valueRequired. 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:

OptionEffect on up
noneEvery call counts. This is the default.
dailyEach visitor counts once per day (UTC).
onceEach 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.

If you count from your own server, every request comes from the same IP, so unique counting would treat all your users as one visitor. Use 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}`)
Badges in a GitHub README are fetched by GitHub's image proxy, not by the reader's browser, so those visits show up as a bot with GitHub's location. They still count.

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.

ParameterDescription
range24h (hourly, the default), 7d, 30d or 90d (daily).
formatcsv 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.

ParameterDescription
limitHow many to return, from 1 to 1,000. Defaults to 100.
filterOnly events with this custom property, written name=value. Also works with country, city, device, browser and os, for example country=IN.
formatcsv 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:

ParameterDescription
nameRequired. 1 to 64 lowercase letters, digits, - or _. Unique in the workspace.
typecount (default), like, poll or rating. Can't be changed later.
visibilitypublic (default) or private.
descriptionUp to 200 characters, shown in the dashboard.
stepHow much up and down change the count, from 1 to 1,000,000. Defaults to 1.
min, maxOptional limits. Counting up stops at max and counting down stops at min.
uniquenone (default), daily or once. See unique visitors.
startThe 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.

Deleting can't be undone. If you only want to start from zero, use reset instead.