Skip to content
howmuchuserswtf
Sign in
← Docs

Full reference

Everything there is. Three routes, no SDK — what follows is the exact contract the code already keeps, not a summary of it.

Authentication

The key goes in the header, either of two ways:

Authorization: Bearer hmu_sk_live_9f3ac1d2_...X-HMU-Key: hmu_sk_live_9f3ac1d2_...

The second exists for environments where a proxy already uses "Authorization" for something else.

hmu_pk_live_…hmu_sk_live_…
Where it livesIn the browser. It's public by design.Server only.
Can sendpage.viewed, session.started, your custom eventsEverything the public key can, plus user.created, user.deleted, user.activated, subscription.*
Can importNoYes

The distinction isn't a convention — it's in the code. A user.created sent with the public key is rejected with secret_key_required and logged as a signal for review. It's what keeps a number published on a ranking from being "whatever any visitor wanted it to be".

The secret key is shown once, when it's created. Our database only holds its hash.

POST /v1/events

One event
{ "event": "user.created", "user_id": "usr_123", "created_at": "2026-08-08T20:00:00Z" }
Or up to 100 at once
{ "events": [ { "event": "user.created", "user_id": "usr_123" }, … ] }

Fields

FieldTypeNotes
eventrequiredLowercase, up to three levels: checkout.completed. Can't start with user., subscription. or hmu. unless it's one of ours.
user_idrequired for user eventsA stable, non-personal id. We hash it before storing it, but sending emails anyway is still wrong — we don't need them.
created_atoptionalISO 8601. Defaults to now if missing. 2026-08-08 is accepted too (anchored to noon UTC).
idempotency_keyoptionalIf you retry, the event isn't counted twice.
propsoptionalA flat object, up to 20 keys, simple values. Don't send personal data here.

Response — 202

{ "accepted": 97, "duplicated": 2, "rejected": [ { "index": 4, "code": "missing_user_id", "message": "…" } ] }

A bad event never takes down the batch. The good ones are accepted and you get back the detail of each rejection with its index. If the whole batch failed instead, your retry would fail on the same event forever.

Per-event rejection codes

codeWhat happened
secret_key_requiredAn authoritative event sent with the public key.
missing_user_idMissing user_id on an event that needs it.
future_timestampcreated_at is in the future. Check your server clock.
timestamp_too_oldMore than 10 years in the past. Use the import endpoint for history.
reserved_event_nameThe name collides with a reserved one.
invalid_eventDoesn't match the schema (the message says which field).

Whole-request errors

401 invalid_key · 403 project_inactive · 413 payload_too_large · 429 rate_limited (with Retry-After) · 400 invalid_request

Event types we understand

user.created · user.deleted · user.activated · subscription.started · subscription.cancelled · session.started · page.viewed · your own.

session.started is what feeds daily/weekly/monthly actives; page.viewed deliberately doesn't move that metric (a single session is dozens of views).

POST /v1/users/import

Users you already had before installing us. Secret key only, up to 1,000 per request, 5 requests per hour.

{ "users": [ { "user_id": "usr_1", "created_at": "2025-11-03" } ] }

Response 202: { "imported": 843, "duplicated": 12, "failed": 0 }

They're marked as imported, forever: they count toward your total and it's disclosed on your profile, but they don't count toward growth rankings or the minimum observed users that eligibility requires. It's what keeps importing a thousand made-up users from being the shortcut to first place.

GET /v1/status

To confirm the key works and that we're receiving.

{
  "project": { "id": "…", "slug": "plata", "name": "Plata" },
  "key": { "kind": "secret", "prefix": "hmu_sk_live_9f3ac1d2" },
  "live": true,
  "last_event_at": "2026-08-08T20:14:02Z",
  "total_users": 1284,
  "today": { "accepted": 312, "rejected": 0, "duplicated": 4, "last_error": null }
}

Embeddables

No key needed — they only draw what the owner published.

GET /api/badge/<slug>.svg?theme=dark|light|neon|minimal&lang=en|es
GET /api/counter/<slug>.svg?theme=…&lang=…

The HTML you paste comes from the panel, and carries the attribution link back (/r/b/<slug>). That link is what lets us know a visit came from THAT badge — instead of "from nowhere", which is what badge traffic looks like without it.

Limits

Limit
Events with the secret key600 requests/min per project
Events with the public key120 requests/min per project
Import5 requests/hour
Body of /v1/events256 KB
Events per request100

Over the limit: 429 with Retry-After in seconds. Batch your events before raising the frequency.

Privacy

We don't want — and don't store — your users' names, emails, phone numbers or addresses. The user_id you send becomes an irreversible hash, different for every project, before it ever touches the database. We measure products, not people.

Platawtf1KFrambuesa97