Credits System

Deduct Credits

import { CreditsService } from "@readystart/api-core/billing/services/credits.service"

constructor(private readonly creditsService: CreditsService) {}

async handleRequest(req: CustomRequest) {
  const result = await this.creditsService.deductCreditsFromTenant({
    tenant_id: req.tenant.id,
    user_id: req.user.user_id,
    credits_to_deduct: 10,
    custom_reason: "API call",
  })

  if (!result.success) {
    // result.message: "No available credits" | "Credit limit exceeded"
    throw new ForbiddenException(result.message)
  }
}

Deduction Flow

1. Check member credit limit (tenant_member_credit_limits)
   ↓ If limit is set (!= -1) and used_credits >= credit_limit → return "Credit limit exceeded"
2. Query org's available credit packages (sorted by priority DESC, expires_at ASC)
   ↓ No packages → return "No available credits"
3. Deduct from packages by priority, record transactions in credit_transactions
4. If all packages exhausted and credits remain, the rest goes to overdraft (credit_overdrafts)
5. Update member usage (tenant_member_credit_limits.used_credits += credits_to_deduct)

Credit Package Priority

Credit packages are consumed by priority in descending order. Packages with the same priority are consumed by expiration date ascending (earliest expiring first).

Priority can be adjusted from the frontend Credits page.

Member Credit Limits

Owners can set a credit usage cap for each member:

Before deduction: credit_limit != -1 && used_credits >= credit_limit → reject
After deduction: used_credits += credits_to_deduct

A single request may exceed the limit (since token consumption is unpredictable), but the next request will be rejected.

Query Credits

// Query org's available credits (ordered by created_at DESC)
const result = await this.creditsService.getActiveByTenant(tenant_id)
// result.data = { all: CreditPackage[], total: number }