Acceso y consumo

Conecta los límites de tus planes a las acciones de tu aplicación.

Define una cuota y asígnala a un plan

Preparar el catálogo
await billete.features.create({
  key: "tokens",
  name: "Tokens",
  type: "metered",
  unit: "tokens",
  reset: { every: "billing" },
});

await billete.plans.create({
  name: "Pro",
  currency: "USD",
  prices: [{ months: 1, amount: 1000 }],
  features: { tokens: 100_000 },
});

El cliente obtiene las cuotas a través de su suscripción. customer admite el ID de Billete o tu externalId. Una cuota se identifica por su clave (features.retrieve("tokens"), features.check("user_123", "tokens")) o por su ID feat_, en todos los métodos.

Consume antes de ejecutar

Autorizar un trabajo
const usage = await billete.features.consume(
  "user_123",
  "tokens",
  850,
  { idempotencyKey: "tokens-job-example-001" },
);

if (!usage.allowed) {
  throw new Error("No tienes cuota suficiente para esta operación");
}

La operación es atómica: si supera el límite, no consume nada. La idempotencia de la petición evita repetir el consumo; tu propio trabajo también debe ejecutarse una sola vez.

Otras operaciones

const access = await billete.features.check("user_123", "tokens");
const enabled = await billete.features.isEnabled("user_123", "export-pdf");
const features = await billete.features.listForCustomer("user_123");

Solo las cuotas count permiten release(customer, key, amount, options) y set(customer, key, value, options). Para metered, registra una operación: toda devolución vuelve al periodo original, aunque ya haya empezado otro.

Reservar trabajos y confirmar su consumo real

const { allowed, operation } = await billete.features.reserve("org_123", "tokens", 40, {
  member: "user_42", operation: job.id, expiresInSeconds: 600,
});
if (!allowed || !operation) return quotaExceeded();
if (operation.status !== "reserved") return alreadyHandled(operation);

const result = await generate().catch(async (error: unknown) => {
  await billete.features.operations.release(operation.id);
  throw error;
});
await billete.features.operations.commit(operation.id, { amount: result.tokensUsed });

Tu cola debe garantizar que solo un worker ejecute cada trabajo. Billete evita descontar cuota dos veces; no ejecuta ni bloquea tus trabajos. Elige un vencimiento mayor que su duración máxima.

Una respuesta de confirmación perdida se reintenta con el mismo monto o se consulta con features.operations.retrieve(id). Nunca liberes por una duda de red: si está committed, ya quedó; si sigue reserved, reintenta. operation_expired significa que el trabajo terminó sin reserva: tu aplicación decide si lo entrega y registra el consumo o lo descarta.

features.operations.release(id) libera una reserva; features.operations.refund(id) devuelve consumo confirmado. Ambas son idempotentes. Para consumo inmediato idempotente usa consume(customer, key, amount, { operation: job.id, member }). Las operaciones pueden cerrarse después de quitar al miembro o cambiar de plan.

Al crear una cuota, per: "member" mantiene un contador separado por miembro. Pasa { member: "user_42" } a check, consume, set o listForCustomer. Las consultas sin miembro muestran capacidades del cliente; los contadores individuales son null.