Referencia completa
Todo lo que hay. Tres rutas, sin SDK — lo que sigue es el contrato exacto que ya cumple el código, no un resumen.
Autenticación
La clave va en la cabecera, de cualquiera de las dos formas:
Authorization: Bearer hmu_sk_live_9f3ac1d2_...X-HMU-Key: hmu_sk_live_9f3ac1d2_...La segunda existe para entornos donde un proxy ya usa "Authorization" para otra cosa.
| hmu_pk_live_… | hmu_sk_live_… | |
|---|---|---|
| Dónde vive | En el navegador. Es pública por definición. | Sólo en tu servidor. |
| Puede mandar | page.viewed, session.started, eventos personalizados | Todo lo de la pública, más user.created, user.deleted, user.activated, subscription.* |
| Puede importar | No | Sí |
La distinción no es una convención: está en el código. Un user.created mandado con clave pública se rechaza con secret_key_required y queda anotado como señal para revisión — es lo que hace que el número publicado en un ranking no sea "lo que cualquier visitante quiso que fuera".
La clave secreta se muestra una sola vez, al crearla. En nuestra base sólo está su hash.
POST /v1/events
{ "event": "user.created", "user_id": "usr_123", "created_at": "2026-08-08T20:00:00Z" }{ "events": [ { "event": "user.created", "user_id": "usr_123" }, … ] }Campos
| Campo | Tipo | Notas |
|---|---|---|
| event | requerido | Minúsculas, hasta tres niveles: checkout.completed. No puede empezar con user., subscription. ni hmu. salvo que sea uno de los nuestros. |
| user_id | requerido para eventos de usuario | Un id estable y no personal. Lo hasheamos antes de guardarlo, pero mandar emails igual está mal: no los necesitamos. |
| created_at | opcional | ISO 8601. Si falta, ahora. Se acepta 2026-08-08 (se ancla al mediodía UTC). |
| idempotency_key | opcional | Si reintentás, el evento no se cuenta dos veces. |
| props | opcional | Objeto plano, hasta 20 claves, valores simples. No mandes datos personales acá. |
Respuesta — 202
{ "accepted": 97, "duplicated": 2, "rejected": [ { "index": 4, "code": "missing_user_id", "message": "…" } ] }Un evento malo no tira el lote. Se aceptan los buenos y se devuelve el detalle de cada rechazado con su índice. Si el lote entero fallara, tu reintento volvería a fallar por el mismo evento, para siempre.
Códigos de rechazo por evento
| code | Qué pasó |
|---|---|
| secret_key_required | Un evento autoritativo con clave pública. |
| missing_user_id | Falta user_id en un evento que lo necesita. |
| future_timestamp | created_at en el futuro. Mirá el reloj del servidor. |
| timestamp_too_old | Más de 10 años atrás. Para historia, usá la importación. |
| reserved_event_name | El nombre pisa uno reservado. |
| invalid_event | No cumple el esquema (el mensaje dice qué campo). |
Errores de la petición entera
401 invalid_key · 403 project_inactive · 413 payload_too_large · 429 rate_limited (con Retry-After) · 400 invalid_request
Eventos que entendemos
user.created · user.deleted · user.activated · subscription.started · subscription.cancelled · session.started · page.viewed · los tuyos.
session.started es lo que alimenta los activos diarios/semanales/mensuales; page.viewed no mueve esa métrica a propósito (una sesión son decenas de vistas).
POST /v1/users/import
Los usuarios que ya tenías antes de instalarnos. Sólo con clave secreta, hasta 1.000 por pedido, 5 pedidos por hora.
{ "users": [ { "user_id": "usr_1", "created_at": "2025-11-03" } ] }Respuesta 202: { "imported": 843, "duplicated": 12, "failed": 0 }
Quedan marcados como importados, para siempre: suman a tu total y se dice en tu perfil, pero no cuentan para los rankings de crecimiento ni para el mínimo de usuarios observados que pide la elegibilidad. Es lo que evita que importar mil usuarios inventados sea el camino corto al primer puesto.
GET /v1/status
Para confirmar que la clave anda y que estamos recibiendo.
{
"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 }
}Embebibles
No necesitan clave: dibujan sólo lo que el dueño publicó.
GET /api/badge/<slug>.svg?theme=dark|light|neon|minimal&lang=en|es
GET /api/counter/<slug>.svg?theme=…&lang=…El HTML que hay que pegar sale del panel, y lleva el enlace de vuelta con atribución (/r/b/<slug>). Ese enlace es lo que permite saber que una visita vino de ESE badge — y no "de ningún lado", que es como se ve el tráfico de badges sin él.
Límites
| Límite | |
|---|---|
| Eventos con clave secreta | 600 pedidos/min por proyecto |
| Eventos con clave pública | 120 pedidos/min por proyecto |
| Importación | 5 pedidos/hora |
| Cuerpo de /v1/events | 256 KB |
| Eventos por pedido | 100 |
Al pasarse: 429 con Retry-After en segundos. Agrupá eventos en lotes antes de subir la frecuencia.
Privacidad
No queremos —ni guardamos— nombres, emails, teléfonos ni direcciones de tus usuarios. El user_id que mandás se convierte en un hash irreversible, distinto para cada proyecto, antes de tocar la base. Medimos productos, no personas.