Entitlements, balances and usage
Check access, report usage, manage balances and Entities, and bill AI calls.
API v1Updated
Check access and report usage
Call checkEntitlement before running a feature and trackUsage after it succeeds. Quantities are decimal strings.
1import { createIndianaServer } from "@tinkerlandai/indiana-sdk/server";2 3const indiana = createIndianaServer({4 baseUrl: process.env.INDIANA_API_URL!,5 apiKey: process.env.INDIANA_API_KEY!,6 scope: { projectId: process.env.INDIANA_PROJECT_ID!, environment: "sandbox" },7});8const featureId = process.env.INDIANA_FEATURE_ID!;9 10async function main() {11 // 1. Find or create the Customer for your signed-in user.12 const { customer } = await indiana.getOrCreateCustomer({ externalUserId: "user-42" });13 14 // 2. Check access before doing the work.15 const { check } = await indiana.checkEntitlement({16 customerId: customer.id,17 requirement: { internal_feature_id: featureId, quantity: "1", properties: {} },18 });19 if (!check.allowed) return console.log("blocked:", check.reason);20 21 // 3. Report usage after the work succeeds. Use your own event ID in production.22 const { receipts } = await indiana.trackUsage({23 customerId: customer.id,24 command: { internal_feature_id: featureId, quantity: "1", idempotencyKey: crypto.randomUUID() },25 });26 console.log("recorded:", receipts[0]?.accepted);27}28 29main().catch((error) => {30 console.error(error);31 process.exitCode = 1;32});
- Generate and save an
idempotencyKey per usage; retrying an unknown result with the same key never bills twice. - HTTP 200 does not mean usage was accepted: check each receipt's
accepted and reason.
Reserve, then confirm
For long-running work, reserve credits first, confirm the actual quantity when it finishes, or release them on failure.
1import { createIndianaServer } from "@tinkerlandai/indiana-sdk/server";2 3const indiana = createIndianaServer({4 baseUrl: process.env.INDIANA_API_URL!,5 apiKey: process.env.INDIANA_API_KEY!,6 scope: { projectId: process.env.INDIANA_PROJECT_ID!, environment: "sandbox" },7});8 9const feature = process.env.INDIANA_FEATURE_ID!;10 11// Hold credits before long-running work; keys are persisted per job.12export async function runJob(customerId: string, jobId: string, estimate: string) {13 const { receipt } = await indiana.mutateBalance({14 customerId,15 command: {16 operation: "reserve",17 internal_feature_id: feature,18 quantity: estimate,19 properties: {},20 idempotencyKey: `${jobId}:reserve`,21 expiresInSeconds: 600,22 },23 });24 if (!receipt.accepted) return { started: false as const, reason: receipt.reason };25 return { started: true as const, reservationId: receipt.id };26}27 28export async function finishJob(customerId: string, jobId: string, reservationId: string, actual: string | null) {29 const command = actual30 ? { operation: "confirm" as const, operationId: reservationId, quantity: actual, idempotencyKey: `${jobId}:confirm` }31 : { operation: "release" as const, operationId: reservationId, idempotencyKey: `${jobId}:release` };32 const { receipt } = await indiana.mutateBalance({ customerId, command });33 return receipt;34}
Read balances and the ledger
balances.tsTypeScript 1import { createIndianaServer } from "@tinkerlandai/indiana-sdk/server";2 3const indiana = createIndianaServer({4 baseUrl: process.env.INDIANA_API_URL!,5 apiKey: process.env.INDIANA_API_KEY!,6 scope: { projectId: process.env.INDIANA_PROJECT_ID!, environment: "sandbox" },7});8 9export async function readBalances(customerId: string) {10 const { holdings } = await indiana.readHoldings({ customerId });11 const { ledger } = await indiana.readLedger({12 customerId,13 query: { type: null, from: null, to: null, after: null, limit: 20 },14 });15 return { holdings, ledger };16}
Balance management| Task | Methods |
|---|
| Grant or claw back credits | manageBalanceGrant, listBalanceGrants |
| Correct one balance | adjustBalance, readBalanceSource |
| Set usage | setUsage, readUsage |
| Report usage in bulk | submitUsageBatch, readUsageBatch |
| Statements and analytics | readCreditStatement, readUsageAnalytics, readCreditAnalytics |
| Meter per workspace or device | manageUsageEntity |
| Assign seats | readSeats, manageSeat |
Bill AI calls
Pass the middleware to the AI SDK's wrapLanguageModel; each call is billed by actual token usage.
ai-billing.tsTypeScript 1import {2 createAiUsageMiddleware,3 createConfiguredAiPricing,4 createIndianaServer,5 type AiUsageRecord,6} from "@tinkerlandai/indiana-sdk/server";7 8const indiana = createIndianaServer({9 baseUrl: process.env.INDIANA_API_URL!,10 apiKey: process.env.INDIANA_API_KEY!,11 scope: { projectId: process.env.INDIANA_PROJECT_ID!, environment: "sandbox" },12});13 14// Your own durable store for usage records and frozen quotes.15declare function saveUsageRecord(record: AiUsageRecord): Promise<void>;16 17export async function billingMiddleware(customerId: string, callId: string) {18 const featureId = process.env.INDIANA_AI_FEATURE_ID!;19 const pricing = await createConfiguredAiPricing(indiana, {20 internal_feature_id: featureId,21 customerId,22 });23 return createAiUsageMiddleware({24 pricing,25 client: indiana,26 modelId: "openai/gpt-4o",27 reasoning: "output",28 context: () => ({ customerId, internal_feature_id: featureId, idempotencyKey: callId }),29 persist: saveUsageRecord,30 });31}32// Pass the result to wrapLanguageModel({ model, middleware }) from the AI SDK.
- Use a new
idempotencyKey per model call and store records in persist to recover after failures. - Write
modelId as provider/model; your backend needs access to https://models.dev for model prices.
Related operations
- Entitlements and product presentation (4)
checkEntitlement, resolveOffering, resolvePaywall, readHoldings- Balances, usage and ledger (16)
readBalanceSource, adjustBalance, readUsage, setUsage, manageBalanceGrant, listBalanceGrants, manageUsageEntity, readUsageAnalytics, readCreditAnalytics, submitUsageBatch, readUsageBatch, readLedger, readCreditStatement, quoteBalance, trackUsage, mutateBalance- Seats (2)
readSeats, manageSeat- AI pricing (1)
readAiPricing
Related: Paths, credentials and scopes in All operations