vevee.canUse()POST /api/v1/can-usesk_live_

Read-only check. Returns whether the user is currently allowed to consume the given quantity, plus a per-group breakdown. Does not increment any counter - use for UI gating and pre-flight checks, not for enforcement.

Signatures

canUse(
  userId: string,
  event: string,
  quantity?: number,
  metadata?: EventMetadata,
  options?: {
    inputTokens?: number;      // token count to check against tokens-unit groups
    inputCostCents?: number;   // cost in cents to check against cents-unit groups
  },
): Promise<CanUseResponseData>;

// Shorthand - returns just the boolean
can(userId, event, quantity?, metadata?): Promise<boolean>;

Parameters

Identical to track(): userId, event, optional quantity (default 1), optional metadata. Pass inputTokens or inputCostCents when checking a plan group in tokens or cents units - the same resolution logic as reserve().

Response

interface CanUseResponseData {
  allowed: boolean;
  matched: boolean;                   // false → no limit group covers this event,
                                      //   or the user has no subscription on file
  reasons: string[];                  // 'limit_reached' | 'reservation_headroom_exceeded'
                                      //   | 'unmatched_event' | 'no_subscription'
  details: {
    groupId: string;
    current: number;                  // current counter value
    quota: number;                    // limit
    resetsAt: string | null;          // ISO 8601, null for lifetime
    headroomGate?: number;            // effective gate when headroom is configured
                                      //   (quota × pct/100 or quota for multiplier mode)
  }[];
}
i
Fail-closed by default.If the event_type doesn't match any limit group on the user's plan, canUse returns { allowed: false, matched: false, reasons: ['unmatched_event'] }. Same for users with no subscription on file (no_subscription). The SDKconsole.warns in development so typos and missing limit groups surface immediately.

Examples

Boolean check (UI gating)

if (await vevee.can(userId, 'image.render')) {
  showGenerateButton();
} else {
  showUpgradePrompt();
}

Detailed check with reasons

const check = await vevee.canUse(userId, 'image.render', 1, { model: 'flux-pro' });

if (!check.allowed) {
  return res.status(429).json({
    error: 'limit_reached',
    reasons: check.reasons,
    details: check.details,
  });
}

Showing remaining quota in your UI

const { details } = await vevee.canUse(userId, 'image.render');
const group = details[0];
const remaining = group.quota - group.current;
// → "12 of 50 images left this month, resets Dec 1"

Previewing reservation headroom

When a plan has headroom configured on a limit group, canUse() mirrors the same buffer math as reserve() - without incrementing any counter. The details[i].headroomGate field exposes the effective threshold so your paywall or dashboard can display how much is left before the buffer fires:

const check = await vevee.canUse(userId, 'llm.gpt-4o', 0, undefined, {
  inputTokens: 5_000,
});

const group = check.details[0];

if (!check.allowed) {
  if (check.reasons.includes('reservation_headroom_exceeded')) {
    // User is inside the safety buffer, not yet at the hard cap.
    // headroomGate shows the threshold; quota shows the true cap.
    const tokensUntilGate = (group.headroomGate ?? group.quota) - group.current;
    showNearLimitWarning({ tokensUntilGate, resets: group.resetsAt });
  } else {
    // 'limit_reached' - hard cap hit
    showUpgradePrompt();
  }
}
!
Headroom is not credit-coverable. Even if the user has credit packs, a headroom block stands. Credits extend the true quota; the safety buffer exists to leave room for unknown output cost - those are different concerns. When reasons includes reservation_headroom_exceeded, the correct CTA is “wait until the next period” or “upgrade to a plan with a higher cap”, not “buy more credits.”
!
canUse is not a substitute for reserve. Two parallel requests can both pass canUse and then both call track, exceeding the limit. For enforcement under concurrency, use reserve / commit.

Errors

  • invalid_key (401)
  • requires_secret_key (403)
  • invalid_request (400)