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:countersscope — 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 >= 400and return an error early rather than silently dropping events.
Where to next
- Using Counters in Node.js — Node.js example
- Using Counters in Python — Python example
- Counter API Keys — create and scope API keys
- Dashboards & Analytics — visualise the data you are sending
Found an issue in the docs? Open an issue on GitHub.