Começar
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 link de pagamento e SDK. Use a CLI se você prefere catálogo em git a catálogo no dashboard.
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.
Arquivo
// 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.
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".
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 empayment.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:
{ "balances": [ { "meter": "tokens", "balance": "50000", "total": "50000" } ] }`/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.
`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".
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:
{ 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.
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
webhooks.
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.