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

# Integração básica

> Do zero à primeira comissão: integração ponta a ponta.

Este guia percorre uma integração completa do Repass, do primeiro recurso até a comissão chegar a um payout. É mais profundo que o [Quickstart](/docs/quickstart): além dos passos do caminho feliz, ele explica o que acontece em cada transição (atribuição, snapshot de regra, hold, fechamento de ciclo e o gate fiscal) e como observar tudo isso pela API.

O Repass é multi-tenant: cada recurso é escopado pela sua organização. Toda a integração server-to-server usa uma [API key](/docs/autenticacao) com prefixo `rstr_` no header `Authorization: Bearer`, contra a base URL `https://api.userepass.com`. Antes de começar, vale conhecer as [convenções da API](/docs/convencoes/ids-e-recursos): IDs são opacos (prefixo de tipo + ULID), dinheiro é sempre inteiro em **centavos** e percentuais em **basis points** (`10000 bps = 100%`).

<Note>
  Os modelos de cobrança mais sofisticados (recorrência, tiers, clawback) ficam fora do escopo deste guia básico, mas estão linkados em cada passo. Aqui montamos a espinha dorsal: um afiliado que divulga, uma venda atribuída e uma comissão paga.
</Note>

## O fluxo completo

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant You as Sua integração (rstr_)
    participant API as API Repass
    participant V as Visitante
    participant Exec as Processamento Repass

    You->>API: POST /programs
    API-->>You: prog_…
    You->>API: POST /programs/{id}/commission-rules
    API-->>You: cmrl_… (versão 1)
    You->>API: POST /programs/{id}/affiliates
    API-->>You: aff_… (pending)
    You->>API: POST /affiliates/{id}/approve
    API-->>You: aff_… (approved) + link_… default
    V->>API: GET /t/{token} → 302 destino (+ cookie)
    You->>API: POST /conversions (server-to-server)
    API-->>You: conv_… (pending) + commission.created
    You->>API: GET /commissions?conversion_id=…
    API-->>You: comm_… (pending, em hold)
    Exec-->>API: fim do hold → commission.approved
    You->>API: POST /payouts/run (dryRun=false)
    API-->>You: pay_… (scheduled)
    Exec-->>API: liquidação PIX → payout.completed + commission.paid
```

Cada passo emite [eventos](/docs/conceitos/event-store) imutáveis a cada mudança de estado, então a história inteira é auditável e pode ser entregue por [webhooks](/docs/webhooks/visao-geral).

## Pré-requisitos

<Steps>
  <Step title="Tenha uma organização e uma API key admin">
    A organização é o seu tenant. Gere uma API key server-to-server (formato `rstr_…`) no painel da organização; ela herda a role do membro dono. Os passos abaixo incluem operações sensíveis (aprovar afiliado, fechar ciclo de payout), então use uma key com role `admin` ou `owner`.

    A key é mostrada **uma única vez**. Guarde-a em local seguro. Detalhes em [Autenticação](/docs/autenticacao).

    ```bash Exporte a key theme={null}
    export REPASS_KEY="rstr_..."
    export REPASS_BASE="https://api.userepass.com"
    ```

    <Warning>
      Nunca exponha uma `rstr_` em frontend ou repositório público: ela é uma credencial de servidor com os poderes do membro dono. Para ambientes de homologação e produção, use keys distintas; veja [Ambientes](/docs/convencoes/ambientes).
    </Warning>
  </Step>
</Steps>

## Passos

<Steps>
  <Step title="Crie o programa">
    Um [Programa](/docs/conceitos/programas-e-regras) concentra a configuração de todo o ciclo: moeda (`BRL`, imutável após criação), modelo e janela de [atribuição](/docs/conceitos/atribuicao), `holdDays` (carência da comissão, default 30) e `approvalMode` dos afiliados.

    ```bash cURL theme={null}
    curl -X POST "$REPASS_BASE/programs" \
      -H "Authorization: Bearer $REPASS_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: prog-integracao-001" \
      -d '{
        "name": "Programa de Indicações",
        "currency": "BRL",
        "attributionModel": "last_click",
        "approvalMode": "manual"
      }'
    ```

    ```json Resposta 201 theme={null}
    {
      "id": "prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y",
      "name": "Programa de Indicações",
      "currency": "BRL",
      "attributionModel": "last_click",
      "approvalMode": "manual",
      "holdDays": 30,
      "status": "active"
    }
    ```

    O programa nasce `active`. O status só muda pelos endpoints de ação (`pause` / `activate` / `archive`), nunca por `PATCH`. Veja [Programas e regras](/docs/conceitos/programas-e-regras) para todos os campos de configuração.
  </Step>

  <Step title="Publique uma regra de comissão">
    Sem uma regra vigente, a conversão até é atribuída, mas a comissão não tem como ser calculada. Crie a primeira versão da [regra de comissão](/docs/conceitos/comissoes) do programa: neste guia, um percentual simples sobre cada cobrança.

    O `percentage` é informado em **decimal** (`0` a `100`, passo `0,01`) e equivale a um valor em **basis points** (`percentage × 100`): `20.0%` corresponde a `2000` bps. A regra é **imutável**: alterar significa publicar uma nova versão; aplicar uma nova regra ao passado só via [Reprocessamento](/docs/conceitos/reprocessamento).

    ```bash cURL theme={null}
    curl -X POST "$REPASS_BASE/programs/prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y/commission-rules" \
      -H "Authorization: Bearer $REPASS_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: rule-integracao-001" \
      -d '{
        "type": "percentage",
        "percentage": 20.0,
        "recurrence": { "kind": "one_time" }
      }'
    ```

    ```json Resposta 201 theme={null}
    {
      "id": "cmrl_01J9ZQA1B2C3D4E5F6G7H8J9K0",
      "programId": "prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y",
      "version": 1,
      "type": "percentage",
      "percentage": 20.0,
      "recurrence": { "kind": "one_time" }
    }
    ```

    <Tip>
      `recurrence` controla por quais ciclos de cobrança a comissão é paga. `one_time` (default) comissiona só a primeira cobrança; `lifetime`, `months:N` e `decreasing` cobrem assinaturas recorrentes. Para faixas por volume (`tiered`), que exigem uma faixa-base `minCount: 0`, veja [Programas e regras](/docs/conceitos/programas-e-regras).
    </Tip>
  </Step>

  <Step title="Recrute e aprove um afiliado">
    O [Afiliado](/docs/conceitos/afiliados) é identificado por e-mail dentro do programa. Com `approvalMode: "manual"`, ele nasce `pending` e precisa de aprovação explícita.

    ```bash cURL (criar) theme={null}
    curl -X POST "$REPASS_BASE/programs/prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y/affiliates" \
      -H "Authorization: Bearer $REPASS_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: aff-integracao-001" \
      -d '{ "email": "parceiro@exemplo.com", "name": "Parceiro Exemplo" }'
    ```

    ```json Resposta 201 theme={null}
    {
      "id": "aff_01J9Z3M2P0RD8K4VC1XN6YHTBE",
      "programId": "prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y",
      "email": "parceiro@exemplo.com",
      "name": "Parceiro Exemplo",
      "status": "pending",
      "tier": null
    }
    ```

    Aprovar muda `pending → approved`. Nesse momento o Repass cria automaticamente um [Link](/docs/conceitos/links-e-cupons) default para o afiliado. Você não precisa criar o primeiro link à mão.

    ```bash cURL (aprovar) theme={null}
    curl -X POST "$REPASS_BASE/affiliates/aff_01J9Z3M2P0RD8K4VC1XN6YHTBE/approve" \
      -H "Authorization: Bearer $REPASS_KEY" \
      -H "Content-Type: application/json"
    ```

    ```json Resposta 200 theme={null}
    {
      "id": "aff_01J9Z3M2P0RD8K4VC1XN6YHTBE",
      "status": "approved"
    }
    ```

    <Note>
      O afiliado pode precisar aceitar uma versão dos [termos](/docs/conceitos/termos) do programa antes de divulgar. A máquina de estados completa (`pending`, `approved`, `paused`, `rejected`, `banned`) e o efeito de cada transição sobre comissões e payouts estão em [Afiliados](/docs/conceitos/afiliados).
    </Note>
  </Step>

  <Step title="Gere um link rastreável">
    Cada afiliado pode ter vários Links. O `token` é único globalmente e **imutável**; você pode informar um `subId` opcional para segmentar a divulgação (campanha, canal). O `destinationUrl` precisa estar na whitelist de domínios da organização (`GET`/`PUT /settings/destination-domains`).

    ```bash cURL theme={null}
    curl -X POST "$REPASS_BASE/affiliates/aff_01J9Z3M2P0RD8K4VC1XN6YHTBE/links" \
      -H "Authorization: Bearer $REPASS_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: link-integracao-001" \
      -d '{
        "destinationUrl": "https://app.exemplo.com/signup",
        "subId": "newsletter"
      }'
    ```

    ```json Resposta 201 theme={null}
    {
      "id": "link_01J9Z3N7R2SF9M5WD2YP4ZKUCG",
      "affiliateId": "aff_01J9Z3M2P0RD8K4VC1XN6YHTBE",
      "token": "k3y9q2",
      "destinationUrl": "https://app.exemplo.com/signup",
      "subId": "newsletter",
      "status": "active"
    }
    ```

    <Tip>
      Cupons sincronizados com o gateway de pagamento são uma forma **alternativa** de atribuição, sem clique, úteis para divulgação offline ou em vídeo. Veja [Links e cupons](/docs/conceitos/links-e-cupons).
    </Tip>
  </Step>

  <Step title="Instrumente o tracking">
    O afiliado divulga a URL pública de redirect `https://api.userepass.com/t/{token}`. Quando um visitante a acessa, o Repass registra o [Clique](/docs/conceitos/tracking) (identidade do visitante, geolocalização, detecção de bot, janela de expiração), seta um cookie first-party `repass_vid` e responde `302` para o destino, anexando o `repass_cid` na query.

    ```bash Redirect público (chamado pelo visitante) theme={null}
    curl -i "$REPASS_BASE/t/k3y9q2?sub=campanha-junho"
    # HTTP/1.1 302 Found
    # Location: https://app.exemplo.com/signup?repass_cid=...
    # Set-Cookie: repass_vid=...; Path=/; HttpOnly
    ```

    Para casar a venda ao clique mais tarde, propague essa identidade no seu produto. Há dois sinais que você pode capturar no signup/checkout e enviar de volta na conversão:

    * **`clickId`**: o identificador do clique (derivável do `repass_cid`); o sinal mais forte e direto.
    * **`visitorId`**: o valor do cookie `repass_vid`, que casa por identidade de visitante mesmo sem o `clickId`.

    Quando o usuário se identifica (ex.: cria a conta), você pode opcionalmente vincular o `visitorId` ao e-mail (hasheado) para casar conversões cross-device:

    ```bash POST /track/identify theme={null}
    curl -X POST "$REPASS_BASE/track/identify" \
      -H "Content-Type: application/json" \
      -d '{
        "visitorId": "vis_01J9Z3P9T4UG0N6XE3ZQ5ALVDH",
        "emailHash": "sha256:9c1185a5c5e9fc54612808977ee8f548b2258d31"
      }'
    ```

    <Note>
      As rotas `/t/{token}`, `/track/click` e `/track/identify` são **públicas** (não usam a API key). São chamadas pelo navegador do visitante ou pelo seu frontend. A engine de [atribuição](/docs/conceitos/atribuicao) usa todos esses sinais em cascata (`clickId` → `visitorId` → `emailHash` → fingerprint) para decidir a qual afiliado a venda pertence.
    </Note>
  </Step>

  <Step title="Ingira a conversão server-to-server">
    Quando a venda acontece, registre a [Conversão](/docs/conceitos/conversoes-e-fraude) via `POST /conversions`. A API roda atribuição **e** o score de [fraude](/docs/conceitos/conversoes-e-fraude) de forma síncrona e devolve a decisão completa na resposta.

    Informe o `clickId` quando o tiver; caso contrário, mande os identificadores que você capturou (`visitorId`, e-mail do cliente) para o matching multi-sinal. O `sourceEventId` é a sua chave de idempotência por fato de negócio: o mesmo `sourceEventId` retorna a conversão já existente, com `Idempotent-Replay: true`.

    ```bash cURL theme={null}
    curl -X POST "$REPASS_BASE/conversions" \
      -H "Authorization: Bearer $REPASS_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "sourceEventId": "order_8842",
        "type": "sale",
        "amountCents": 9990,
        "customer": { "email": "cliente@exemplo.com" },
        "visitorId": "vis_01J9Z3P9T4UG0N6XE3ZQ5ALVDH"
      }'
    ```

    A resposta é um envelope `{ attributed, deduplicated, replayed, conversion }`. Quando uma conversão nova é criada, vem `201` com `attributed: true` e a conversão completa (com seus snapshots) em `conversion`.

    ```json Resposta 201 (atribuída) theme={null}
    {
      "attributed": true,
      "deduplicated": false,
      "replayed": false,
      "conversion": {
        "id": "conv_01J9Z3Q5V6WH1P7YF4AR6BMWEI",
        "affiliateId": "aff_01J9Z3M2P0RD8K4VC1XN6YHTBE",
        "type": "sale",
        "amountCents": 9990,
        "status": "pending",
        "matchMethod": "visitor_id",
        "attributionSnapshot": { "model": "last_click", "windowDays": 30 },
        "ruleSnapshot": { "version": 1, "type": "percentage", "percentageBps": 2000 },
        "fraudSnapshot": { "score": 0.1, "decision": "approved" }
      }
    }
    ```

    Se o matching **não** encontra um afiliado, a API responde `200` com `attributed: false` e `conversion: null`, e não persiste nada: sem afiliado não há vínculo a registrar. Isso é esperado e não é um erro: significa que a venda não veio de um afiliado.

    ```json Resposta 200 (sem atribuição) theme={null}
    {
      "attributed": false,
      "deduplicated": false,
      "replayed": false,
      "conversion": null
    }
    ```

    <Note>
      Atribuição, regra e fraude são **congelados em snapshots** na conversão no momento do registro. Mudar a regra ou a chave PIX depois não altera o que já foi calculado. Em produção, conversões normalmente chegam pelo webhook do seu gateway de pagamento ou por `/ingest/custom`; veja [Ingestão](/docs/conceitos/ingestao) e o guia [Conversões server-to-server](/docs/guias/conversoes-server-to-server).
    </Note>
  </Step>

  <Step title="Acompanhe a comissão até a aprovação">
    Ao registrar uma conversão atribuída, o Repass cria automaticamente a [Comissão](/docs/conceitos/comissoes) do primeiro ciclo de cobrança. Liste as comissões filtrando pela conversão.

    ```bash cURL theme={null}
    curl "$REPASS_BASE/commissions?conversion_id=conv_01J9Z3Q5V6WH1P7YF4AR6BMWEI" \
      -H "Authorization: Bearer $REPASS_KEY"
    ```

    ```json Resposta 200 theme={null}
    {
      "data": [
        {
          "id": "comm_01J9Z3R1X8YJ2Q8ZG5BS7CNXFJ",
          "conversionId": "conv_01J9Z3Q5V6WH1P7YF4AR6BMWEI",
          "affiliateId": "aff_01J9Z3M2P0RD8K4VC1XN6YHTBE",
          "type": "standard",
          "billingCycle": 1,
          "amountCents": 1998,
          "status": "pending",
          "holdUntil": "2026-07-13T00:00:00.000Z"
        }
      ],
      "hasMore": false
    }
    ```

    A comissão nasce `pending` com `holdUntil = occurredAt + holdDays × 24h` (aqui, 30 dias). O detalhe (`GET /commissions/{id}`) traz o campo `calculation` aberto (base, percentual em bps, versão da regra aplicada) que reconstrói "por que esse valor?". No exemplo, `9990 × 2000 bps = 1998` centavos (half-up no centavo).

    A transição `pending → approved` acontece automaticamente ao fim do hold, **desde que** a conversão de origem esteja `approved` (fraude resolvida) e o afiliado não esteja `banned`. Você também pode aprovar antecipadamente uma comissão `pending` com `POST /commissions/{id}/approve` (aplica as mesmas guardas, exceto a do `holdUntil`).

    ```mermaid theme={null}
    stateDiagram-v2
        [*] --> pending: conversão atribuída<br/>commission.created
        pending --> approved: fim do hold<br/>ou aprovação antecipada<br/>commission.approved
        approved --> paid: payout concluído<br/>commission.paid
        pending --> voided: void / ban / refund total
        approved --> voided: void manual (sem payout)
        paid --> [*]
        voided --> [*]
    ```

    <Tip>
      O saldo agregado do afiliado (pendente, aprovado não pago, clawbacks, projeção do próximo payout) está em `GET /affiliates/{id}/balance`. O ciclo de vida completo da comissão (clawback, bônus, void, multi-touch) está em [Comissões](/docs/conceitos/comissoes).
    </Tip>
  </Step>

  <Step title="Feche o ciclo e pague (payout)">
    Comissões `approved` e não pagas de um afiliado são agregadas em um único [Payout](/docs/conceitos/payouts) no fechamento de ciclo. O fechamento tem **dry-run por padrão**: sempre inspecione a prévia antes de efetivar.

    Use `POST /payouts/preview` (ou `POST /payouts/run` sem `dryRun`) para ver quem recebe, quanto, e quais afiliados estão bloqueados, sem persistir nada.

    ```bash cURL (prévia) theme={null}
    curl -X POST "$REPASS_BASE/payouts/preview" \
      -H "Authorization: Bearer $REPASS_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "programId": "prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y" }'
    ```

    Para efetivar, repita com `dryRun: false` explícito. Isso cria os payouts `scheduled` e **reserva** as comissões (define `payoutId`), de modo que um segundo run não duplica nem dupla-cobra.

    ```bash cURL (efetivar) theme={null}
    curl -X POST "$REPASS_BASE/payouts/run" \
      -H "Authorization: Bearer $REPASS_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: run-2026-07-01" \
      -d '{
        "programId": "prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y",
        "dryRun": false
      }'
    ```

    ```json Resposta 200 (run dryRun=false) theme={null}
    {
      "dryRun": false,
      "totals": { "eligibleAmountCents": 1998, "eligibleCount": 1, "blockedCount": 0 },
      "eligible": [
        {
          "affiliateId": "aff_01J9Z3M2P0RD8K4VC1XN6YHTBE",
          "affiliateName": "Parceiro Exemplo",
          "method": "pix",
          "amountCents": 1998,
          "commissionCount": 1
        }
      ],
      "blocked": [],
      "payouts": [
        {
          "id": "pay_01J9Z4T0Z9AB1CD2EF3GH4JK5L",
          "affiliateId": "aff_01J9Z3M2P0RD8K4VC1XN6YHTBE",
          "status": "scheduled",
          "method": "pix",
          "amountCents": 1998,
          "commissionCount": 1
        }
      ]
    }
    ```

    <Warning>
      `dryRun` tem default `true`. Um `POST /payouts/run` sem corpo (ou sem `dryRun`) **não cria payouts**: apenas calcula a prévia. Envie `dryRun: false` explicitamente para fechar o ciclo.
    </Warning>

    O payout nasce `scheduled` com o destino (chave PIX) **snapshotado**. A liquidação via PIX acontece automaticamente (`scheduled → processing → completed`) e, ao concluir, marca todas as comissões incluídas como `paid`, emitindo `payout.completed` e um `commission.paid` por comissão.

    ```bash Acompanhe o status theme={null}
    curl "$REPASS_BASE/payouts/pay_01J9Z4T0Z9AB1CD2EF3GH4JK5L" \
      -H "Authorization: Bearer $REPASS_KEY"
    ```

    <Check>
      Pronto: você levou uma indicação do clique à comissão paga. O afiliado recebeu via PIX e cada passo deixou um rastro auditável no [event store](/docs/conceitos/event-store).
    </Check>
  </Step>
</Steps>

## O que muda quando a nota fiscal é exigida

Quando a política fiscal da organização exige NF (`nfMode: "affiliate_uploads"`), todo afiliado passa pelo gate de **nota fiscal antes da liquidação**: o fechamento cria uma invoice (`inv_…`) `pending` junto do payout, e a liquidação só ocorre depois que a NF fica `validated`. Enquanto isso, o payout permanece `scheduled`.

```mermaid theme={null}
sequenceDiagram
    actor Op as Você
    actor Af as Afiliado
    participant Run as payouts/run
    participant Exec as Liquidação PIX
    participant Inv as Invoice (fiscal)

    Op->>Run: run (dryRun=false)
    Run-->>Run: cria payout scheduled + invoice pending
    Exec->>Exec: NF pending → payout aguarda (fica scheduled)
    Af->>Inv: envia NF (PDF/XML) → validação de metadados
    Inv-->>Inv: invoice → validated
    Exec->>Exec: NF validated → liquida (completed)
```

Os detalhes da política `nfMode`, do upload e do gate de validação estão em [Fiscal](/docs/conceitos/fiscal).

## Observabilidade: eventos e webhooks

Toda mudança de estado emite um [Evento](/docs/conceitos/event-store). O fluxo deste guia produz, na ordem aproximada: `program.created`, `commission_rule.created`, `affiliate.created`, `affiliate.approved`, `link.created`, `click.recorded`, `conversion.created`, `commission.created`, `commission.approved`, `payout.created` e `payout.completed` (+ `commission.paid`).

Você pode consultar o histórico via `GET /events` (filtros por `type`, `aggregate_id`, período) ou assinar eventos em tempo real configurando um [webhook de saída](/docs/webhooks/visao-geral): entregas são assinadas com HMAC e idempotentes (deduplique pelo `id` do evento).

```bash Histórico de um afiliado theme={null}
curl "$REPASS_BASE/events?aggregate_type=affiliate&aggregate_id=aff_01J9Z3M2P0RD8K4VC1XN6YHTBE" \
  -H "Authorization: Bearer $REPASS_KEY"
```

## Idempotência e erros

Todo POST aceita o header `Idempotency-Key` (escopo por organização + usuário): repetir a mesma chave com o mesmo corpo devolve a resposta original com `Idempotent-Replay: true`. A ingestão de conversões tem uma camada extra de idempotência de negócio pelo `sourceEventId`. Veja [Idempotência](/docs/convencoes/idempotencia).

Erros seguem um envelope único `{ "error": { "type", "code", "message", "param?" } }` com status HTTP semântico:

```json Exemplo de erro 404 theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "resource_not_found",
    "message": "Affiliate not found."
  }
}
```

O mapeamento completo de códigos está em [Erros](/docs/convencoes/erros).

## Próximos passos

<CardGroup cols={2}>
  <Card title="Integrar Stripe" icon="credit-card" href="/docs/guias/integrar-stripe">
    Webhook + metadata `repass_cid` para atribuir cada venda ao afiliado.
  </Card>

  <Card title="Conversões server-to-server" icon="bolt" href="/docs/guias/conversoes-server-to-server">
    O fluxo completo de matching multi-sinal, atribuição e fraude.
  </Card>

  <Card title="Refund e clawback" icon="rotate-left" href="/docs/guias/refund-e-clawback">
    Como estornos e chargebacks geram comissões negativas auditáveis.
  </Card>

  <Card title="Reprocessar mudança de regra" icon="arrows-rotate" href="/docs/guias/reprocessar-mudanca-de-regra">
    Aplicar uma nova versão de regra retroativamente, com dry-run obrigatório.
  </Card>

  <Card title="Multi-touch e atribuição" icon="route" href="/docs/guias/multi-touch-atribuicao">
    Dividir a comissão entre vários afiliados que tocaram a jornada.
  </Card>
</CardGroup>
