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

# Indicações e afiliados

> Emita um código, capture o clique e leia o que você deve — com as regras que decidem quem é creditado e o que um estorno devolve.

Um programa de indicação são três fatos e uma regra que atravessa todos eles. Um
**código** pertence a um dos seus clientes e carrega quanto ele paga. Uma
**atribuição** diz que um contato entrou por um código — uma vez, para sempre.
Uma **comissão** é uma linha num livro-razão, escrita quando uma fatura é paga e
escrita de novo, negativa, quando ela é estornada.

A regra é o **primeiro toque**: quem indicou primeiro indicou. Um segundo `?ref=`
nunca reaponta ninguém, porque a comissão já foi ganha pelo primeiro.

O painel desenha isso em **Receita → Indicações**, atrás de `billing:read` e
`billing:write`.

<Note>
  **Nada aqui paga ninguém.** Isto registra o que é devido; a transferência é um
  ato seu, no seu próprio gateway de pagamento, sob os seus termos. Toda resposta
  diz isso com `payouts_are_manual: true`, e as telas repetem ao lado do número —
  um total que não dissesse seria lido como um total que já foi pago.
</Note>

## Emitir um código

Um código pertence a um contato — a pessoa que será paga pela apresentação — e
carrega os seus termos. Não existe um padrão por ambiente para cair de volta:
um código sem termos é um pagamento que ninguém combinou.

```bash theme={null}
curl -X POST "https://api.userkit.dev/v1/organization/referral-codes?environment=live" \
  -H "Authorization: Bearer uk_st_…" \
  -H "X-Organization-Id: …" \
  -H "Content-Type: application/json" \
  -d '{
    "owner_contact_id": "9f2c…",
    "code": "MARIA",
    "rate_basis_points": 2000
  }'
```

Exatamente um dos dois termos:

| Termo                                   | O que significa                                                         |
| --------------------------------------- | ----------------------------------------------------------------------- |
| `rate_basis_points`                     | Uma parte do que o cliente indicado paga. `2000` é 20%.                 |
| `fixed_amount_minor` + `fixed_currency` | Um valor fixo, na unidade menor da moeda. `5000` + `"BRL"` é R\$ 50,00. |

O ambiente viaja como `?environment=live` ou `?environment=test` também nas
escritas, e não é opcional — um código emitido sem ambiente teria que assumir um,
e assumir o live é como um código feito para um teste começa a atribuir
cadastros de verdade.

Desligar e religar um código são duas rotas em vez de um campo, porque são atos
opostos:

```bash theme={null}
curl -X POST "https://api.userkit.dev/v1/organization/referral-codes/{id}/deactivate?environment=live" \
  -H "Authorization: Bearer uk_st_…" -H "X-Organization-Id: …"
```

Um código inativo **não atribui mais ninguém**. O que ele já trouxe continua
rendendo: a atribuição era um fato sobre o passado, e desativar é uma afirmação
sobre o futuro.

## Capturar o clique

Não existe endpoint para isso. O `?ref=CODE` é lido da página de entrada pelo
SDK, guardado junto com o resto da atribuição de primeiro toque, e viaja com a
primeira chamada que identifica alguém — um boot com `external_id`, ou um magic link resgatado.

```
https://seuproduto.com/?ref=MARIA
```

Essa é a integração inteira, e é por isso que funciona para quem clica na
segunda-feira e se cadastra na quinta: o valor fica guardado no navegador e
sobrevive aos dias entre o clique e a conta. Um endpoint dedicado seria um
segundo caminho para o mesmo estado — e o que esquece as regras abaixo.

<Warning>
  O código é capturado **junto** com o resto do primeiro toque, nunca sozinho. Se
  você tirar o `?ref=` da URL antes de o SDK rodar, ou se a sua landing page
  redirecionar sem levar a query string, nada é capturado e não há segunda
  chance: o primeiro toque aconteceu, e registrou nada.
</Warning>

### O que não atribui nada, e nunca derruba um cadastro

Cada um destes não escreve atribuição alguma e não recusa nada — o cadastro que
carregou o código dá certo do mesmo jeito:

* **Um código que não existe** neste ambiente: um erro de digitação, ou um link
  montado à mão contra o outro ambiente.
* **Um código inativo.**
* **O próprio código de quem está entrando.** A conta que paga seria a conta que
  recebe, o que é um desconto se passando por indicação.
* **Um segundo `?ref=`** para alguém já atribuído. O primeiro toque permanece.
* **Um visitante anônimo.** Atribuição precisa de um contato identificado; um
  dispositivo que nunca entrou é uma linha que ninguém conseguiria achar depois.
* **Um boot federado sem prova para um contato que já existe.** Um boot sem hash
  é uma afirmação que qualquer pessoa que saiba um `external_id` consegue fazer
  do navegador, então deixar uma delas grudar uma indicação em alguém que já
  existe seria uma comissão roubada com um parâmetro de URL. Um boot que *cria* o
  contato pode atribuir — a mesma regra que impede uma chamada sem prova de
  reescrever um `name` ou um `email`.

## O que um pagamento rende

Quando uma fatura de um cliente indicado é paga, uma linha de comissão é escrita.
Ela sai do que de fato continuou pago — o total da fatura menos tudo que foi
estornado — então um estorno não é outro caminho, é a mesma conta sobre um número
menor.

Para uma porcentagem: `líquido × rate_basis_points ÷ 10000`, truncado, então você
nunca paga uma fração de centavo acima dos termos.

Para um valor fixo, a comissão é **proporcional** ao que continuou pago, e essa é
a única decisão aritmética aqui que vale dizer em voz alta. Tudo-ou-nada faria um
estorno de um centavo apagar um prêmio de R\$ 50; "mantém o prêmio inteiro a menos
que a fatura seja estornada por completo" pagaria integralmente num estorno de
99%. Proporcional é a única regra que responde aos dois.

Um estorno maior que a fatura — um crédito de cortesia acima do valor — devolve a
comissão e nada além disso. Um saldo negativo seria um afiliado devendo dinheiro
a você por uma venda que ele trouxe.

<Note>
  Um código de valor fixo diante de uma fatura em **outra moeda** não registra
  nada e é logado: nenhuma taxa de câmbio é inventada num pagamento a afiliado.
  Ou os termos ou o preço estão errados, e qual dos dois é uma decisão que só
  você pode tomar.
</Note>

## Ler o que você deve

Saldos primeiro, as linhas por trás deles depois, numa resposta só — o relatório
é uma tela que alguém abre para decidir quem pagar este mês, e as linhas são a
evidência do número ao lado de cada nome.

```bash theme={null}
curl "https://api.userkit.dev/v1/organization/referrals/report?environment=live" \
  -H "Authorization: Bearer uk_st_…" \
  -H "X-Organization-Id: …"
```

```json theme={null}
{
  "balances": [
    {
      "referral_code_id": "3d1a…",
      "code": "MARIA",
      "active": true,
      "owner_contact_id": "9f2c…",
      "owner_name": "Maria Andrade",
      "owner_email": "maria@example.com",
      "currency": "BRL",
      "earned_minor": 148000,
      "reversed_minor": -9900,
      "owed_minor": 138100,
      "entry_count": 12
    }
  ],
  "entries": [
    {
      "id": "7be4…",
      "referral_code_id": "3d1a…",
      "code": "MARIA",
      "kind": "earned",
      "amount_minor": 11800,
      "currency": "BRL",
      "basis_minor": 59000,
      "invoice_id": "1f77…",
      "customer_id": "5ca9…",
      "referred_contact_id": "b302…",
      "created_at": "2026-07-31T14:02:11Z"
    }
  ],
  "payouts_are_manual": true
}
```

Os saldos são **por código e por moeda**, e nunca somados entre moedas — dois
preços em duas moedas são dois valores que alguém combinou, não conversões um do
outro. O `kind` de cada linha é `earned` ou `reversed`, e `basis_minor` é o valor
sobre o qual a comissão foi calculada. `entries` é paginado com `limit` (50 por
padrão, 200 no máximo) e `offset`; `balances` não é, porque é uma linha por
código e moeda.

O livro-razão é **append-only**: uma correção é uma linha nova, nunca uma edição,
e o banco recusa um `UPDATE` na cara. É também por isso que o saldo é uma dobra
sobre as linhas, e não um total guardado que poderia discordar delas.

## O que quem indicou vê

O seu cliente lê o próprio lado em `GET /v1/contact/referrals`, com a sessão de
contato dele — os códigos que tem, quantas pessoas cada um trouxe e quanto cada
um rendeu. O portal hospedado desenha isso como **Indique**.

Essa rota exige uma sessão de contato **verificada**, ao contrário da maior parte
de `/v1/contact/*`. A resposta é dinheiro devido a quem está segurando a sessão,
então uma sessão identificada que não provou nada deixaria qualquer pessoa que
saiba um `external_id` ler os ganhos de outra pelo navegador. Uma sessão anônima
passa e lê uma lista vazia, o que é verdade e não uma recusa: um visitante não
tem código nenhum.

```json theme={null}
{
  "codes": [
    {
      "id": "3d1a…",
      "code": "MARIA",
      "owner_contact_id": "9f2c…",
      "active": true,
      "referred_count": 12,
      "rate_basis_points": 2000,
      "fixed_amount_minor": null,
      "fixed_currency": null,
      "created_at": "2026-06-02T10:00:00Z",
      "balances": [
        { "currency": "BRL", "earned_minor": 148000, "reversed_minor": -9900, "owed_minor": 138100 }
      ]
    }
  ],
  "payouts_are_manual": true
}
```

Ela nunca diz **quem** foi indicado. Isso é uma lista de contas de outras
pessoas, e quem segura o código não tem direito sobre ela — a contagem é a
resposta que essa pessoa merece.

Também não existe rota para um contato emitir o próprio código. Um que qualquer
um emite para si mesmo é um que um atacante emite em looping; os códigos são
emitidos por você.

### No seu app

`<Referrals />` desenha a mesma tela dentro do seu produto, e
`client.getReferrals()` entrega a resposta crua para quem quer montar a própria:

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

export default function ReferralsPage() {
  return <Referrals empty={<p>Peça um código de indicação para a nossa equipe.</p>} />;
}
```

Num app Next, importe do `@userkit/nextjs` — é o mesmo componente, 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. Ela não leva
`X-Customer-Id`: a comissão é do contato, e trocar de time não a move.

O componente carrega as três recusas da rota: não lista quem foi indicado, não
oferece emitir código, e mostra `payouts_are_manual` como frase em vez de um
botão que nada atrás dele honraria. Ele não desenha nada para quem não tem
código — a maioria das pessoas nunca vai ter um, e uma caixa explicando isso para
todas elas é ruído. `empty` é a frase da sua página para o caso contrário: quem
ganha um código, e como, é política sua, e nenhum dicionário aqui adivinha.

## O que sobrevive a quê

Duas perguntas aparecem depois que o programa já rodou um tempo, e as respostas
são deliberadas em vez de acidentais:

* **Uma fusão.** Quando dois contatos são fundidos e os dois carregam uma
  atribuição, a **mais antiga** sobrevive — primeiro toque aplicado por
  comparação, porque só uma atribuição pode viver no contato de destino.
* **Um apagamento.** As linhas de comissão sobrevivem ao contato. Dinheiro devido
  a quem fez a apresentação não deixa de ser devido quando a pessoa apresentada
  encerra a conta, então as linhas guardam os ids por valor e nenhuma chave
  estrangeira as arrasta junto.
