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

# Programas e regras de comissão

> Configure programas de afiliados e regras de comissão versionadas.

O **Programa** é a entidade-raiz da Repass: é nele que você define **como** o seu programa de afiliados funciona (moeda, modelo de atribuição, janela de atribuição, carência/hold e modo de aprovação de afiliados) e, por meio de **regras de comissão versionadas**, **quanto** se paga por conversão.

Uma organização pode manter múltiplos programas simultâneos e independentes. Tudo o que os demais módulos fazem (registrar cliques, aprovar afiliados, criar conversões, calcular comissões) lê a configuração do programa vigente no momento do fato e a congela em snapshots, garantindo auditabilidade.

<Info>
  IDs de programa têm o prefixo `prog_` seguido de um ULID (ex.: `prog_01J9Z…`). Regras de comissão usam `cmrl_` e tiers usam `tier_`. Veja [IDs e recursos](/docs/convencoes/ids-e-recursos).
</Info>

## O programa

Um programa concentra a configuração de comportamento de todo o ciclo de vida do afiliado e da comissão.

<ParamField body="name" type="string" required>
  Nome do programa (1 a 120 caracteres).
</ParamField>

<ParamField body="currency" type="string" default="BRL">
  Moeda de comissões e payouts. Atualmente aceita apenas `"BRL"`. **Imutável após a criação**: o campo `currency` não pode ser alterado via `PATCH`.
</ParamField>

<ParamField body="status" type="string" default="active">
  Ciclo de vida do programa: `active`, `paused` ou `archived`. Programas nascem `active`. Só muda pelos endpoints de ação (`pause` / `activate` / `archive`), nunca pelo `PATCH`.
</ParamField>

<ParamField body="approvalMode" type="string" default="manual">
  Como novos afiliados entram:

  * `automatic`: aprovado direto (`approved`).
  * `manual`: fica `pending` aguardando revisão.
  * `domain_whitelist`: aprovado se o domínio do e-mail estiver na `approvalDomainWhitelist` (case-insensitive); caso contrário, `pending`.
</ParamField>

<ParamField body="attributionModel" type="string" default="last_click">
  Modelo de atribuição de cliques: `first_click`, `last_click`, `linear`, `time_decay` ou `position_based`. Detalhes e fórmulas em [Atribuição](/docs/conceitos/atribuicao).
</ParamField>

<ParamField body="attributionWindowDays" type="integer" default={30}>
  Janela de atribuição (1 a 365 dias): define a validade do clique e o `max-age` do cookie (dias × 86 400 s). Veja [Tracking](/docs/conceitos/tracking).
</ParamField>

<ParamField body="holdDays" type="integer" default={30}>
  Carência (0 a 90 dias) até a comissão `pending` virar `approved`. O hold conta a partir de `conversion.occurredAt`, não da data de registro. Veja [Comissões](/docs/conceitos/comissoes).
</ParamField>

<ParamField body="approvalDomainWhitelist" type="string[]" default="null">
  Domínios aprovados automaticamente no modo `domain_whitelist` (até 50). Normalizados para minúsculas com deduplicação na escrita. No `PATCH`: omitido = inalterado, `null` = limpa, lista = substitui por inteiro.
</ParamField>

<ParamField body="maxLinksPerAffiliate" type="integer" default={50}>
  Limite de links ativos por afiliado (1 a 1000). Veja [Links e cupons](/docs/conceitos/links-e-cupons).
</ParamField>

<ParamField body="couponAttributionPolicy" type="string" default="coupon_wins">
  Desempate cupom × clique na conversão: `coupon_wins`, `click_wins` ou `split_50_50`.
</ParamField>

<ParamField body="commissionBasis" type="string" default="gross">
  Base de cálculo da comissão: `gross` (valor bruto) ou `net_of_gateway_fees` (líquido de taxas do gateway).
</ParamField>

<ParamField body="landingUrl" type="string" default="null">
  URL de destino padrão e fallback de links arquivados.
</ParamField>

<ParamField body="publicSignupEnabled" type="boolean" default={false}>
  Permite cadastro público de afiliados.
</ParamField>

### Criar um programa

Programas nascem com status `active`.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.userepass.com/programs \
    -H "Authorization: Bearer rstr_..." \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Programa Principal",
      "currency": "BRL",
      "approvalMode": "domain_whitelist",
      "approvalDomainWhitelist": ["parceiro.com.br"],
      "attributionModel": "last_click",
      "attributionWindowDays": 30,
      "holdDays": 30
    }'
  ```

  ```json Resposta theme={null}
  {
    "id": "prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y",
    "name": "Programa Principal",
    "currency": "BRL",
    "status": "active",
    "approvalMode": "domain_whitelist",
    "approvalDomainWhitelist": ["parceiro.com.br"],
    "attributionModel": "last_click",
    "attributionWindowDays": 30,
    "holdDays": 30,
    "maxLinksPerAffiliate": 50,
    "couponAttributionPolicy": "coupon_wins",
    "commissionBasis": "gross",
    "publicSignupEnabled": false,
    "landingUrl": null
  }
  ```
</CodeGroup>

<Tip>
  Todos os `POST` deste módulo aceitam `Idempotency-Key`. Um replay com o mesmo payload devolve a resposta original com o header `Idempotent-Replay: true`. Veja [Idempotência](/docs/convencoes/idempotencia).
</Tip>

### Atualizar a configuração

`PATCH /programs/:programId` aplica mudanças parciais: campo omitido permanece inalterado. `currency` e `status` **não** são editáveis aqui. Um `PATCH` sem mudança efetiva responde `200` com o estado vigente e **não** emite evento.

```bash cURL theme={null}
curl -X PATCH https://api.userepass.com/programs/prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y \
  -H "Authorization: Bearer rstr_..." \
  -H "Content-Type: application/json" \
  -d '{ "holdDays": 45, "couponAttributionPolicy": "split_50_50" }'
```

O `PATCH` emite `program.updated` com um diff (`before`/`after`) contendo **apenas** as chaves efetivamente alteradas.

## Ciclo de vida do programa

```mermaid theme={null}
stateDiagram-v2
    [*] --> active : POST /programs (program.created)
    active --> paused : POST /programs/:id/pause (program.paused)
    paused --> active : POST /programs/:id/activate (program.activated)
    active --> archived : POST /programs/:id/archive (program.archived)
    paused --> archived : POST /programs/:id/archive (program.archived)
```

Cada transição emite o evento indicado (com payload `{ before, after }`). Transições fora do diagrama, incluindo repetir o status atual (`active → active`) e qualquer saída de `archived`, são rejeitadas com **`409 conflict`**. `archived` é terminal.

| Status     | Efeito nos demais módulos                                                                                                                                                                                                                                 |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `active`   | Operação plena.                                                                                                                                                                                                                                           |
| `paused`   | Cliques continuam sendo registrados; novas conversões são criadas **sem comissão**, marcadas com `commission_skipped_reason: "program_paused"`; afiliados ainda podem ser cadastrados.                                                                    |
| `archived` | Nenhum clique novo é registrado (o link redireciona para a `landingUrl` com aviso, ou responde "gone" se não houver landing); novas conversões e novos afiliados são rejeitados com `409`; comissões existentes seguem seu ciclo normal até a liquidação. |

<Note>
  Arquivar bloqueia a **operação** (cliques, conversões, afiliados), não a **administração**: o `PATCH` de configuração e as mutações de tier (criar/renomear/reordenar/arquivar) continuam funcionando em programas arquivados.
</Note>

```bash cURL theme={null}
curl -X POST https://api.userepass.com/programs/prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y/pause \
  -H "Authorization: Bearer rstr_..." \
  -H "Content-Type: application/json"
```

## Regras de comissão

Uma regra de comissão define **quanto** o afiliado ganha. As regras são **versionadas e imutáveis por dono**: cada **dono** tem sua própria sequência independente de versões. Existem dois tipos de dono: a regra **padrão** do programa (`programTierId: null`) e **cada tier** (`programTierId` preenchido). Cada criação gera a versão `max + 1` **daquele dono**, atribuída atomicamente. Versões anteriores permanecem consultáveis para sempre: **não há endpoints de edição ou exclusão**. A regra "corrente" de cada dono é sempre a de maior versão **daquele dono**.

A regra padrão vive em `/programs/:programId/commission-rules` (+ `/current`); a regra própria de um tier vive em `/programs/:programId/tiers/:tierId/commission-rules` (+ `/current`). Um tier sem regra própria cai na regra padrão do programa (ver [Precedência de regras](#precedência-de-regras)).

<Warning>
  Uma nova versão de regra aplica-se **apenas a conversões futuras**. A regra aplicada a cada conversão é resolvida e snapshotada no momento da conversão; o cálculo da comissão usa sempre esse snapshot, nunca a regra "viva". Para aplicar uma nova versão ao passado, use [Reprocessamento](/docs/conceitos/reprocessamento).
</Warning>

### Tipos de regra

A Repass suporta três tipos:

<Tabs>
  <Tab title="percentage">
    Percentual sobre o valor da cobrança. O `percentage` é informado de `0` a `100` com precisão de `0,01` (ex.: `20.5`), e a resposta o devolve no mesmo formato.

    ```json theme={null}
    { "type": "percentage", "percentage": 20.5 }
    ```
  </Tab>

  <Tab title="fixed">
    Valor fixo por cobrança comissionável, em **centavos** (`fixedAmountCents` ≥ 1).

    ```json theme={null}
    { "type": "fixed", "fixedAmountCents": 5000 }
    ```
  </Tab>

  <Tab title="tiered">
    Faixas por volume de conversões aprovadas do afiliado (contagem acumulada ao longo de toda a vida da conta). Cada faixa define `minCount` (≥ 0) e **exatamente um** de `percentage` ou `fixedAmountCents`.

    ```json theme={null}
    {
      "type": "tiered",
      "tiers": [
        { "minCount": 0,  "percentage": 10 },
        { "minCount": 10, "percentage": 15 },
        { "minCount": 50, "percentage": 20 }
      ]
    }
    ```
  </Tab>
</Tabs>

A regra pode ainda restringir-se a produtos específicos via `applicableProductIds` (lista opcional com ≥ 1 ID). Uma conversão de produto fora da lista não gera comissão.

<Warning>
  Regras `tiered` **exigem uma faixa-base `minCount: 0`** (a taxa de partida), garantindo que todo volume tenha taxa definida. Uma lista de tiers sem a faixa-zero é rejeitada com `400 parameter_invalid`. Cada faixa deve ter **exatamente um** de `percentage` ou `fixedAmountCents`. Nenhum ou ambos também resulta em `400`.
</Warning>

No cálculo, uma regra `tiered` escolhe a faixa de **maior `minCount` ≤ total de conversões aprovadas do afiliado**. Por isso a faixa-base `minCount: 0` é o piso para qualquer volume.

### Recorrência

O campo `recurrence` controla por quais ciclos de cobrança a comissão é paga. O default é `{ "kind": "one_time" }`.

| `kind`       | Comportamento no cálculo                                                                                                                                                           |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `one_time`   | Comissiona apenas o primeiro ciclo de cobrança.                                                                                                                                    |
| `lifetime`   | Comissiona todos os ciclos, enquanto o cliente pagar.                                                                                                                              |
| `months`     | Comissiona ciclos ≤ `months` (1 a 120).                                                                                                                                            |
| `decreasing` | Percentual por step (`cycle` ≥ 1, ≥ 1 step): aplica o step de maior `cycle ≤ ciclo atual` e **persiste no último step** dali em diante. Os steps são percentuais e definem a taxa. |

```json theme={null}
{ "kind": "months", "months": 12 }
```

```json theme={null}
{
  "kind": "decreasing",
  "steps": [
    { "cycle": 1, "percentage": 30 },
    { "cycle": 4, "percentage": 20 },
    { "cycle": 7, "percentage": 10 }
  ]
}
```

<Note>
  Quando a recorrência é `decreasing`, os steps definem a taxa e **se sobrepõem ao `type` da regra**, inclusive às faixas de uma regra `tiered`. Combine `tiered` com `decreasing` com cautela.
</Note>

### Criar uma versão de regra

```bash cURL theme={null}
curl https://api.userepass.com/programs/prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y/commission-rules \
  -H "Authorization: Bearer rstr_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9f8e7d6c-..." \
  -d '{
    "type": "tiered",
    "tiers": [
      { "minCount": 0,  "percentage": 10 },
      { "minCount": 25, "percentage": 15 }
    ],
    "recurrence": { "kind": "months", "months": 6 }
  }'
```

```json Resposta theme={null}
{
  "id": "cmrl_01J9ZQA1B2C3D4E5F6G7H8J9K0",
  "programId": "prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y",
  "version": 1,
  "type": "tiered",
  "tiers": [
    { "minCount": 0,  "percentage": 10 },
    { "minCount": 25, "percentage": 15 }
  ],
  "recurrence": { "kind": "months", "months": 6 }
}
```

Para consultar o histórico de versões use `GET /programs/:programId/commission-rules`, a versão corrente via `GET /programs/:programId/commission-rules/current` e uma versão específica via `GET /programs/:programId/commission-rules/:ruleId`. Veja a aba Referência da API para o detalhamento.

## Tiers (níveis)

Tiers são uma lista **ordenada** de níveis (ex.: bronze / prata / ouro) que agrupam afiliados e podem ter uma regra de comissão **própria**. Cada tier tem um `name` (1 a 60 caracteres, único entre tiers ativos do programa) e uma `position` (≥ 0, única entre ativos). O nome do tier casa com o campo `tier` do afiliado. Cada tier mantém seu **próprio histórico** versionado de regra de comissão (em `/programs/:programId/tiers/:tierId/commission-rules`); um tier **sem regra própria** cai na regra padrão do programa.

Os tiers são geridos **individualmente**: criar, renomear, reposicionar, reordenar e arquivar (soft-delete). **Não** há mais substituição da lista inteira nem vínculo direto do tier a um ID de regra (o tier passa a ter seu próprio histórico de regras).

```bash Criar theme={null}
curl -X POST https://api.userepass.com/programs/prog_01J9ZP6Q8X3K7M2N4R5T6V7W8Y/tiers \
  -H "Authorization: Bearer rstr_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "ouro" }'   # position no fim, se omitida
```

```bash Renomear / reposicionar theme={null}
curl -X PATCH https://api.userepass.com/programs/prog_01J9.../tiers/tier_01J9... \
  -H "Authorization: Bearer rstr_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "platina" }'   # informe name e/ou position
```

```bash Reordenar theme={null}
curl -X PUT https://api.userepass.com/programs/prog_01J9.../tiers/order \
  -H "Authorization: Bearer rstr_..." \
  -H "Content-Type: application/json" \
  -d '{ "tierIds": ["tier_ouro", "tier_prata"] }'
```

```bash Arquivar (soft-delete, idempotente) theme={null}
curl -X POST https://api.userepass.com/programs/prog_01J9.../tiers/tier_01J9.../archive \
  -H "Authorization: Bearer rstr_..."
```

Arquivar é um **soft-delete** (`archivedAt` preenchido): o tier some da lista ativa preservando seu histórico e as conversões passadas, e os afiliados daquele tier voltam à regra padrão do programa. Os índices de unicidade de `name`/`position` valem **apenas entre tiers ativos**, então um nome/posição liberado por arquivamento pode ser reutilizado. As mutações de tier exigem papel **owner ou admin**. Veja [Afiliados](/docs/conceitos/afiliados) para a relação afiliado ↔ tier.

Para publicar / consultar a regra própria de um tier use `POST`/`GET /programs/:programId/tiers/:tierId/commission-rules` (e `/current`). A regra criada terá `programTierId` igual ao ID do tier.

## Precedência de regras

Para cada conversão, a regra efetiva é resolvida nesta ordem e o resultado é snapshotado na conversão com a marcação `precedence`:

```mermaid theme={null}
flowchart TD
    A[Conversão do afiliado X no programa P] --> B{afiliado tem customCommissionRuleId?}
    B -- sim --> C[Regra custom · precedence: custom]
    B -- não --> D{tier do afiliado existe, está ativo e tem regra própria?}
    D -- sim --> E[Regra corrente do tier, maior versão do tier · precedence: tier]
    D -- não --> F[Regra padrão corrente, maior versão do dono padrão · precedence: default]
    C --> G[Grava ruleSnapshot imutável na conversão]
    E --> G
    F --> G
```

A precedência é **custom → tier → padrão**. Um afiliado sem tier, com tier arquivado, ou cujo tier ainda não publicou regra própria **cai na regra padrão** do programa (`precedence: default`).

O cálculo da comissão usa **sempre** o snapshot, nunca a regra viva: mudanças futuras não afetam conversões passadas. Veja [Conversões e antifraude](/docs/conceitos/conversoes-e-fraude) e [Comissões](/docs/conceitos/comissoes).

## Erros comuns

Todos seguem o envelope `{ error: { type, code, message, param? } }`. Veja [Erros](/docs/convencoes/erros).

| HTTP  | `code`               | Quando                                                                                                                                                                                                                                                  |
| ----- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | `parameter_invalid`  | Corpo/parâmetro fora do schema: `name > 120`, `percentage > 100`, nome de tier `> 60`, `tierIds` vazio no reorder, `PATCH` de tier sem `name` nem `position`, faixa `tiered` sem `minCount: 0` ou sem exatamente um de `percentage`/`fixedAmountCents`. |
| `403` | `not_allowed`        | Membro sem papel owner/admin tentando criar/renomear/reordenar/arquivar tier.                                                                                                                                                                           |
| `404` | `resource_not_found` | Programa, regra ou tier inexistente (inclui regra `current` de tier sem regra própria, tier arquivado), inclusive recursos de outra organização (404, nunca 403, para não vazar existência).                                                            |
| `409` | `conflict`           | Transição de status não permitida (`param: "status"`); nome de tier já usado por outro tier ativo (`param: "name"`); posição de tier já ocupada por outro tier ativo (`param: "position"`).                                                             |

## Próximos passos

<CardGroup cols={2}>
  <Card title="Afiliados" icon="users" href="/docs/conceitos/afiliados">
    Cadastro, aprovação, tiers e regras custom por afiliado.
  </Card>

  <Card title="Comissões" icon="coins" href="/docs/conceitos/comissoes">
    Como o hold, a base de cálculo e o snapshot da regra geram cada comissão.
  </Card>

  <Card title="Atribuição" icon="route" href="/docs/conceitos/atribuicao">
    Modelos de atribuição e a janela de validade do clique.
  </Card>

  <Card title="Reprocessamento" icon="rotate" href="/docs/conceitos/reprocessamento">
    Aplique uma nova versão de regra retroativamente.
  </Card>
</CardGroup>
