Server events
A service with no pages, such as an API or an MCP server, has nowhere to put the tag. Its own server can send fivebar an event for each call instead.
It takes an API token made to send events, and a request to fivebar for each call, sent once your service has answered, so it never waits on fivebar. Each call counts as a pageview of its route or tool, or as a custom event, on the site’s dashboard, beside any pages its tag counts. A call is never taken for a bot’s, as your token vouches for it.
Make a token
In Settings, under API tokens, make a token for one site, under Only the ones you
choose, with Send events ticked and Read stats unticked. A token that only sends reads nothing,
so one that leaks from your service shows nobody your stats. Give it to your service in its environment, as
FIVEBAR_TOKEN, never in its code.
The site can be your site, its calls counted beside its pages, or a site of its own, such as
api.example.com, to keep them apart: add it on Sites first.
A token sends for a site only while you own it or are its editor. It stops within a minute of being revoked, or of you leaving the site, being removed or being made a viewer: see Sharing a site. A token you’ve just made can send from a minute later.
Send an event
For each call, send a POST to https://fiveb.ar/api/events, with the token in the
Authorization header, as Bearer and the token, and the event as a JSON object of up to
64 KiB. A basic event, its caller told apart by address and user agent, as a browser is:
{
"site": "example.com",
"name": "/v1/postcodes/:code",
"ip": "203.0.113.7",
"user_agent": "curl/8.7.1"
}An advanced one: your service’s account 4821, calling through Claude, with two properties of the call:
{
"site": "example.com",
"name": "/v1/postcodes/:code",
"props": { "status": 200, "format": "json" },
"ip": "203.0.113.7",
"id": "4821",
"client": "claude"
}| Field | Holds |
|---|---|
site | Required. The site’s domain, as the tag’s data-domain names it. |
name | Required. The route or tool called, as your code names it, such as /v1/postcodes/:code rather than the address called, or get_overview. Spaces at its ends are trimmed, anything from a ? or # is cut off, a name without a / first is given one, so get_overview is /get_overview, and it’s cut to 2,048 characters, as a page’s path is. |
event | Optional. A custom event’s name, to count the call as that event, on the page name gives, rather than as a pageview. It carries no data: send props instead. |
props | Optional. Custom properties for this call, as an object of names and values, such as {"status": 200}, read by the tag’s rules. A value is text or a number, and anything else is dropped, as is a name with capitals in it. Never put anything about the caller in one: that’s what id is for. |
ip | Optional. The caller’s IP address. |
user_agent | Optional. The caller’s user agent. |
id | Optional. Your service’s own word for the caller, as text, such as an account’s number or an MCP session’s id. |
client | Optional. The app that called, as text, such as MCP’s clientInfo.name. |
Any other field is ignored, and a call is counted at the time fivebar has it. An event needs ip or
id, as Who called says. fivebar answers
202, with no body, once it has the event, or with one of the Errors. No answer can be
read by a page on another site, so send events from your server alone.
Who called
Send ip, id or both: an event with neither is refused.
ipgives the caller’s country, city and network, as a visitor’s address does, and leaves the call out where the site excludes that address, network or country. An app such as Claude calls an MCP server from its own servers, so there it places the app’s servers, not the person using it.idtells your callers apart: an account’s number counts each account as a visitor, and an MCP session’s id each session.user_agent, besideip, tells callers apart where there’s noid, as a browser’s does. It names no browser.
Visitors are counted once a day each, as on any site, so over a range they’re visitor-days. Without
id, a caller is told apart by ip and user_agent, so everyone calling from one
address with one user agent, such as one of an app’s servers, is one visitor. On a site that counts its pages too,
someone using both can be two visitors a day, one by their browser and one by your id.
fivebar makes the visitor code from id as it makes a browser’s from its address and user agent,
one way, with the site and the day’s secret, and keeps none of the three: see Privacy.
What is counted
A call is a pageview of its name, in Top pages and the Content drilldown, and reaches any
page goal it matches. With event, it’s that
custom event instead, which, as on a page, makes no visit or visitor of its own, so
callers who send only events aren’t counted as visitors.
client is the call’s source, read as a link’s utm_source is: claude or
claude.ai is Claude, under AI Assistants, as are Claude’s MCP apps, which call themselves
claude-ai and claude-code. A name fivebar doesn’t know, such as cursor, is
kept as sent, under Referral. Without it, the source is Direct. See
Sources and campaigns.
A server event is never taken for a bot’s, whatever its user agent or network, as your token vouches for it. Traffic the site excludes is still left out.
A call has no browser, operating system, device, speed, time on page or scroll depth, so those panes leave it out. Calls make visits as pages do: a visit of calls alone lasts 0 seconds, and is a bounce if it’s one call.
Calls count towards your plan’s billable pageviews, as pages’ pageviews and events do: see Your trial.
Examples
See Server event examples for curl, Python, Node and an MCP server.
Errors
fivebar checks an event in this order, and the first that applies is the answer. An answer with a body is JSON.
| Status | Answer | When |
|---|---|---|
| 405 | { "error": "method not allowed" } | A request other than a POST.
Allow says POST. |
| 401 | { "error": "token required" } | No Authorization header. Its
WWW-Authenticate header asks for a token. |
| 401 | { "error": "invalid token" } | The header holds no token that can send:
it isn’t one, or the token is unknown, revoked or made without Send events, was made in the last minute, or
is for no site you own or edit. Its WWW-Authenticate header says so too. |
| 413 | { "error": "body too large" } | The body is over 64 KiB. |
| 400 | { "error": "malformed body" } | The body isn’t a JSON object. |
| 400 | { "error": "missing fields" } | site or name isn’t
text, or is empty once trimmed and cut. |
| 404 | { "error": "unknown site" } | The site isn’t on fivebar, or the token can’t send for it. Both are answered alike. |
| 400 | { "error": "bad event" } | event isn’t a name a custom event can
have, such as one starting with /: see Names. |
| 400 | { "error": "bad ip" } | ip isn’t one public IP address, such as a
range or a private address. |
| 400 | { "error": "missing caller" } | Neither ip nor
id. |
| 202 | No body | The site excludes the caller’s address, network or country, so the call is left out. |
| 503 | { "error": "no salt" } | The day’s secret for the site’s time zone hasn’t reached fivebar’s server yet. Nothing was counted, so send it again later. |
| 503 | { "error": "unavailable" } | The event couldn’t be kept just now. Nothing was counted, so send it again later. |
| 202 | No body | fivebar has the event. |
Check it works
Send an event with the curl example, with your site’s domain: it prints the status. A 202 means fivebar has it, and within a minute or two the call is in the site’s Top pages, under its name.