> ## 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.

# Entitlements no SDK

> O que o time logado tem direito, e por que uma resposta desconhecida não é uma recusa.

Um entitlement é o que o **plano** permite, e é uma pergunta diferente de o que
uma **pessoa** pode. `getEntitlements()` responde a primeira, para o time dentro do
qual a sessão atual está agindo.

```ts theme={null}
const grant = await userkit.getEntitlements();

const seats = grant.features["seats"];
const cabeMaisUm = seats?.unlimited || (seats?.limit ?? 0) > assentosUsados;
```

```json theme={null}
{
  "customer_id": "9f2c…",
  "plan": "pro",
  "resolved": true,
  "features": {
    "seats": { "kind": "metered", "enabled": true, "limit": 25, "unlimited": false, "included": null },
    "projects": { "kind": "metered", "enabled": true, "limit": null, "unlimited": true, "included": null },
    "ai_credits": { "kind": "credit", "enabled": true, "limit": null, "unlimited": false, "included": 5000 },
    "sso": { "kind": "boolean", "enabled": false, "limit": null, "unlimited": false, "included": null }
  }
}
```

As features são o **seu** catálogo — os planos que você vende, não os nossos.
`kind` é uma string simples e não uma união justamente por isso: um kind que você
criar não pode depender de uma nova versão do SDK para ser lido por um cliente.

`limit` é um teto para comparar com o uso; `included` é uma concessão por período
para somar a um saldo, e só existe para o kind `credit`. São dois campos porque
são duas perguntas.

**"Sem teto" viaja como `unlimited: true`, nunca como um `limit` ausente.** Uma
feature com `limit: null` e `unlimited: false` não tem teto *registrado*, o que é
um teto de zero e não permissão para um teto infinito — um número mágico no fio é
um número que todo cliente precisa aprender, e um `null` solto é um que todo
cliente interpreta do seu jeito. Toda feature do seu catálogo aparece aqui,
inclusive as que o plano atual não carrega: elas chegam com `enabled: false`, que
é a resposta e não uma lacuna.

## No React

```tsx theme={null}
import { useEntitlements } from "@userkit/react";

function PainelDeAssentos() {
  const grant = useEntitlements();
  if (!grant) return <Spinner />;          // ainda não, ou não dá para saber
  return grant.features["sso"]?.enabled ? <ConfigSso /> : <ChamadaDeUpgrade />;
}
```

Num app Next, importe o hook do `@userkit/nextjs` — é o mesmo hook, e em
[modo proxy](/pt-br/customer-auth/session-tokens) a leitura é encaminhada pelos
handlers, com a sessão num cookie httpOnly da sua própria origem. O time continua
sendo resolvido e nunca nomeado: o `X-Customer-Id` atravessa os handlers e identifica
sem nunca autorizar.

Ele segue a sessão: sair da conta limpa o valor, e trocar de time refaz a leitura
para o time dentro do qual a próxima requisição vai agir.

## O time é resolvido, nunca nomeado

A concessão pertence ao cliente que a sessão resolveu — o que
`setActiveCustomer()` nomeou, ou a associação mais antiga do contato quando
nenhum foi nomeado. Não existe parâmetro que nomeie um cliente, e é isso que faz
um id colado responder 404 em vez do plano de outra pessoa.

## Três respostas que valem um branch

* **404** — a sessão não pertence a time nenhum. Um visitante anônimo recebe isso,
  e é a resposta verdadeira em vez de uma recusa: é o que diz à interface para
  oferecer a criação de um time.
* **403 `unverified_session`** — uma sessão identificada mas não verificada. O que
  uma conta paga inclui não se entrega a uma afirmação que qualquer um que saiba um
  `external_id` poderia fazer de um navegador.
* **503 `entitlements_unavailable`** — nada está sendo recusado. A concessão não
  pôde ser resolvida agora, então pergunte de novo. A API não responde corpo
  nenhum, em vez de responder um cheio de `false`, porque uma tela cheia de `false`
  tomaria a decisão por você, em silêncio, na direção que fecha o seu produto
  durante uma queda nossa.

`useEntitlements()` renderiza as três como `null`, que é a mesma renderização de
"ainda carregando", de propósito: as duas significam *ainda não há nada para
barrar*. **Nunca trate `null` como uma concessão de nada** — barre em
`grant?.features[key].enabled` e deixe `null` significar "ainda não".

## Isto é o portão da interface, não a aplicação da regra

Um navegador pode ouvir qualquer coisa. Leia a mesma concessão no seu backend —
com uma chave de servidor, `GET /v1/customers/{id}/entitlements`, ao lado do
[token de sessão](/pt-br/customer-auth/session-tokens) que prova quem está
perguntando — e aplique a regra lá. O que `getEntitlements()` compra é uma tela que
não oferece um botão que o plano vai recusar.
