TapTallyTapTally
HomeUser GuideDeploymentArchitectureSourcesAdvanced
GitHub

Using Counters in Go

Send counter increments, decrements, and reads from a Go application using the TapTally HTTP API.

Overview

Every counter operation in TapTally is a plain HTTP request. This page shows how to fire increments, decrements, and reads from Go — no SDK required, only the standard library.

Prerequisites

  • A TapTally instance with a known base URL (e.g. http://localhost:8080)
  • An API key with at least write:counters scope — see Counter API Keys for how to create one
  • A counter that already exists (namespace + key pair) — see Counters

API at a glance

POST /v1/counters/{namespace}/{key}/incr
POST /v1/counters/{namespace}/{key}/decr
GET  /v1/counters/{namespace}/{key}

The request body for increment/decrement is optional. Omit it to step by 1, or pass { "by": N } to step by any integer:

{ "by": 5 }

Every response returns a counter object:

{
  "namespace": "blog",
  "key":       "post-views",
  "value":     42,
  "updated_at": "2025-07-07T10:30:00Z"
}

The X-API-Key header is required on every authenticated request.


Example

Uses only the standard library — net/http and encoding/json.

package taptally

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
)

type Counter struct {
	Namespace string `json:"namespace"`
	Key       string `json:"key"`
	Value     int64  `json:"value"`
	UpdatedAt string `json:"updated_at"`
}

type Client struct {
	BaseURL string
	APIKey  string
	http    *http.Client
}

func NewClient(baseURL, apiKey string) *Client {
	return &Client{BaseURL: baseURL, APIKey: apiKey, http: &http.Client{}}
}

func (c *Client) Increment(namespace, key string, by int) (*Counter, error) {
	return c.mutate("incr", namespace, key, by)
}

func (c *Client) Decrement(namespace, key string, by int) (*Counter, error) {
	return c.mutate("decr", namespace, key, by)
}

func (c *Client) Get(namespace, key string) (*Counter, error) {
	url := fmt.Sprintf("%s/v1/counters/%s/%s", c.BaseURL, namespace, key)
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("X-API-Key", c.APIKey)
	return c.do(req)
}

func (c *Client) mutate(op, namespace, key string, by int) (*Counter, error) {
	url := fmt.Sprintf("%s/v1/counters/%s/%s/%s", c.BaseURL, namespace, key, op)
	body, _ := json.Marshal(map[string]int{"by": by})
	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("X-API-Key", c.APIKey)
	req.Header.Set("Content-Type", "application/json")
	return c.do(req)
}

func (c *Client) do(req *http.Request) (*Counter, error) {
	resp, err := c.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		return nil, fmt.Errorf("taptally: %s %s → %d", req.Method, req.URL.Path, resp.StatusCode)
	}
	var counter Counter
	return &counter, json.NewDecoder(resp.Body).Decode(&counter)
}
// Example usage
client := taptally.NewClient("http://localhost:8080", os.Getenv("TAPTALLY_API_KEY"))
counter, err := client.Increment("blog", "post-views", 1)
if err != nil {
    log.Fatal(err)
}
fmt.Println("New value:", counter.Value)

Error reference

Status Meaning
401 Missing or invalid API key
403 Key exists but lacks write:counters scope, or the key is scoped to a different counter
404 Counter (namespace / key pair) does not exist — create it first via the UI or the API
400 Malformed request body

Inspect resp.StatusCode >= 400 and return an error early rather than silently dropping events.


Where to next


Found an issue in the docs? Open an issue on GitHub.

TapTally — You tap, we tally. Free & self-hosted · GPLv3

GitHubDocker HubSupport