Pular para o conteúdo

Integração

SDK

Cliente @beinfi/sdk: inscrição, medição de uso, meter e créditos.

Referência dos principais blocos do @beinfi/sdk (0.10.0). Se você só quer receber sem construir nada, comece por link de pagamento.

Cliente

import { Infi } from "@beinfi/sdk";
 
const infi = new Infi({ secretKey: process.env.INFI_SECRET_KEY! });
// atalho: new Infi(process.env.INFI_SECRET_KEY!)

O prefixo da chave decide o modo, e o modo decide os dois hosts: sk_test_ → api-sandbox.beinfi.com + app-sandbox.beinfi.com; sk_live_ → api.beinfi.com

  • app.beinfi.com. Pra apontar pra outro lugar (local, self-host, teste), passe apiUrl e/ou appUrl no construtor — não existe variável de ambiente pra isso.

Cobrança sem checkout do seu lado. Precisa de produto com versão publicada — veja catálogo:

const link = await infi.links.create(productId, { slug: "seu-tenant" });
link.url; // https://app-sandbox.beinfi.com/pay/seu-tenant/links/plink_… (sandbox)
 
await infi.links.list(productId, { slug: "seu-tenant" });
await infi.links.revoke(productId, link.id);

Identifique o pagador

A Infi não faz o login do seu usuário final — traga o seu auth (Clerk, Supabase, NextAuth, o seu) e inscreva o id que você já tem no produto:

const enrollment = await infi.products.enroll(productId, {
  externalId: seuUserId,          // o id do SEU auth (idempotente nele)
  email: "[email protected]",
  taxId: "52998224725",           // CPF/CNPJ: exigido por Pix e boleto
});
 
enrollment.id; // ← o id que toda chamada de cobrança referencia

products.enroll é a chamada recomendada: ela devolve a inscrição (enrollment) do cliente naquele produto. Existe também infi.customers.create(input), que cria um cliente no nível do tenant e recebe só o input — passar productId pra ela responde 400.

Use `.id` — não `.customerId`

A resposta do enroll traz dois ids diferentes, e o campo chamado customerId não é o que as chamadas de cobrança querem:

const e = await infi.products.enroll(productId, { externalId: seuUserId });
e.id;          // 7b4e3712-…  ← é este que você guarda e passa adiante
e.customerId;  // f7701d03-…  ← o cliente no nível do tenant, outro id

O parâmetro se chama customerId mas espera e.id. Passar e.customerId responde 422 (customerId: unknown customer):

await infi.track({ customerId: e.id,         productId, meter: "tokens", value: "1" }); // ok
await infi.track({ customerId: e.customerId, productId, meter: "tokens", value: "1" }); // 422

Guarde e.id associado ao seu usuário. É a única coisa que você precisa persistir do nosso lado.

Wallet: enroll + saldo numa chamada

Se o seu produto é pré-pago (o cliente compra crédito antes de usar), wallet.forCustomer faz a inscrição, aplica o saldo inicial e devolve tudo que você precisa depois:

const wallet = await infi.wallet.forCustomer(seuUserId, {
  productKey: "ai-chat",
  starterCredits: "2000", // opcional — prefira grants[] no company file
});
// wallet.enrollmentId, wallet.productId, wallet.defaultMeter, wallet.balance()

O saldo ainda não é isolado por meter

A API é por meter (wallet.balance("tokens"), wallet.debit("exports", …)), mas hoje existe um saldo só por inscrição: pedir o saldo de dois meters diferentes devolve o mesmo número (verificado com @beinfi/[email protected]). O meter vai na referência do lançamento, então o histórico separa — o saldo, não. Não conte com isolamento por meter ainda.

Grants do plano (on: cycle | payment) vivem em infi.company.ts — veja company as code.

Medição de uso

await infi.track({
  customerId: enrollment.id,
  productId,                // obrigatório quando o tenant tem mais de um produto
  meter: "tokens",
  value: "1200",
});
 
await infi.trackBatch([
  { customerId: enrollment.id, productId, meter: "tokens", value: "1200" },
  { customerId: enrollment.id, productId, meter: "api_call", value: "1" },
]);

Fora do caminho crítico

track registra uso sem segurar request nem run de agente. Ele responde { eventId, accepted, duplicate } — duplicate: true é dedupe, não erro.

LLM medido

meter checa saldo, roda a chamada e registra uso. Sem saldo → InsufficientCreditError (402) antes de gastar.

import { InsufficientCreditError } from "@beinfi/sdk";
 
try {
  const res = await infi.meter(
    { customerId: enrollment.id, productId, meter: "tokens" },
    () => openai.chat.completions.create({ model: "gpt-4o", messages }),
  );
} catch (err) {
  if (err instanceof InsufficientCreditError) {
    // 402 / upsell — err.balance é o saldo atual
  }
}

Streaming (baixa explícita no onFinish, porque o total de tokens só existe no fim):

await infi.meter(
  { customerId: enrollment.id, productId, meter: "tokens", mode: "streaming" },
  () =>
    streamText({
      onFinish: ({ usage }) =>
        infi.customers.credits.consume(enrollment.id, {
          amount: String(usage.totalTokens ?? 0),
        }),
    }),
);

Estado agregado (saldo + assinaturas + uso do período):

const state = await infi.customers.state(enrollment.id);
// { customer, credit: { balance, total }, subscriptions, usage }
 
const report = await infi.usage.get({ customerId: enrollment.id });
// { from, to, meters: [{ meter, unit, totalValue, eventCount }] }

Erros

try {
  await infi.links.create(productId, { slug });
} catch (err) {
  err.status;   // 422
  err.code;     // "validation_failed"
  err.errors;   // [{ field: "productId", description: "product has no published version…" }]
}

InfiError.errors[] é onde vive o motivo real de um 422 — é o campo que responde "por que essa escrita foi recusada". err.fix traz remediação quando a API manda uma.

React

import { UsagePanel } from "@beinfi/sdk/react";
 
const state = await infi.customers.state(enrollment.id); // server only
<UsagePanel state={state} creditLabel="créditos" />;