UsherStats Docs Contents usherstats.com Start free

Sites, hostnames and keys

A site is what UsherStats measures: a name, a time zone, the hostnames it is served on, and its keys.

  • The public key (pk_ and 26 characters) goes in the snippet. It is public by design; collect accepts it only from the site's hostnames.
  • A server secret (sk_live_ and 40 characters) authenticates the server SDK and proxy mode. It is shown once and stored hashed, like an API token.

Reading needs sites:read, changing needs sites:write. Another workspace's site, or one outside a token's site restriction, is 404. The Free plan has 5 sites, Starter 20, Growth and Business unlimited; past the limit, creating a site answers 402 naming the plan.

RouteScope
GET /v1/sitessites:read
POST /v1/sitessites:write (not for site-restricted tokens)
GET /v1/sites/{id}sites:read
PATCH /v1/sites/{id}sites:write
DELETE /v1/sites/{id}sites:write
POST /v1/sites/{id}/hostnamessites:write
GET /v1/sites/{id}/hostnames/{hostname}sites:read
POST /v1/sites/{id}/hostnames/{hostname}/verifysites:write
DELETE /v1/sites/{id}/hostnames/{hostname}sites:write
GET /v1/sites/{id}/keyssites:read
POST /v1/sites/{id}/keyssites:write
POST /v1/sites/{id}/keys/{keyId}/rotatesites:write
DELETE /v1/sites/{id}/keys/{keyId}sites:write
GET /v1/sites/{id}/snippetsites:read
GET /v1/sites/{id}/install-statussites:read
PATCH /v1/sites/{id}/settingssites:write

The examples assume $US_TOKEN holds a token with account:admin.

Create a site

curl https://api.usherstats.com/v1/sites \
  -H "Authorization: Bearer $US_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Field notes", "timezone": "Europe/Bucharest", "hostnames": ["fieldnotes.example", "www.fieldnotes.example"]}'

The answer holds the site and its first public key.

curl https://api.usherstats.com/v1/sites \
  -H "Authorization: Bearer $US_TOKEN"
curl https://api.usherstats.com/v1/sites/$SITE_ID \
  -H "Authorization: Bearer $US_TOKEN"
curl -X PATCH https://api.usherstats.com/v1/sites/$SITE_ID \
  -H "Authorization: Bearer $US_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Field Notes", "timezone": "America/New_York"}'

Install it

The snippet is a script tag with the public key. Serving it through your own domain (/_us/) keeps it working where third-party analytics hosts are blocked; the answer includes the steps.

curl https://api.usherstats.com/v1/sites/$SITE_ID/snippet \
  -H "Authorization: Bearer $US_TOKEN"
<script defer src="https://collect.usherstats.com/t.js" data-site="pk_…"></script>

Once a page with the snippet has been viewed, the install check says so (collect stamps the site at most once a minute):

curl https://api.usherstats.com/v1/sites/$SITE_ID/install-status \
  -H "Authorization: Bearer $US_TOKEN"

Hostnames

Bare hostnames only (no scheme, path or port); they are lower-cased. Up to 50 per site.

A hostname may be on sites of more than one workspace: collect only counts a page for a site whose key the page carries. Proxy mode is stricter: a proxied hostname belongs to exactly one site, and is switched on only after its ownership is verified (verifiedAt), through its Cloudflare custom hostname's validation or a DNS TXT record.

curl https://api.usherstats.com/v1/sites/$SITE_ID/hostnames \
  -H "Authorization: Bearer $US_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"hostname": "blog.fieldnotes.example"}'

Each hostname carries verification: the DNS record that proves the hostname is yours, a TXT record _usherstats.<hostname> with the value usherstats-verify=<token> (the token is this site's own for this hostname). Publish it at your DNS provider, then ask for the check. A verified hostname (verifiedAt set, verification null) is one the bot-protection challenge may send visitors back to, and one delegated Search Console access may read; an unverified one is still counted by collect. The check is limited to 30 an hour per site.

curl -X POST https://api.usherstats.com/v1/sites/$SITE_ID/hostnames/blog.fieldnotes.example/verify   -H "Authorization: Bearer $US_TOKEN"

The answer's verified says whether the record was found; when it was not, message names the record to publish. Proxy-mode hostnames need no TXT record of ours: Cloudflare's validation of the custom hostname verifies them.

One hostname: for a proxy-mode hostname, proxy is its live status at Cloudflare and the DNS records it still needs; for any other, proxy is null.

curl https://api.usherstats.com/v1/sites/$SITE_ID/hostnames/blog.fieldnotes.example \
  -H "Authorization: Bearer $US_TOKEN"

To serve a hostname through proxy mode, add it with "proxy": true and the origin its requests are forwarded to. The answer's proxy.records are the DNS records to set (a CNAME to the proxy; the TXT records let it go live before the DNS moves). Proxy hostnames count against the plan's allowance: paid plans are billed for each one past it, Free is refused with a 402; and a hostname another site already proxies is a 409.

{ "hostname": "shop.fieldnotes.example", "proxy": true, "origin": "https://origin.fieldnotes.example" }

Removing a proxy-mode hostname removes it at Cloudflare as well.

curl -X DELETE https://api.usherstats.com/v1/sites/$SITE_ID/hostnames/blog.fieldnotes.example \
  -H "Authorization: Bearer $US_TOKEN"

Keys

A server secret, for the server SDK or proxy mode. value is in this answer only:

curl https://api.usherstats.com/v1/sites/$SITE_ID/keys \
  -H "Authorization: Bearer $US_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"kind": "secret"}'

Listing shows public keys in full and secrets by prefix:

curl https://api.usherstats.com/v1/sites/$SITE_ID/keys \
  -H "Authorization: Bearer $US_TOKEN"

Rotating makes a new key of the same kind and ends the old one at once, so deploy the new secret right after:

curl -X POST https://api.usherstats.com/v1/sites/$SITE_ID/keys/$SECRET_ID/rotate \
  -H "Authorization: Bearer $US_TOKEN"
curl -X DELETE https://api.usherstats.com/v1/sites/$SITE_ID/keys/$NEW_SECRET_ID \
  -H "Authorization: Bearer $US_TOKEN"

Settings

Four lists; a PATCH replaces the ones it names and keeps the rest.

  • internalNetworks: CIDRs (IPv4 or IPv6) whose traffic is your own, counted apart from readers.
  • botWindows: time windows (from, to, optional country, network, note) whose traffic is filed as a bot fleet after the fact.
  • goals: conversions, by event name ("type": "event") or path ("type": "path").
  • experiments: A/B tests by key, with at least two variants and a status of draft, running or stopped.
curl -X PATCH https://api.usherstats.com/v1/sites/$SITE_ID/settings \
  -H "Authorization: Bearer $US_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "internalNetworks": ["203.0.113.0/24", "2001:db8::/32"],
    "botWindows": [{"from": "2026-09-30T03:00:00Z", "to": "2026-09-30T03:10:00Z", "country": "CN", "note": "one fleet"}],
    "goals": [{"name": "Newsletter", "type": "event", "match": "subscribe"}],
    "experiments": [{"key": "hero-copy", "variants": ["control", "short"], "status": "running"}]
  }'

A value that does not fit is 400 naming the field, such as internalNetworks[0]:

curl -X PATCH https://api.usherstats.com/v1/sites/$SITE_ID/settings \
  -H "Authorization: Bearer $US_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"internalNetworks": ["10.0.0.0/33"]}'

Delete a site

Its keys stop working at once.

curl -X DELETE https://api.usherstats.com/v1/sites/$SITE_ID \
  -H "Authorization: Bearer $US_TOKEN"
curl https://api.usherstats.com/v1/sites/$SITE_ID \
  -H "Authorization: Bearer $US_TOKEN"

View this page as Markdown