Skip to content
howmuchusers
Sign in

Docs

One HTTP call. Two kinds of key, and the difference between them is the whole reason the numbers here mean anything.

Don't have an account yet? Create your project for free — your keys are ready to copy in the Developers tab.

Create my project

Two keys, two permissions

hmu_pk_live_…

Public key — browser-safe. Page views, sessions and your own custom events. It cannot create users, by design.

hmu_sk_live_…

Secret key — server only. Creating and deleting users, imports, subscription events.

Sending events

One event, or up to 100 at a time. A bad event never takes down the batch: you get back what was accepted and exactly what was wrong with each rejection.

POST /v1/events
curl -X POST https://howmanyusers.wtf/api/v1/events \
  -H "Authorization: Bearer $HMU_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event":"user.created","user_id":"usr_123","created_at":"2026-08-08T20:00:00Z"}'
Node / Next.js
// HowManyUsers — from your server (Node, Next.js, any backend).
// The secret key lives in the environment. NEVER in browser code.
async function hmuUserCreated(user) {
  const res = await fetch("https://howmanyusers.wtf/api/v1/events", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.HMU_SECRET_KEY}`,
    },
    body: JSON.stringify({
      event: "user.created",
      user_id: user.id,          // a stable id. Never emails or names.
      created_at: new Date(user.createdAt).toISOString(),
      idempotency_key: `signup:${user.id}`, // retrying never counts twice
    }),
  });

  // A 202 IS NOT "IT WORKED": it carries what it rejected inside, and a
  // rejected event looks exactly like success if you never read it.
  const { rejected } = await res.json();
  if (rejected?.length) console.warn("[hmu]", rejected);
}
Browser
<!-- HowManyUsers — from the browser. Feeds analytics (device, browser,
     country, referrer). It does NOT count users: only your server can, with the
     secret key, and that is what makes the published number worth anything. -->
<script>
  fetch("https://howmanyusers.wtf/api/v1/events", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer hmu_pk_live_..._YOUR_PUBLIC_KEY",
    },
    body: JSON.stringify({ event: "page.viewed" }),
  });
</script>

Importing your history

Users you had before installing us. They're marked as imported forever: they count toward your total, they're disclosed on your public profile, and they don't count toward growth rankings.

POST /v1/users/import
# The users you already had. They stay marked as imported: they count toward
# your total, but never toward the growth rankings. Up to 1,000 per request.
curl -X POST https://howmanyusers.wtf/api/v1/users/import \
  -H "Authorization: Bearer $HMU_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"users":[
        {"user_id":"usr_1","created_at":"2025-11-03"},
        {"user_id":"usr_2","created_at":"2025-11-04"}
      ]}'

Checking it works

Same header, no body. Returns live: true once your first event has arrived.

GET /v1/status
curl https://howmanyusers.wtf/api/v1/status \
  -H "Authorization: Bearer $HMU_SECRET_KEY"

Badges and counters

No key needed — they only draw what you chose to publish. The embed code, with the attribution link, is in your project's Developers tab.

SVG
<img src="https://howmanyusers.wtf/api/badge/tu-startup.svg?theme=dark&lang=en" alt="" height="44" />

Trophy badge

Shows your latest challenge win and who you beat. Empty until you win one — same address starts working the day you do.

SVG
<img src="https://howmanyusers.wtf/api/badge/tu-startup/trophy.svg?theme=dark&lang=en" alt="" height="44" />

Full reference

How do we know these numbers are real?