> ## Documentation Index
> Fetch the complete documentation index at: https://docs.userkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Pontos, níveis e conquistas

> Regras que pagam quando um fato acontece, métricas que contam o que não é ponto, níveis que pedem tudo isso junto, e o teto que a API se recusa a escolher por você.

Pontos são o outro lado da [checklist de onboarding](/pt-br/guides/onboarding).
Aquela lista mede o que alguém fez uma vez; esta mede o que a pessoa **continua**
fazendo — e usa exatamente a mesma máquina, incluindo a regra que dá a ela todo o
seu valor: nada é marcado por ninguém.

## Uma regra é um fato, um valor e um teto

```bash theme={null}
curl -X POST https://api.userkit.dev/v1/organization/points/rules?environment=live \
  -H "Authorization: Bearer $UK_SESSION" \
  -d '{
    "key": "convidou_colega",
    "name": "Convidou um colega",
    "kind": "event",
    "event_type": "customer_member.joined",
    "points": 25,
    "award_limit": "once"
  }'
```

Ela nasce desligada. Enquanto você discute quanto vale, ninguém é creditado — e
ligar é o momento em que a contagem começa, a partir dali e não do que já
aconteceu.

### Dois tipos de gatilho

`event` é um fato que a plataforma já publica sobre uma pessoa: verificou o
e-mail, entrou num time, saiu da lista de espera. Não há nada para instrumentar.
A lista completa vem na própria leitura das regras, em `event_types`.

`product_event` é um nome que **o seu produto** manda pelo
[`track()`](/pt-br/guides/events). O vocabulário é seu, então nada aqui pode
recusar um nome por não existir — o que o painel faz é dizer se este ambiente já
viu aquele nome alguma vez, que é a metade honesta de uma verificação que não
pode reprovar.

<Warning>
  Um `product_event` carrega uma garantia mais fraca que a de um `event`, e é a
  mesma diferença que a checklist já tem. O analytics descarta eventos quando o
  buffer enche; um evento descartado é um ponto que ninguém vai pagar depois,
  porque o extrato não sabe que ele era devido. Para o que precisa ser exato,
  prefira um fato da plataforma ou uma [concessão pelo seu
  back-end](#concedendo-pontos-pelo-seu-back-end).
</Warning>

**Não existe gatilho de "direito".** Um ponto é um acontecimento; um direito é um
estado. Pagar por um estado exigiria um relógio perguntando "ainda tem?" e um
estorno quando deixasse de ter — e a resposta pertence a um *time*, do qual uma
pessoa pode fazer parte de vários. Um saldo cujo valor depende de qual aba
resolveu a pergunta não é um saldo.

### O teto não tem padrão, e é de propósito

`award_limit` é obrigatório:

| Valor       | O que significa                                               |
| ----------- | ------------------------------------------------------------- |
| `once`      | Uma vez por pessoa, para sempre                               |
| `per_day`   | No máximo `limit_value` vezes por pessoa em 24 horas corridas |
| `cooldown`  | No máximo uma vez a cada `limit_value` minutos                |
| `unlimited` | Sem teto — e você digitou isso                                |

A primeira regra que alguém aponta para um evento frequente sem teto é um laço
sem fim com uma tabela no fim dele. As janelas são **corridas** e não de
calendário: ninguém ganha uma cota nova numa meia-noite que a gente escolheu por
ele.

Na listagem, cada regra volta com `awards` e `earners`. Muitas concessões para
poucas pessoas é o formato do erro que este módulo tem: um teto deixado em
`unlimited` num nome que o produto manda em toda página.

### O que não muda depois

`key`, `kind` e `event_type` são congelados. As linhas que a regra já escreveu
dizem "você ganhou isto por X", então reapontá-la reescreveria o histórico de
todo mundo sem tocar em uma linha sequer. Aposente a regra e crie outra: as
linhas antigas guardam o nome que a regra tinha na hora.

Repreçar é permitido e **não é retroativo**. Mudar de 10 para 5 muda o que a
próxima concessão vale, e nada do que já aconteceu.

## Métricas: o que você conta além de pontos

Enquanto a única unidade era o ponto, "ouro é 4 cursos" só dava para escrever
como "uma regra que paga 1 ponto por curso, e ouro são 4 pontos" — e aí a
unidade morria na porta: no instante em que o fato virava linha do extrato, ele
tinha deixado de ser um curso. Uma **métrica** devolve a unidade.

```bash theme={null}
curl -X POST https://api.userkit.dev/v1/organization/points/metrics?environment=live \
  -H "Authorization: Bearer $UK_SESSION" \
  -d '{"key": "cursos", "name": "Cursos concluídos", "unit": "curso", "icon": "🎓"}'
```

Uma regra credita a métrica em vez do saldo — mesmo extrato, mesmo teto, mesma
idempotência, um campo a mais:

```bash theme={null}
curl -X POST https://api.userkit.dev/v1/organization/points/rules?environment=live \
  -H "Authorization: Bearer $UK_SESSION" \
  -d '{
    "key": "curso_concluido",
    "name": "Concluiu um curso",
    "kind": "product_event",
    "event_type": "course.completed",
    "metric_id": "'"$METRIC_ID"'",
    "points": 1,
    "award_limit": "unlimited"
  }'
```

Com `metric_id`, `points` é **quanto daquela métrica** vale uma ocorrência: 1
curso, 1 e-mail. O saldo não se mexe, e uma conquista `points_total` não é
liberada por isso — um crédito de métrica não moveu saldo nenhum.

Criar uma métrica não faz nada sozinha. Sem uma regra creditando, ela fica em
zero para todo mundo, e todo degrau que a pede fica vazio junto — é por isso que
a listagem devolve `lines` e `holders`, que são o par que denuncia isso antes de
alguém abrir um chamado dizendo que o programa não funciona. A ordem que
funciona é **métrica, depois regra, depois nível**.

<Warning>
  Uma métrica que já creditou alguém **não pode ser apagada**, e isso é uma
  decisão e não uma limitação. Uma linha do extrato sobrevive à regra que a
  escreveu — ela carrega o nome congelado, e nenhum saldo se move. Não pode
  sobreviver à métrica: sem ela, a linha deixaria de ser um curso e viraria
  silenciosamente um ponto. Apagar "e-mails" daria mil pontos a quem já mandou mil
  e-mails, e subiria essa gente de nível por causa disso.
</Warning>

## Níveis são uma função do que a pessoa tem

Um degrau é **um conjunto de condições, e todas precisam valer**:

```bash theme={null}
curl -X POST https://api.userkit.dev/v1/organization/points/levels?environment=live \
  -H "Authorization: Bearer $UK_SESSION" \
  -d '{
    "name": "Ouro",
    "icon": "🥇",
    "requirements": [
      {"kind": "points", "threshold": 400},
      {"kind": "metric", "metric_id": "'"$CURSOS"'", "threshold": 4},
      {"kind": "metric", "metric_id": "'"$EMAILS"'", "threshold": 1000}
    ]
  }'
```

Cinco tipos de condição, cada um respondido por uma máquina diferente:

| `kind`        | Condição                                                          |
| ------------- | ----------------------------------------------------------------- |
| `points`      | O saldo chegou a `threshold`                                      |
| `metric`      | A conta daquela métrica chegou a `threshold`                      |
| `rule_awards` | Aquela regra pagou esta pessoa `threshold` vezes                  |
| `achievement` | A pessoa tem aquela medalha                                       |
| `segment`     | A pessoa está naquele [público](/pt-br/guides/segments) **agora** |

O último é o único que pode **deixar** de valer: sair do público derruba o nível.
Se a intenção era "quem chegou ao Pro" e não "quem está no Pro", use uma medalha
de segmento — ela é escrita para sempre no instante em que é ganha.

O nível não é guardado em ninguém: ele é derivado a cada leitura como o degrau de
maior `rank` cujas condições todas valem. É o que impede que ele discorde do
extrato, das contas e das medalhas que o descrevem.

### A ordem virou um campo

O limite era a própria ordem enquanto um degrau era um número só. Deixou de
poder ser: nenhuma aritmética compara "400 pontos e 4 cursos" com "400 pontos e
10 cursos". O `rank` é a única coisa que ordena a escada, e é único por
ambiente. Omitir `rank` na criação põe o degrau acima do último — que é o que
criar um nível quase sempre é.

Dois degraus **no mesmo saldo** agora são permitidos, e é exatamente para isso
que isto existe: ouro pode ser 400 pontos e 4 cursos enquanto platina é 400
pontos e 10.

### Coisas que vale ler antes de precisar delas

* **Um degrau sem condições é alcançado por NINGUÉM**, e não por todo mundo. A
  leitura contrária promoveria o ambiente inteiro no instante em que alguém cria
  um nível e sai para almoçar.
* **Uma linha negativa derruba o nível junto com o saldo**, na hora e sem
  anúncio — e o mesmo vale para sair de um público que um degrau pede. Não
  existe `level_lost`: rebaixar alguém não é um comunicado que produto nenhum
  quer fazer em seu nome.
* **O anúncio acontece uma vez só.** Quem alcança três degraus de uma vez — uma
  correção grande, uma fusão de cadastros, uma medalha que era a última condição
  de dois níveis — recebe **um** `points.level_reached`, o do degrau mais alto.
  Três fatos no mesmo segundo seriam três e-mails sobre o mesmo momento.
* **Editar as condições move quem está no nível na hora**, sem linha nenhuma no
  extrato. É a diferença entre um nível e uma regra: uma regra descreve o que
  aconteceu, e um nível descreve o que é verdade agora.

## Conquistas

Três formatos, cada um satisfeito por uma máquina diferente:

| `kind`         | Condição                                               |
| -------------- | ------------------------------------------------------ |
| `event_count`  | Uma regra pagou esta pessoa N vezes                    |
| `points_total` | O saldo chegou a N                                     |
| `segment`      | A pessoa entrou num [segmento](/pt-br/guides/segments) |

O terceiro é o mais barato de acertar, porque reaproveita o motor de públicos que
você já usa — e a única diferença entre uma medalha e um segmento é que **a
medalha não sai**. Quem foi trialista de enterprise em março continua com a
medalha em junho; recalcular pela definição a tiraria de volta.

`hidden` esconde a medalha de quem ainda não a tem. É metade da graça do
mecanismo por um booleano — e funciona porque **a condição nunca vai para o
navegador**: a API manda um nome, um desenho e se é sua. Dizer "libera com 40
convites" é entregar a regra para ser farmada.

A condição também é congelada. Quem já tem a medalha ganhou a antiga, e editar o
que ela quer dizer mudaria o que cada liberação existente afirma.

<Note>
  Apagar uma regra que uma conquista `event_count` conta é recusado, e o erro diz
  quantas conquistas estão no caminho. Uma medalha cuja regra não existe mais é uma
  medalha na estante que ninguém consegue mais tirar.
</Note>

## Mostrando para a pessoa

```tsx theme={null}
import { Points, PointsPill } from "@userkit/react";

<Points />;                    {/* saldo, nível e estante */}
<Points history />;            {/* já com o extrato aberto */}
<Points achievements={false} />;

<PointsPill />;                          {/* o mesmo saldo num header */}
<PointsPill border={false} />;         {/* dentro de uma toolbar que já tem moldura */}
<PointsPill level={false} ring={false} />; {/* o mais compacto: só o número */}
<PointsPill onClick={() => router.push("/pontos")} />;
```

O `<PointsPill />` é o gêmeo do `<OnboardingLauncher />` — mesma pílula, mesmo
anel — para os lugares onde um cartão não cabe. Ele não abre nada: não existe
painel flutuante de pontos, então `onClick` serve para o produto que tem a
própria página. Num produto sem gamification configurada ele não desenha nada.

Ou pelo cliente, se você desenha a sua própria tela:

```ts theme={null}
const { balance, metrics, level, next_level, points_to_next, achievements } =
  await userkit.getPoints();

// O que falta para o próximo degrau, condição por condição.
for (const requirement of next_level?.requirements ?? []) {
  if (!requirement.met) {
    console.log(requirement.kind, requirement.have, "de", requirement.need);
  }
}

const { lines, total } = await userkit.getPointsStatement({ limit: 20 });
```

`level` é `null` quando nenhum degrau foi alcançado — **inclusive no caso comum
de um produto sem níveis nenhum**. Desenhe o nulo: um zero seria uma posição que
ninguém configurou.

`points_to_next` continua sendo só a distância em **pontos**, e é zero quando o
próximo degrau não pede pontos. Numa escada com métricas, desenhe a barra a
partir de `next_level.requirements`: "faltam 60 pontos" numa condição que também
quer quatro cursos é uma barra que enche e não promove ninguém.

<Note>
  As condições de um **nível** viajam para o navegador e as de uma **medalha**
  não, na mesma resposta, e é o mesmo princípio duas vezes. Uma medalha é uma
  surpresa que o produto tem direito de guardar; um degrau é uma meta que o
  produto está **pedindo** para a pessoa alcançar. A única coisa retida é o nome
  de um público — esse é o seu texto interno sobre os seus usuários, e nunca foi
  escrito para eles lerem.
</Note>

Um visitante anônimo recebe zeros, e não um 401: o widget roda na landing page. O
que ele ganhar depois de se cadastrar vai junto — a fusão do visitante com a
conta move o extrato inteiro.

### Não existe escrita nesta superfície

Nenhum endpoint do plano do cliente mexe num saldo, e não vai existir. É a mesma
regra da checklist, pelo mesmo motivo: no instante em que uma página consegue
pedir pontos, a aba de rede daquela página vira o jogo.

## Concedendo pontos pelo seu back-end

A exceção, e ela mora na superfície de máquina — um programa seu, não uma página
que você publica:

```bash theme={null}
curl -X POST https://api.userkit.dev/v1/points \
  -H "Authorization: Bearer $UK_SECRET_KEY" \
  -d '{
    "external_id": "user_123",
    "amount": 500,
    "reason": "Compensação do suporte",
    "request_key": "ticket-4711"
  }'
```

`request_key` é **obrigatório** — diferente da chave opcional do
[`/v1/usage`](/pt-br/guides/entitlements). Uma concessão repetida sem chave é um
segundo presente, e quem mais quer este endpoint é um script que alguém roda duas
vezes. A repetição responde `200` com `duplicate: true` e o saldo como está: o
estado que você pediu é verdade, então um erro seria uma repetição para tratar
diante de um resultado correto.

A linha entra com `source: "manual"`, que é a coluna para agrupar quando um saldo
parecer impossível. Um contato anônimo é recusado: saldo em visitante pertence a
um carregamento de página, não a uma pessoa.

Com `metric`, o mesmo endpoint credita uma **conta** em vez do saldo — a porta
para o que aconteceu fora do seu produto:

```bash theme={null}
curl -X POST https://api.userkit.dev/v1/points \
  -H "Authorization: Bearer $UK_SECRET_KEY" \
  -d '{
    "external_id": "user_123",
    "metric": "cursos",
    "amount": 3,
    "reason": "Importado do LMS",
    "request_key": "lms-import-2026-08"
  }'
```

É um campo e não um segundo endpoint porque é o mesmo ato: mesmo destinatário,
mesma `request_key` obrigatória, mesma linha no mesmo extrato. O `balance` da
resposta passa a ser a conta daquela métrica, e `metric` ao lado dele diz qual
foi movida.

## Os fatos que saem daqui

Três, e todos entregues aos seus [webhooks](/pt-br/guides/webhooks):

| Fato                   | Quando                                                                                                                 |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `points.awarded`       | Cada linha do extrato, com o saldo **depois** dela — e `metric_key`, quando a linha creditou uma conta em vez do saldo |
| `points.level_reached` | Um limite cruzado, no máximo o mais alto por vez                                                                       |
| `achievement.unlocked` | Uma medalha, uma vez por pessoa e para sempre                                                                          |

`points.awarded` é o fato mais frequente do catálogo por construção — ele dispara
por linha. O que o limita é o `award_limit` da regra, que é exatamente o motivo de
ele não ter padrão.

**Uma regra não pode escutar esses três.** Pagar por ser pago é um laço que roda
na velocidade da fila, e a API recusa a regra na hora de criá-la.

## O que é apagado quando alguém pede para ser esquecido

O extrato inteiro, as medalhas e o registro dos níveis anunciados. Os números do
painel — o que cada regra pagou, quantas pessoas têm cada medalha — são
agregados de quem está presente, então eles se movem em vez de quebrar. É a
aritmética correta: quem ganhou aqueles pontos não está mais aqui.
