A logo of an ABACUS

Abacus

Counting should be easy, right?

Abacus

This API allows you to create simple numeric counters. IaaS, Integer as a Service.

It goes down to:

All counters are “unlisted”—accessible if you know the key but not publicly listed anywhere.

Want to track the number of hits a page had? Sure.
Want to know the number of users that clicked on the button “Press me”? There you go.

So far, ... keys have been created and there have been ... requests served. ... of those have been hits and ... have been gets. Abacus is currently on version ....
This page has been visited ... times.

TL;DR

Each counter is identified inside a namespace with a key.
The namespace should be unique, so its recommend using your site's domain.
Inside each namespace you can generate all the counters you may need.

The hit endpoint provides increment by one counters directly. Each time its requested the counter will increase by one:

https://abacus.jasoncameron.dev/hit/namespace/key⇒ 200 { "value": 1234 }

Want to start counting right away? Check the examples below!

Example

Let’s say you want to display the amount of page views a site received.

<div id="visits">...</div>
Remember to change  .

Using JSONP

<script> function cb(response) { document.getElementById('visits').innerText = response.value; } </script> <script async src="https://abacus.jasoncameron.dev/hit/mysite.com/visits?callback=cb"></script>

Using XHR

var xhr = new XMLHttpRequest(); xhr.open("GET", "https://abacus.jasoncameron.dev/hit/mysite.com/visits"); xhr.responseType = "json"; xhr.onload = function() { document.getElementById('visits').innerText = this.response.value; } xhr.send();

Using jQuery

$.getJSON("https://abacus.jasoncameron.dev/hit/mysite.com/visits", function(response) { $("#visits").text(response.value); });

Multiple pages

If you want to have a counter for each individual page you can replace visits with a unique identifier for each page, i.e. index, contact, item-1234. Check the right format a key must have.

Alternatively, you can use some reserved words that are replaced server-side.
For example, if a request is made from https://mysite.com/example/page:


Note: Reserved words are left padded with dots if their length is less than three.

So you could use something like:

https://abacus.jasoncameron.dev/hit/mysite.com/:PATH:

Or even more generic (though not recommended):

https://abacus.jasoncameron.dev/hit/:HOST:/:PATH:

Counting events

You can use the API to count any kind of stuff, let’s say:

<button onclick="clicked()">Press Me!</button> <script> function clicked() { let xhr = new XMLHttpRequest(); xhr.open("GET", "https://abacus.jasoncameron.dev/hit/mysite.com/awesomeclick"); xhr.responseType = "json"; xhr.onload = function() { alert(`This button has been clicked ${this.response.value} times!`); } xhr.send(); } </script>

FAQ

Where is the API documentation?

Scroll a bit further.

Is the API free?

Completely free.

Rate Limiting?

General Rate Limit

This means that each unique IP address is allowed to make a maximum of 30 requests every 3 seconds. Exceeding this limit will temporarily block further requests from that IP until your limit is up.

If you require a higher rate limit for legitimate use cases, please contact me at [email protected].

Rate Limit Headers

The API provides informative headers in responses to help you track your usage:

Rate Limit Exceeded

When you exceed the rate limit, the API will respond with a 429 Too Many Requests status code response similar to:
{
    "error": "Too many requests. Try again in 2s"
}

Can I delete a key?

If you originally created the key using the /create endpoint, then yes, you can delete the key and all data associated with it by using the /delete endpoint along with your admin key.

Will I blow up the Abacus server?

Abacus is using Valkey as a database, a rapid key-value solution.
If you are planning to make tens of thousands of requests, I'll be glad if you email me letting me know.

Issues/Contact

If you have issues, suggestions or just want to contact me, shoot me an email or create a GitHub issue.

API

Authentication & Rate Limiting

Endpoints requiring administrative actions (delete, set, reset, update) need an admin key passed in the `Authorization` header as a Bearer token. You get this admin key when creating a counter via /create.

Rate limiting is in place to ensure fair usage: 30 requests per IP address every 3 seconds.

Namespaces

Namespaces are used to avoid name collisions. You can specify a namespace when creating a key. It's recommended to use the domain of your app as the namespace to prevent conflicts with other websites.
If you don't specify a namespace, the key is assigned to the default namespace. You don't need to specify the `default` namespace in your requests.

Endpoints

All requests support cross-origin resource sharing (CORS) and SSL.

Base API path: https://abacus.jasoncameron.dev

In case of a server failure, the API will send:

⇒ 500 { "error": "Error description" }

/healthcheck

Check the health and uptime of the API.

GET /healthcheck
⇒ 200 { "status": "ok", "uptime": "1h23m45s" }

/docs

Redirects to the API documentation.

/get/:namespace/*key

Retrieve the current value of a counter. Optionally specify the namespace.

If you want to use JSONP, please pass in the callback via the ?callback query param (e.g. ?callback=myjsfunction) 
GET /get/test
⇒ 200 { "value": 42 }
GET /get/nonexisting
⇒ 404 { "error": "Key not found" }

/hit/:namespace/*key

Increment a counter by 1 and return the new value. If the counter doesn't exist, it will be created. Optionally specify a namespace

If you want to use JSONP, please pass in the callback via the ?callback query param (e.g. ?callback=myjsfunction) 
GET /hit/mysite.com/visits (value was 35)
⇒ 200 { "value": 36 }
GET /hit/nonexisting (key is created)
⇒ 200 { "value": 1 }

/stream/:namespace/*key

Stream updates to a counter's value using Server-Sent Events (SSE). This means you get updated right as a key is updated instead of having to poll. Optionally specify a namespace.

GET /stream/mysite.com/visits
⇒ data: {"value": 36}

/create/:namespace/*key

Create a new counter with an optional initial value (default 0). Specify both namespace and key.

Note about admin_key: this is the only time you will be able to see it, if you lose the key then you lose access to control the counter. 
Note about expiration: Every time a key is accessed its expiration is set to 6 months. So don't worry, if you still using it, it won't expire.
Keys and namespaces must have at least 3 characters and less or equal to 64. Keys and namespaces must match: ^[A-Za-z0-9_-.]{3,64}$

GET /create/myapp/newcounter?initializer=10
⇒ 201 {"key": "newcounter", "namespace": "myapp", "admin_key": "YOUR_ADMIN_KEY", "value": 10}
GET /create/myapp/alreadyexists
⇒ 409 { "error": "Key already exists, please use a different key." }

/create/

Create a new counter with a random namespace and key. This endpoint does not take any parameters.

GET /create
⇒ 201 {"key": "randomkey", "namespace": "randomnamespace", "admin_key": "YOUR_ADMIN_KEY", "value": 0}

/info/:namespace/*key

Get detailed information about a counter, including its value, key, expiration, etc. Optionally specify the namespace.

GET /info/existing
⇒ 200 {
    "value": 42,           // Current counter value
    "full_key": "K:default:existing", // The full DB key (K:namespace:key)
    "is_genuine": true,   // Indicates if the counter was created with an admin key (false) or not (true)
    "expires_in": 172800, // Time to live (TTL) in seconds
    "expires_str": "2d",   // TTL in a human-readable format
    "exists": true        // Whether the key exists in the DB
}
GET /info/nonexisting
⇒ 404 {
    "value": -1,
    "full_key": "default:nonexisting",
    "is_genuine": true,
    "expires_in": -2e-9,
    "expires_str": "-2ns", // When the server received the request compared to when it output a response.
    "exists": false
}

/delete/:namespace/*key (Requires Admin Key)

Delete a counter. Specify both namespace and key. Include the admin key in the `Authorization` header.

POST /delete/myapp/mycounter
Authorization: Bearer YOUR_ADMIN_KEY
⇒ 200 { "status": "ok", "message": "Deleted key: myapp:mycounter" }

/set/:namespace/*key?value=:value (Requires Admin Key)

Set the value of a counter, overwriting the existing value. Specify both namespace and key, and provide the `value` query parameter. Include the admin key in the `Authorization` header.

POST /set/myapp/mycounter?value=15
Authorization: Bearer YOUR_ADMIN_KEY
⇒ 200 { "value": 15 }
POST /set/myapp/nonexisting?value=15
Authorization: Bearer YOUR_ADMIN_KEY
⇒ 404 { "error": "Key does not exist, please use a different key." }

/reset/:namespace/*key (Requires Admin Key)

Reset a counter to 0. Specify both namespace and key. Include the admin key in the `Authorization` header.

POST /reset/myapp/mycounter
Authorization: Bearer YOUR_ADMIN_KEY
⇒ 200 { "value": 0 }
POST /reset/myapp/nonexisting
Authorization: Bearer YOUR_ADMIN_KEY
⇒ 404 { "error": "Key doesnot exist, please use a different key." }

/update/:namespace/*key?value=:amount (Requires Admin Key)

Increment or decrement a counter by the specified amount. Specify both namespace and key, and provide the value query parameter (positive to increment, negative to decrement). Include the admin key in the Authorization header.

POST /update/myapp/mycounter?value=5
Authorization: Bearer YOUR_ADMIN_KEY
⇒ 200 { "value": 20 } // Assuming the previous value was 15
POST /update/myapp/nonexisting?value=-3
Authorization: Bearer YOUR_ADMIN_KEY
⇒ 404 { "error": "Key does not exist, please first create it using /create." }

/stats

Gives some info about the server and database. The "commands" stats are updated every 30s per shard

GET /stats/
⇒ 200 {
  "commands": {
    "create": 20394, // total number of /create's
    "get": 403232,   // total number of /get's
    "hit": 703232,   // total number of /hit's
    "total": 2003058 // total number of requests served
  },
  "db_uptime": "5558", // database uptime in seconds
  "db_version": "6.0.16", // database version
  "expired_keys__since_restart": "130", // number of keys expired since db's last restart
  "key_misses__since_restart": "205", // number of keys not found since db's last restart
  "total_keys": 87904, // total number of keys created
  "version": "1.3.3", // Abacus's version
  "shard": "boujee-coorgi", // Handler shard
  "uptime": "1h23m45s" // shard uptime

}