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

# Company as code

> Declare tenant, produtos, apps e webhooks em infi.company.ts — sync como Terraform.

**Company as code** configura seu catálogo por arquivo: um TypeScript versionado
no git, aplicado com a CLI (plan/apply), sem clicar no dashboard pra cada mudança.

Isso é ferramenta de setup, não parte do seu app — quem constrói contra o Infi em
runtime usa <a href="/link-de-pagamento">link de pagamento</a> e
<a href="/sdk">SDK</a>. Use a CLI se você prefere catálogo em git a
catálogo no dashboard.

<Info>
  **A CLI deduz o host da chave.** A partir da `0.2.0` ela resolve o host pelo prefixo da chave (`sk_test_` →
  sandbox, `sk_live_` → produção), então `INFI_API_URL` só serve pra apontar pra
  outro lugar de propósito. Antes da 0.2.0 ele era obrigatório em sandbox.
</Info>

## Arquivo

```ts theme={null}
// infi.company.ts
import { defineCompany } from "@beinfi/sdk";

export default defineCompany.fromIntent("prepaid-ai-chat");

// ou hand-authored:
export default defineCompany({
  products: [
    {
      key: "ai-chat",
      name: "AI Chat",
      pricingModel: "prepaid",
      billingCycle: "monthly",
      basePrice: "19.90",
      meters: [{ key: "tokens", unit: "token", aggregation: "sum" }],
      // Plan grants — creditam o saldo daquele meter na inscrição do cliente
      grants: [{ meter: "tokens", amount: "50000", on: "cycle" }],
    },
  ],
  webhooks: [{ url: "https://seu-app.com/api/webhooks/infi", events: ["payment.confirmed"] }],
});
```

`defineBilling` / `infi.billing.ts` ainda funcionam como aliases.

<Info>
  **O arquivo é carregado como ESM.** A CLI importa o `.ts` direto. Se o `package.json` do projeto não tem
  `"type": "module"`, o load falha com *"Cannot use import statement outside a
  module"*.
</Info>

## Intents

Atalhos que geram um company file sensato. Vivem na CLI e no arquivo — o endpoint
público de provisionamento **não** aceita `intent`:

| Intent            | Uso típico                      |
| ----------------- | ------------------------------- |
| `crm`             | SaaS B2B / CRM                  |
| `prepaid-ai-chat` | Chat/LLM com créditos por meter |
| `one-time`        | Pack / ebook / cobrança única   |
| `usage-saas`      | Pay-as-you-go metered           |

## Comandos

| Comando                              | Pra quê                                   | Estado hoje |
| ------------------------------------ | ----------------------------------------- | ----------- |
| `infi claim create --ref cli --json` | Provisiona tenant claimable + chave       | ok          |
| `infi sync infi.company.ts`          | Aplica o estado desejado                  | ok          |
| `infi sync infi.company.ts --plan`   | Dry-run (diff)                            | ok          |
| `infi pull`                          | Backend → `infi.company.ts`               | ok          |
| `infi doctor --json`                 | Saúde do setup (checks + hints)           | ok          |
| `infi go-live --json`                | Guidance claim → conta → KYC → `sk_live_` | ok          |
| `infi bootstrap --intent …`          | Claim + company file + sync + doctor      | ok          |

## Grants do plano

Cada produto pode declarar `grants[]`:

* `on: "cycle"` — credita no abrir/renovar o período (assinatura/prepaid)
* `on: "payment"` — credita em `payment.confirmed` (packs one-time)

O `meter` do grant é real: **cada meter tem a sua carteira**. Um grant em
`tokens` credita a carteira de `tokens`, e `GET /metering/customers/{id}/wallet`
devolve o saldo de cada uma:

```json theme={null}
{ "balances": [ { "meter": "tokens", "balance": "50000", "total": "50000" } ] }
```

<Warning>
  **`/credit` é legado e responde outra pergunta.** `GET /metering/customers/{id}/credit` lê **só** o pool `credits` legado. Numa
  inscrição com 50.000 em `tokens` ele responde `0` — não é saldo zerado, é a
  carteira errada. Use `/wallet?meter=…`. A SDK já faz isso sozinha desde a
  0.11.1: `infi.meter({ meter: "tokens" })` gateia contra a carteira daquele meter.
</Warning>

<Info>
  **`creditsPerCycle` ainda existe (mas é legado).** Ele continua nos tipos e continua sendo honrado: a regra é *primeiro
  `grants[{ on: "cycle" }]`, e `creditsPerCycle` como fallback*. Ou seja, arquivo
  antigo não quebra — mas escreva `grants[]` em código novo, que é o único jeito de
  creditar um meter específico ou de creditar `on: "payment"`.
</Info>

<Info>
  **Versão `prepaid` precisa de preço para publicar.** Publicar uma versão `prepaid` exige `basePrice` positivo **ou** um preço de
  meter publicado nela. Sem nenhum dos dois o publish responde `422` e o produto
  fica em `draft` — cobrável por ninguém.

  Ou seja: dá para ter tier grátis (sem mensalidade) desde que o meter tenha
  preço, que é o que rateia o consumo da carteira:

  ```ts theme={null}
  { key: "studio", type: "agent", pricingModel: "prepaid", billingCycle: "monthly",
    meters: [{ key: "tokens", unit: "token", aggregation: "sum", valueProperty: "value" }],
    grants: [{ meter: "tokens", amount: "50000", on: "cycle" }],
    prices: [{ meter: "tokens", model: "per_unit", unitAmount: "0.00004", currency: "BRL" }] }
  ```

  Toda `price` é taxa de meter: um valor fixo não é `price`, é o `basePrice` da
  versão.
</Info>

## Webhooks no arquivo

O `webhooks[]` do company file é aplicado pelo `sync` — e em sandbox isso bate no
`503 secret_store_unavailable`, porque tenant de teste não tem cofre de segredos.
Não é erro de sintaxe do seu arquivo. Veja
<a href="/webhooks">webhooks</a>.

<Tip>
  Agentes: rodem `infi doctor --json`, e em qualquer falha leiam `InfiError.errors[]`
  — é onde vem `{ field, description }` dizendo o que a API recusou. `fix.command` /
  `hint` aparecem só em alguns códigos de erro.
</Tip>
