Counter API

A small counter API. Built to be dependable.

Create counters, read their current values, and keep a durable record of each change.

Scoped keys for tenant accessIdempotent changes safe to retryOperation history records each mutation

API reference

POST

Create Tenant

POST {base_url}/tenants

Request headers

KeyValueDescription
X-API-Key{{ADMIN_API_KEY}}Administrator key required.
Content-Typeapplication/jsonRequest body is JSON.

Request body

KeyValueDescription
label"production"Required and unique. 1–255 characters; letters, numbers, spaces, hyphens, underscores, and periods.

Response headers

KeyValueDescription
Content-Typeapplication/jsonJSON response body.

Sample request

curl --silent --show-error --request POST "$BASE_URL/tenants" --header "X-API-Key: $ADMIN_API_KEY" --header "Content-Type: application/json" --data-raw '{"label":"production"}'

Sample response

{
  "tenant_id": "01912345-6789-7000-8000-000000000001",
  "label": "production",
  "created_at": "2026-09-25T08:00:00Z",
  "updated_at": "2026-09-25T08:00:00Z"
}

Creates a new tenant with a unique label. Tenants act as isolated workspaces that contain counters.

Authentication: Requires API key via X-Api-Key header

Rate Limit: 10 requests per minute (configurable via RATE_LIMIT_REQUESTS)

Errors

  • 400 INVALID_JSON - Invalid JSON in request body
  • 400 INVALID_PARAMETER - Label validation failed
  • 409 TENANT_LABEL_EXISTS - Label already in use
  • 401 UNAUTHORIZED - Missing or invalid API key
  • 429 RATE_LIMIT_EXCEEDED - Too many requests
POST

Create Credential

POST {base_url}/v2/tenants/{tenant_id}/credentials

Request headers

KeyValueDescription
X-API-Key{{ADMIN_API_KEY}}Administrator key required to manage credentials.
Content-Typeapplication/jsonRequest body is JSON.

Request body

KeyValueDescription
scopes["counter:read", "counter:increment", "counter:history"]Optional; supports tenant:read, counter:read, counter:list, counter:create, counter:increment, counter:adjust, and counter:history. Defaults to read, increment, and history; listing is not granted by default.
expires_at"2026-12-31T23:59:59Z"Optional expiration timestamp; omitted for a non-expiring credential.

Response headers

KeyValueDescription
Content-Typeapplication/jsonJSON response body.

Sample request

curl --silent --show-error --request POST "$BASE_URL/v2/tenants/$TENANT_ID/credentials" --header "X-API-Key: $ADMIN_API_KEY" --header "Content-Type: application/json" --data-raw '{"scopes":["counter:read","counter:increment","counter:history"]}'

Sample response

{
  "credential_id": "01912345-6789-7000-8000-000000000010",
  "tenant_id": "01912345-6789-7000-8000-000000000001",
  "api_key": "ck_01912345-6789-7000-8000-000000000010.example-secret",
  "scopes": [
    "counter:read",
    "counter:increment",
    "counter:history"
  ]
}

Creates a tenant-scoped key. Requires the administrator API key. The secret is returned once; store it securely. Supported scopes: tenant:read, counter:read, counter:list, counter:create, counter:increment, counter:adjust, and counter:history. By default, keys receive read, increment, and history scopes; listing, counter creation, and adjustment must be granted explicitly.

POST

Create Counter

POST {base_url}/tenants/{tenant_id}/counters

Request headers

KeyValueDescription
X-API-Key{{TENANT_API_KEY}}Administrator key or tenant key with counter:create.
Content-Typeapplication/jsonRequest body is JSON.

Request body

KeyValueDescription
label"daily_steps"Optional; unique within this tenant, 1–255 characters.
initial_value0Optional int64; defaults to 0.
max_delta50Optional positive int64; defaults to 50 and must be at least 1.

Response headers

KeyValueDescription
Content-Typeapplication/jsonJSON response body.

Sample request

curl --silent --show-error --request POST "$BASE_URL/tenants/$TENANT_ID/counters" --header "X-API-Key: $TENANT_API_KEY" --header "Content-Type: application/json" --data-raw '{"label":"daily_steps","initial_value":0,"max_delta":50}'

Sample response

{
  "counter_id": "01912345-6789-7000-8000-000000000002",
  "tenant_id": "01912345-6789-7000-8000-000000000001",
  "label": "daily_steps",
  "value": 0,
  "max_delta": 50,
  "created_at": "2026-09-25T08:00:00Z",
  "updated_at": "2026-09-25T08:00:00Z"
}

Creates a new counter under the specified tenant.

Authentication: Requires an administrator key or tenant-scoped key with

the counter:create scope.

Rate Limit: 10 requests per minute (configurable via RATE_LIMIT_REQUESTS)

Errors

  • 400 INVALID_JSON - Invalid JSON in request body
  • 400 INVALID_UUID - Invalid tenant_id format
  • 400 INVALID_PARAMETER - Parameter validation failed
  • 404 TENANT_NOT_FOUND - Tenant does not exist
  • 409 COUNTER_LABEL_EXISTS - Label already exists for this tenant
  • 401 UNAUTHORIZED - Missing or invalid API key
  • 429 RATE_LIMIT_EXCEEDED - Too many requests
GET

Get Counter

GET {base_url}/v2/tenants/{tenant_id}/counters/{counter_id}

Request headers

KeyValueDescription
X-API-Key{{TENANT_API_KEY}}Tenant credentials require counter:read; administrator keys are also accepted.

Response headers

KeyValueDescription
Content-Typeapplication/jsonJSON response body.

Sample request

curl --silent --show-error --request GET "$BASE_URL/v2/tenants/$TENANT_ID/counters/$COUNTER_ID" --header "X-API-Key: $TENANT_API_KEY"

Sample response

{
  "counter_id": "01912345-6789-7000-8000-000000000002",
  "tenant_id": "01912345-6789-7000-8000-000000000001",
  "label": "daily_steps",
  "value": 8421,
  "max_delta": 100000,
  "created_at": "2026-09-25T08:00:00Z",
  "updated_at": "2026-09-25T12:01:00Z"
}

Reads the current counter value using a tenant-scoped credential with the

see the deployment guide for cache configuration and consistency behavior.

POST

Increment Counter

POST {base_url}/v2/tenants/{tenant_id}/counters/{counter_id}/inc?delta=5

Request headers

KeyValueDescription
X-API-Key{{TENANT_API_KEY}}Requires counter:increment.
Idempotency-Key{{UUID_IDEMPOTENCY_KEY}}Required UUID; reuse only for retries of this same mutation.

Query parameters

KeyValueDescription
delta5Optional positive int64 increment; defaults to 1 and cannot exceed max_delta.

Response headers

KeyValueDescription
Content-Typeapplication/jsonJSON response body.

Sample request

curl --silent --show-error --request POST "$BASE_URL/v2/tenants/$TENANT_ID/counters/$COUNTER_ID/inc?delta=5" --header "X-API-Key: $TENANT_API_KEY" --header "Idempotency-Key: $IDEMPOTENCY_KEY"

Sample response

{
  "operation_id": "01912345-6789-7000-8000-000000000003",
  "counter_id": "01912345-6789-7000-8000-000000000002",
  "delta": 5,
  "value": 47,
  "replayed": false,
  "updated_at": "2026-09-25T12:01:00Z"
}

Applies a durable increment. Use a fresh UUID idempotency key for each

logical mutation and reuse that same key on transport retries. Requires a

tenant-scoped credential with counter:increment.

The counter update and operation-history record commit together in

POST

Set Counter

POST {base_url}/v2/tenants/{tenant_id}/counters/{counter_id}/set

Request headers

KeyValueDescription
X-API-Key{{TENANT_API_KEY}}Requires the counter:adjust scope.
Idempotency-Key{{UUID_IDEMPOTENCY_KEY}}Required UUID; reuse only for retries of this same request.
Content-Typeapplication/jsonRequest body is JSON.

Request body

KeyValueDescription
value100Required int64 value to set.

Response headers

KeyValueDescription
Content-Typeapplication/jsonJSON response body.

Sample request

curl --silent --show-error --request POST "$BASE_URL/v2/tenants/$TENANT_ID/counters/$COUNTER_ID/set" --header "X-API-Key: $TENANT_API_KEY" --header "Idempotency-Key: $IDEMPOTENCY_KEY" --header "Content-Type: application/json" --data-raw '{"value":100}'

Sample response

{
  "operation_id": "01912345-6789-7000-8000-000000000004",
  "counter_id": "01912345-6789-7000-8000-000000000002",
  "delta": 53,
  "value": 100,
  "replayed": false,
  "updated_at": "2026-09-25T12:03:00Z"
}

Sets a counter to a value and records the adjustment in operation history.

Requires counter:adjust and a UUID Idempotency-Key. Reuse the key only

when retrying the same logical request.

GET

Operation History

GET {base_url}/v2/tenants/{tenant_id}/counters/{counter_id}/operations?limit=50

Request headers

KeyValueDescription
X-API-Key{{TENANT_API_KEY}}Requires the counter:history scope.

Query parameters

KeyValueDescription
limit50Optional integer from 1–100; defaults to 50.
cursor{{CURSOR}}Optional opaque cursor; pass next_cursor unchanged.

Response headers

KeyValueDescription
Content-Typeapplication/jsonJSON response body.

Sample request

curl --silent --show-error --request GET "$BASE_URL/v2/tenants/$TENANT_ID/counters/$COUNTER_ID/operations?limit=50" --header "X-API-Key: $TENANT_API_KEY"

Sample response

{
  "operations": [
    {
      "operation_id": "01912345-6789-7000-8000-000000000003",
      "counter_id": "01912345-6789-7000-8000-000000000002",
      "kind": "increment",
      "delta": 5,
      "value_before": 42,
      "value_after": 47,
      "metadata": {},
      "created_at": "2026-09-25T12:01:00Z",
      "completed_at": "2026-09-25T12:01:00Z"
    }
  ],
  "next_cursor": null
}

Returns paginated, reverse-chronological counter operations. Requires a

tenant-scoped credential with counter:history.

Pass the opaque next_cursor from a response unchanged to fetch the next