Skip to main content
Company as code configures your catalog from a file: a TypeScript file versioned in git, applied with the CLI (plan/apply), instead of clicking through the dashboard for every change. This is a setup tool, not part of your app — whoever builds against Infi at runtime uses payment link and SDK. Use the CLI if you prefer a catalog in git to a catalog in the dashboard.
The CLI infers the host from the key. From 0.2.0 on it resolves the host from the key prefix (sk_test_ → sandbox, sk_live_ → production), so INFI_API_URL is only for deliberately pointing somewhere else. Before 0.2.0 it was required in sandbox.

The file

defineBilling / infi.billing.ts still work as aliases.
The file is loaded as ESM. The CLI imports the .ts directly. If the project’s package.json has no "type": "module", the load fails with “Cannot use import statement outside a module”.

Intents

Shortcuts that generate a sensible company file. They live in the CLI and in the file — the public provisioning endpoint does not accept intent:

Commands

Plan grants

Each product may declare grants[]:
  • on: "cycle" — credits when the period opens or renews (subscription/prepaid)
  • on: "payment" — credits on payment.confirmed (one-time packs)
A grant’s meter is real: each meter has its own wallet. A grant on tokens credits the tokens wallet, and GET /metering/customers/{id}/wallet returns each balance:
/credit is legacy and answers a different question. GET /metering/customers/{id}/credit reads only the legacy credits pool. On an enrollment with 50,000 in tokens it answers 0 — that is not an empty balance, it is the wrong wallet. Use /wallet?meter=…. The SDK does this on its own since 0.11.1: infi.meter({ meter: "tokens" }) gates against that meter’s wallet.
creditsPerCycle still exists (but is legacy). It is still in the types and still honoured: the rule is grants[{ on: "cycle" }] first, and creditsPerCycle as a fallback. So an old file does not break — but write grants[] in new code, which is the only way to credit a specific meter or to credit on: "payment".
A prepaid version needs a price to publish. Publishing a prepaid version requires a positive basePrice or a meter price published on it. With neither, publish answers 422 and the product stays in draft — chargeable by nobody.Which means a free tier (no monthly fee) works as long as the meter has a price, which is what rates the wallet’s consumption:
Every price is a meter rate: a fixed amount is not a price, it is the version’s basePrice.

Webhooks in the file

The company file’s webhooks[] is applied by sync — and in sandbox that hits 503 secret_store_unavailable, because a test tenant has no secret store. It is not a syntax error in your file. See webhooks.
Agents: run infi doctor --json, and on any failure read InfiError.errors[] — that is where { field, description } says what the API refused. fix.command / hint only appear on some error codes.