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

# Criando uma regra a partir de uma política

> Transforme uma frase de política escrita em uma regra do Tracer em produção: mapeie para campos, crie como rascunho, ensaie em uma conta de teste, ative-a e altere ou desative depois.

export const GMetadata = ({children}) => <Tooltip headline="Metadados" tip="Informações adicionais de chave-valor anexadas a entidades como contas ou transações, como IDs externos, números de referência ou códigos de departamento." cta="Ver glossário" href="/pt/start-here/glossary">
    {children}
  </Tooltip>;

export const GCEL = ({children}) => <Tooltip headline="CEL (Common Expression Language)" tip="Uma linguagem de expressão leve para escrever regras de negócio, por exemplo, 'if transaction amount > 10000 then REVIEW'. O Tracer usa CEL para regras de validação." cta="Ver glossário" href="/pt/start-here/glossary">
    {children}
  </Tooltip>;

Um analista de compliance entrega uma frase: *"recuse compras com cartão não presente acima de R\$ 5.000 vindas de um dispositivo que nunca vimos antes."*

Este guia transforma essa frase em uma regra que avalia do jeito que a política diz. Você ensaia essa regra onde ela não alcança ninguém, depois a coloca em produção.

**O que muda na sua operação:** a política deixa de viver em um chamado e passa a viver em um endpoint. A pessoa que escreveu a frase consegue ler a regra de volta. O ensaio passa pela avaliação real, em vez de uma planilha. Cada alteração na regra deixa um evento de auditoria registrado.

<Tip>
  **Para quem é este guia?** Analistas de risco e fraude que criam regras, e os desenvolvedores que conectam os campos da política à requisição de validação. Os passos 1 e 2 são sobre a política. Os passos 3 a 8 são chamadas de API.
</Tip>

## Antes de começar

***

* [ ] O Tracer em execução e acessível, com uma chave de API. Veja [Primeiros passos](./getting-started.mdx)
* [ ] As variáveis <GCEL>CEL</GCEL> e o modelo de scope. Veja [Motor de regras](./rule-engine.mdx)
* [ ] Um id de conta de teste para o qual você pode enviar validações, que nenhum tráfego de cliente usa
* [ ] A frase da política, anotada, com quem a escreveu disponível para uma pergunta

Todas as chamadas abaixo enviam a chave de API como `X-API-Key`.

***

## Passo 1: Mapeie a frase para campos

***

Uma regra lê o que a requisição de validação carrega. Divida a frase cláusula por cláusula e coloque cada uma na coluna a que pertence.

| Cláusula na política                   | De onde vem o valor                                         | Lida como                  |
| -------------------------------------- | ----------------------------------------------------------- | -------------------------- |
| "compras com cartão"                   | `transactionType`, um enum do Tracer                        | `CARD`                     |
| "acima de R\$ 5.000"                   | `amount` e `asset` na requisição                            | `amount`, `asset`          |
| "cartão não presente"                  | `subType`, texto livre que sua integração define            | `subType`                  |
| "um dispositivo que nunca vimos antes" | <GMetadata>metadados</GMetadata>, que sua integração define | `metadata.deviceFirstSeen` |

Os dois primeiros são campos que o Tracer define. Os dois últimos são campos **que sua integração precisa enviar**. O Tracer não tem opinião sobre o que significam "cartão não presente" ou "dispositivo novo". Essa é a pergunta para levar de volta a quem escreveu a política: *qual flag no nosso payload indica que o dispositivo é novo?*

<Note>
  `subType` chega às expressões em minúsculas, então `"card_not_present"` é a forma usada na comparação. Se sua integração já usa `subType` para outra coisa, carregue o modo de entrada em `metadata` e compare por ali. Os dois funcionam do mesmo jeito em uma expressão.
</Note>

Para a lista completa de variáveis e os campos que cada mapa de contexto carrega, veja [Motor de regras](./rule-engine.mdx#expressions).

***

## Passo 2: Divida a regra entre scope e expressão

***

Duas coisas naquela tabela (o tipo de transação e a conta) são coisas que o Tracer pode filtrar antes de uma expressão ser executada. Elas pertencem ao `scopes` da regra. As comparações de valor pertencem à `expression`.

**Scope**, que decide *se a regra é considerada ou não*:

```json theme={null}
"scopes": [{ "transactionType": "CARD" }]
```

**Expressão**, que decide *se a regra dispara*:

```cel theme={null}
subType == "card_not_present" && amount > 5000 && metadata.deviceFirstSeen == true
```

Leia a expressão ao lado da frase: modo de entrada, limite e o flag do dispositivo. `amount > 5000` é estritamente maior, então uma transação de exatamente `5000.00` não a dispara. Confira isso com a política antes de continuar, porque "acima de" e "a partir de" são regras diferentes.

Uma regra que lê uma chave de metadados que a requisição não carrega não corresponde, e as outras regras continuam sendo executadas. Por isso, a expressão acima não precisa de um teste de presença para `deviceFirstSeen`. Veja [Motor de regras](./rule-engine.mdx#expressions).

<Warning>
  Não repita condições de scope dentro da expressão. Ter `transactionType == "CARD"` nos dois lugares não está errado. Isso deixa dois lugares para editar quando a política muda, e a expressão é a que precisa percorrer todo o ciclo de vida para ser editada.
</Warning>

***

## Passo 3: Crie a regra como rascunho

***

`POST /v1/rules` cria a regra em `DRAFT`. Um rascunho nunca chega à avaliação, então nada que você faz aqui alcança o tráfego.

```bash theme={null}
curl -X POST http://localhost:4020/v1/rules \
  -H "X-API-Key: your-secure-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Deny CNP card purchases above BRL 5,000 from a new device",
    "description": "Compliance policy 2026-14, approved 2026-07-20",
    "expression": "subType == \"card_not_present\" && amount > 5000 && metadata.deviceFirstSeen == true",
    "action": "DENY",
    "scopes": [
      { "transactionType": "CARD", "accountId": "8a1f2c34-5d6e-4a7b-8c9d-0e1f2a3b4c5d" }
    ]
  }'
```

O `accountId` nesse scope é sua conta de teste. É isso que mantém o passo 4 fora do tráfego de clientes. O passo 5 o remove.

Um `201` responde com a regra armazenada:

```json theme={null}
{
  "ruleId": "4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68",
  "name": "Deny CNP card purchases above BRL 5,000 from a new device",
  "description": "Compliance policy 2026-14, approved 2026-07-20",
  "expression": "subType == \"card_not_present\" && amount > 5000 && metadata.deviceFirstSeen == true",
  "action": "DENY",
  "scopes": [
    { "transactionType": "CARD", "accountId": "8a1f2c34-5d6e-4a7b-8c9d-0e1f2a3b4c5d" }
  ],
  "status": "DRAFT",
  "createdAt": "2026-07-31T11:04:12.318Z",
  "updatedAt": "2026-07-31T11:04:12.318Z"
}
```

O Tracer preserva a caixa e os espaços internos do nome. Ele remove os espaços do início e do fim antes de armazenar. A unicidade do nome da regra diferencia maiúsculas de minúsculas dentro do contexto derivado dos scopes da regra. Assim, `FraudRule` e `fraudrule` são nomes diferentes, e o mesmo nome pode coexistir em contextos diferentes.

Pegue o `ruleId` da resposta. Esse é o identificador usado por todas as chamadas abaixo. Veja [Criar uma regra](/pt/reference/products/tracer/create-rule).

A expressão é compilada nessa chamada, então uma expressão que não pode ser executada nunca se torna um rascunho. Um erro de sintaxe responde `0340`, uma expressão que não retorna um booleano responde `0341`, e uma cujo custo estimado está acima do `CEL_COST_LIMIT` responde `0342`.

***

## Passo 4: Ensaie em uma conta que mais ninguém usa

***

O scope que você definiu é o que mantém o ensaio contido. A regra chega à avaliação apenas para transações naquela única conta de teste. A ativação a coloca na frente exatamente do tráfego que você enviar.

<Steps>
  <Step title="Ative a regra com o scope definido">
    ```bash theme={null}
    curl -X POST http://localhost:4020/v1/rules/4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68/activate \
      -H "X-API-Key: your-secure-api-key"
    ```

    A resposta retorna com `status: "ACTIVE"` e um `activatedAt`. Veja [Ativar uma regra](/pt/reference/products/tracer/activate-rule).
  </Step>

  <Step title="Envie uma transação que a política deve recusar">
    ```bash theme={null}
    TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)

    curl -X POST http://localhost:4020/v1/validations \
      -H "X-API-Key: your-secure-api-key" \
      -H "Content-Type: application/json" \
      -d '{
        "requestId": "c1a7f402-6b93-4d5e-8f10-2a4c6e8b0d31",
        "transactionType": "CARD",
        "subType": "card_not_present",
        "amount": "7500.00",
        "asset": "BRL",
        "transactionTimestamp": "'"$TS"'",
        "account": {
          "accountId": "8a1f2c34-5d6e-4a7b-8c9d-0e1f2a3b4c5d",
          "type": "checking",
          "status": "active"
        },
        "metadata": {
          "deviceFirstSeen": true
        }
      }'
    ```

    Veja [Validar uma transação](/pt/reference/products/tracer/validate-transaction).
  </Step>

  <Step title="Leia a decisão">
    ```json theme={null}
    {
      "validationId": "b7e3d190-4c25-4e8f-9a16-3d5f7b0c2e41",
      "requestId": "c1a7f402-6b93-4d5e-8f10-2a4c6e8b0d31",
      "decision": "DENY",
      "matchedRuleIds": ["4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68"],
      "evaluatedRuleIds": ["4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68"],
      "reason": "Rule matched with DENY action",
      "totalRulesLoaded": 1,
      "truncated": false,
      "limitUsageDetails": [],
      "processingTimeMs": 6.1,
      "evaluatedAt": "2026-07-31T11:09:44.207Z"
    }
    ```

    Seu `ruleId` em `matchedRuleIds` significa que o ensaio passou.
  </Step>

  <Step title="Envie os casos que não devem disparar">
    Altere um valor por vez e repita a chamada com um `requestId` novo: `"amount": "5000.00"` para o valor limite, `"deviceFirstSeen": false` para um dispositivo conhecido, `"subType": "purchase"` para uma venda com cartão presente. Cada uma deve voltar sem o seu `ruleId` em `matchedRuleIds`.
  </Step>
</Steps>

<Warning>
  Envie um `requestId` novo a cada tentativa. O `requestId` é a chave de idempotência. Repita um e o Tracer responde `200` com a decisão que já registrou para essa chave. A alteração que você acabou de fazer então parece não ter feito nada.
</Warning>

<Note>
  Um ensaio é uma validação real. Ele armazena um registro de decisão e grava um evento de auditoria, e uma decisão `ALLOW` consome os limites de gastos que cobrem aquela conta. É por isso que a conta de teste importa.
</Note>

Se o seu `ruleId` não estiver em `matchedRuleIds`, siga esta ordem. A regra está `ACTIVE` (`GET /v1/rules/{id}`)? A transação corresponde ao scope que você definiu? Os valores que você enviou satisfazem a expressão?

***

## Passo 5: Coloque em produção

***

Ir para produção significa uma única edição: remover a conta de teste do scope para que a regra se aplique à população que a política nomeia.

<Steps>
  <Step title="Pare de avaliar a versão de ensaio">
    Uma edição de scope não exige `INACTIVE`. Desativar primeiro faz a troca acontecer no momento que você controla e registra uma lacuna visível na trilha de auditoria. Cada instância serve regras a partir de uma cache que atualiza em um poll (a cada 10 segundos por padrão, `RULE_SYNC_POLL_INTERVAL_SECONDS`). Reserve essa janela para a desativação alcançar todas as instâncias. `GET /v1/rules` confirma o status armazenado, não que todas as instâncias já se atualizaram.

    ```bash theme={null}
    curl -X POST http://localhost:4020/v1/rules/4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68/deactivate \
      -H "X-API-Key: your-secure-api-key"
    ```

    O status muda para `INACTIVE`. Veja [Desativar uma regra](/pt/reference/products/tracer/deactivate-rule).
  </Step>

  <Step title="Substitua o scope">
    ```bash theme={null}
    curl -X PATCH http://localhost:4020/v1/rules/4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68 \
      -H "X-API-Key: your-secure-api-key" \
      -H "Content-Type: application/json" \
      -d '{ "scopes": [{ "transactionType": "CARD" }] }'
    ```

    `scopes` substitui todo o array. Envie todos os objetos de scope que você quer que a regra mantenha. Veja [Atualizar uma regra](/pt/reference/products/tracer/update-rule).
  </Step>

  <Step title="Ative">
    ```bash theme={null}
    curl -X POST http://localhost:4020/v1/rules/4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68/activate \
      -H "X-API-Key: your-secure-api-key"
    ```

    A ativação alcança a instância que atendeu essa chamada assim que ela é confirmada. Quando você roda várias instâncias atrás de um balanceador de carga, as outras pegam a mudança na próxima sincronização de regras (`RULE_SYNC_POLL_INTERVAL_SECONDS`, padrão `10`). A desativação percorre o mesmo caminho. Reserve essa mesma janela depois da ativação antes de considerar a regra em vigor em todas as instâncias.
  </Step>

  <Step title="Confirme o que está em produção">
    ```http theme={null}
    GET /v1/rules?status=ACTIVE&transaction_type=CARD&sort_by=updated_at
    X-API-Key: {api_key}
    ```

    A listagem responde "quais regras estão em vigor no tráfego de cartão agora". Veja [Listar regras](/pt/reference/products/tracer/list-rules). Para uma única regra, `GET /v1/rules/{id}` retorna a expressão e os scopes como estão armazenados ([Recuperar uma regra](/pt/reference/products/tracer/retrieve-rule)). As duas reportam o estado armazenado, não o que a cache de cada instância contém.
  </Step>
</Steps>

<Note>
  Uma validação carrega seu conjunto de regras uma vez, a partir da cache da instância que a atende, quando a chamada começa. Uma regra que é ativada enquanto uma validação está em andamento não faz parte dessa decisão. Decisões já registradas não mudam quando as regras mudam depois.
</Note>

***

## Passo 6: Saiba onde sua regra fica entre as outras

***

Regras não carregam um campo de prioridade nem uma ordenação para configurar. Regras cujo scope corresponde a uma transação são avaliadas juntas. A decisão vem da ação mais restritiva que disparou: primeiro uma regra `DENY`, depois um limite de gastos excedido, depois `REVIEW`, depois `ALLOW`, depois o padrão configurado para quando nada corresponde. O array `matchedRuleIds` carrega toda regra que correspondeu, qualquer que seja a ação que ela tenha.

Duas consequências para a regra que você acabou de escrever:

* **Uma regra `ALLOW` não isenta ninguém de uma regra `DENY`.** Se a política tem uma exceção (clientes VIP, um lojista parceiro), coloque-a dentro da expressão `DENY`. Adicione-a como mais uma condição que torna a regra mais restrita:

  ```cel theme={null}
  subType == "card_not_present" && amount > 5000 && metadata.deviceFirstSeen == true && metadata.customerTier != "vip"
  ```

  Observe o custo disso: a regra mais restrita agora lê `metadata.customerTier`, e uma requisição que não carrega essa chave não corresponde a ela.

* **Sua regra entra no conjunto que toda transação correspondente avalia.** `MAX_RULES_PER_REQUEST` limita quantas regras uma validação avalia. Quando o conjunto é maior, a resposta reporta `truncated: true`. Veja [variáveis de ambiente](./tracer-environment-variables.mdx).

A tabela de precedência e o raciocínio por trás dela estão na página [Motor de regras](./rule-engine.mdx#evaluation-pattern).

***

## Passo 7: Altere a regra quando a política muda

***

O que você faz depende do campo, não do desempenho da regra.

| O que mudou                                                                               | Como                                                                                    |
| ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| O limite, o modo de entrada, o flag do dispositivo, ou qualquer coisa dentro da expressão | Desative, depois `POST /v1/rules/{id}/draft`, depois `PATCH` na expressão, depois ative |
| `DENY` se torna `REVIEW`                                                                  | `PATCH /v1/rules/{id}` com a nova `action`                                              |
| Nome, descrição ou scopes                                                                 | `PATCH /v1/rules/{id}`                                                                  |

<Warning>
  A `expression` aceita uma edição apenas enquanto a regra está `DRAFT`. Enviar uma para uma regra em outro status responde `422` com o código de erro `0351`. Desativar não é suficiente por si só, porque `INACTIVE` não é `DRAFT`. `POST /v1/rules/{id}/draft` é o passo que as pessoas esquecem. Veja [Rascunhar uma regra](/pt/reference/products/tracer/draft-rule).
</Warning>

Um `PATCH` é armazenado quando responde, e chega à avaliação na próxima sincronização de regras. Quando a mudança importa até o minuto, desative primeiro e ative de novo depois. Essa sequência também coloca uma lacuna visível na trilha de auditoria em que a regra não esteve em vigor. Um revisor vai procurar essa lacuna.

O ciclo de vida é um conjunto fechado de movimentos: `DRAFT` ativa ou é excluído. `ACTIVE` desativa. `INACTIVE` volta para `DRAFT`, volta para `ACTIVE`, ou é excluído. `DELETED` é o fim. Qualquer outra coisa responde `422` com o código de erro `0349`, incluindo uma solicitação para rascunhar uma regra que ainda está `ACTIVE`.

***

## Passo 8: Desative ou exclua a regra

***

**Para parar de aplicar sem perder nada**, desative. A regra mantém sua expressão, seus scopes e seu histórico. Ela não chega mais à avaliação, e `POST /v1/rules/{id}/activate` a traz de volta. Esse é o movimento para uma política suspensa, sazonal ou em revisão.

**Para removê-la**, exclua. Desative primeiro, porque você não pode excluir uma regra em `ACTIVE`:

```http theme={null}
DELETE /v1/rules/4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68
X-API-Key: {api_key}
```

Um `204` responde em caso de sucesso. Veja [Excluir uma regra](/pt/reference/products/tracer/delete-rule).

O que a exclusão remove:

* A regra para de responder em `GET /v1/rules/{id}`, que passa a retornar `404` com o código de erro `0347`.
* Ela não aparece mais em `GET /v1/rules`, e `DELETED` não é um valor que o filtro `status` aceita.
* `DELETED` é o fim do ciclo de vida. Nenhum endpoint move uma regra para fora dele. Uma regra excluída volta apenas como uma nova regra que você cria de novo.

O que a exclusão deixa para trás:

* A trilha de auditoria mantém o ciclo de vida da regra, e o evento `RULE_DELETED` carrega a definição (nome, descrição, expressão, ação, scopes) como ela estava no momento da exclusão. Veja [Auditoria e compliance](./audit-compliance.mdx).
* As decisões que a regra produziu mantêm seu `ruleId` em `matchedRuleIds`. Uma negação de seis meses atrás ainda a nomeia. Veja [Revisando uma transação negada](./reviewing-a-denied-transaction.mdx).
* O nome fica disponível de novo para uma nova regra no mesmo contexto.

<Warning>
  Desative, depois leia a trilha de auditoria, depois exclua. Desativar é reversível em uma única chamada, e excluir não é reversível de forma alguma, então não há motivo para pular o estado intermediário.
</Warning>

***

## Armadilhas comuns

***

<Warning>
  **O que costuma dar errado ao transformar uma política em regra:**

  * **"A regra está ACTIVE, mas não corresponde a nada."** Verifique o campo em que a política mais se apoia. Uma regra que lê `metadata.deviceFirstSeen` não corresponde a nada se sua integração nunca enviar essa chave. A regra está correta e o payload está incompleto.
  * **"Ela disparou em uma transação que a política isenta."** Uma regra `ALLOW` não sobrepõe uma `DENY`. Coloque a isenção dentro da expressão `DENY` (passo 6).
  * **"Meu segundo ensaio retornou a primeira decisão."** `requestId` é a chave de idempotência. Envie um novo UUID a cada tentativa.
  * **"O PATCH rejeitou minha expressão com 422."** A regra não estava em `DRAFT`. Coloque-a lá primeiro (passo 7).
  * **"O nome que enviei não é o nome que recebo de volta."** O Tracer remove apenas os espaços do início e do fim. Ele preserva a caixa e os espaços internos. Referencie a regra pelo `ruleId`.
</Warning>

### Códigos de erro

| Código                   | Status | O que alterar                                                                                                     |
| ------------------------ | ------ | ----------------------------------------------------------------------------------------------------------------- |
| `0340`                   | 400    | A expressão não é interpretada como CEL                                                                           |
| `0341`                   | 400    | A expressão não retorna um booleano, use `amount > 5000`, não `amount`                                            |
| `0342`                   | 422    | O custo estimado da expressão está acima do `CEL_COST_LIMIT`                                                      |
| `0347`                   | 404    | Nenhuma regra tem esse `ruleId`, ou ela foi excluída                                                              |
| `0349`                   | 422    | O ciclo de vida não permite esse movimento, por exemplo excluir uma regra `ACTIVE`                                |
| `0351`                   | 422    | Uma edição de expressão em uma regra que não está `DRAFT`                                                         |
| `0353` / `0355` / `0357` | 400    | `name`, `expression` ou uma `action` válida está faltando                                                         |
| `0354` / `0356` / `0359` | 400    | `name` acima de 255, `expression` acima de 5000, ou `description` acima de 1000 caracteres                        |
| `0358`                   | 400    | Um objeto de scope sem nenhum campo definido, omita `scopes` inteiramente para uma regra global, nunca envie `{}` |
| `0360`                   | 400    | Mais de 100 objetos de scope em uma regra                                                                         |
| `0441`                   | 409    | Outra regra no mesmo contexto já usa esse nome                                                                    |
| `0065`                   | 400    | O id no caminho não é um UUID                                                                                     |
| `0082`                   | 400    | Um filtro em `GET /v1/rules` carrega um valor que o endpoint não aceita                                           |

A lista completa está em [Lista de erros do Tracer](/pt/reference/products/tracer/tracer-error-list).

***

## Referência rápida

***

| Passo                      | Método | Endpoint                    |
| -------------------------- | ------ | --------------------------- |
| Criar como rascunho        | POST   | `/v1/rules`                 |
| Começar a avaliar          | POST   | `/v1/rules/{id}/activate`   |
| Ensaiar ou verificar       | POST   | `/v1/validations`           |
| Parar de avaliar           | POST   | `/v1/rules/{id}/deactivate` |
| Reabrir para edição        | POST   | `/v1/rules/{id}/draft`      |
| Alterar campos             | PATCH  | `/v1/rules/{id}`            |
| Ler uma regra              | GET    | `/v1/rules/{id}`            |
| Ver o que está em produção | GET    | `/v1/rules`                 |
| Remover                    | DELETE | `/v1/rules/{id}`            |
