vevee.availablePlans()GET /api/v1/planspk_live_ or sk_live_

Returns the list of plans you've defined for this app - exactly as configured in the dashboard. Use it to render a pricing page from a single source of truth instead of duplicating plan structure in your frontend code.

Signature

availablePlans(): Promise<PublicPlan[]>

Safe to call from the browser with a pk_* key. No parameters - the API key already scopes the response to your app.

Response

interface PublicPlan {
  id: string;                     // 'plan_…' - pass to upsertSubscription()
  name: string;                   // 'Free', 'Pro', etc. (display label)
  period: {
    type: 'daily' | 'weekly' | 'monthly' | 'lifetime';
    anchor: 'subscription_start' | 'calendar';
  };
  limits: PublicLimitGroup[];
}

interface PublicLimitGroup {
  id: string;
  label: string;                  // user-facing, e.g. 'Image generations'
  unit: 'count' | 'tokens' | 'seconds' | 'cents';
  quota: number;
  event: string;                  // first event_type pattern (e.g. 'image.*')
  matches: MatchRule[];           // full match rules incl. metadata filters
}

interface MatchRule {
  event: string;                  // 'image.gemini-3.1-image' or 'image.*'
  metadata?: Record<string, string>;
}

Plans are returned in the order they were created (oldest first). If you need a specific display order on your pricing page, sort client-side by name or by a hand-maintained array of plan ids.

Live vs test mode

Plans are shared between live and test mode - a pk_test_ key returns the same list as pk_live_. This is intentional: it lets you preview your real pricing page from a sandboxed end-user account without maintaining a duplicate plan catalogue.

Examples

Render a pricing page (React)

'use client';
import { useEffect, useState } from 'react';
import { createClient, type PublicPlan } from '@vevee/sdk';

const vevee = createClient({ apiKey: process.env.NEXT_PUBLIC_VEVEE_KEY! });

export function PricingTable() {
  const [plans, setPlans] = useState<PublicPlan[]>([]);

  useEffect(() => {
    vevee.availablePlans().then(setPlans);
  }, []);

  return (
    <div className="pricing-grid">
      {plans.map((plan) => (
        <div key={plan.id} className="pricing-card">
          <h3>{plan.name}</h3>
          <p>Resets {plan.period.type}</p>
          <ul>
            {plan.limits.map((g) => (
              <li key={g.id}>
                <strong>{g.quota.toLocaleString()}</strong> {g.label}
                {g.matches.some((m) => m.metadata) && (
                  <small> (only {describeFilters(g.matches)})</small>
                )}
              </li>
            ))}
          </ul>
          <button onClick={() => subscribe(plan.id)}>Choose {plan.name}</button>
        </div>
      ))}
    </div>
  );
}

Map matches to user-facing labels

The raw event pattern is great for engineers, but on a pricing page you usually want human-readable copy. Keep a small mapping table next to your UI:

const EVENT_COPY: Record<string, string> = {
  'image.*':              'Image generations',
  'image.flux-pro':       'Flux Pro images',
  'video.*':              'Video seconds',
  'llm.tokens':           'LLM tokens',
};

function pricingLabel(g: PublicLimitGroup): string {
  // Prefer the label set in the dashboard; fall back to event-pattern copy.
  return g.label || EVENT_COPY[g.event] || g.event;
}

Server-side rendering (Next.js)

// app/pricing/page.tsx - runs on the server, no JS shipped for the data fetch.
import { createClient } from '@vevee/sdk';

export const revalidate = 300; // cache 5 min - plans change rarely.

export default async function PricingPage() {
  const vevee = createClient({ apiKey: process.env.VEVEE_PUBLIC_KEY! });
  const plans = await vevee.availablePlans();

  return (
    <main>
      {plans.map((plan) => (
        <article key={plan.id}>
          <h2>{plan.name}</h2>
          {plan.limits.map((g) => (
            <p key={g.id}>{g.quota.toLocaleString()} {g.label}</p>
          ))}
        </article>
      ))}
    </main>
  );
}

What's exposed (and what isn't)

  • Exposed: plan id, name, period, limit-group label / unit / quota / event pattern / match rules including metadata filters.
  • Not exposed: onPlanChange behaviour (internal counter policy) and any pricing-rule cost data. The endpoint returns only what a pricing page needs to render.
  • Prices: not stored on plans today - keep your $/mo copy alongside your planId → display copy mapping in your frontend, or store it in your CMS.

After a user picks a plan

Pass the returned plan.id straight to upsertSubscription(). You typically run that on your backend after a successful Stripe checkout - the same plan id you read from the browser also flows through your Stripe metadata.

Errors

  • invalid_key (401) - missing, malformed, or revoked API key.

See also