Types
All public types are re-exported from @vevee/sdk. They are the single source of truth for the wire format of every endpoint.
Client
import type { ClientOptions, AnalyticsClientConfig, AnalyticsMode } from '@vevee/sdk';
interface ClientOptions {
apiKey: string; // 'sk_live_…' or 'pk_live_…'
baseUrl?: string; // default: 'https://www.vevee.org'
analytics?: AnalyticsClientConfig;
}
interface AnalyticsClientConfig {
mode?: AnalyticsMode; // default: 'hybrid'
requireConsent?: boolean; // default: true
}
type AnalyticsMode = 'hybrid' | 'identified' | 'aggregate';Metering events
type EventMetadata = Record<string, string>;
interface TrackResponseData {
eventId: string;
counters: CounterSummary[];
}
interface CounterSummary {
groupId: string;
event: string; // raw event_type pattern (e.g. "image.gemini-3-1-flash-image-preview" or "image.*")
label: string; // human label from the dashboard
unit: 'count' | 'tokens' | 'seconds' | 'cents';
quota: number; // limit set on the plan
count: number; // current usage in this period
remaining: number; // max(0, quota - count) - pre-computed
costCents: number;
// Distinct metadata filter values aggregated across this group's match
// rules. {} for "overall" groups; e.g. { source: ['text'] } when the
// group is gated to a specific source / variant.
filters: Record<string, string[]>;
}canUse
interface CanUseResponseData {
allowed: boolean;
matched: boolean;
reasons: string[];
details: {
groupId: string;
current: number;
quota: number;
resetsAt: string | null; // ISO 8601 or null for lifetime
creditAvailable: number;
creditPacks: { packId: string; packName: string; available: number }[];
}[];
}Reservations
interface ReserveResponseData {
allowed: boolean;
matched: boolean;
reservationId?: string;
expiresAt?: string;
reasons?: string[];
}Usage
interface UsageResponseData {
userId: string;
period: { start: string; end: string | null } | null;
counters: CounterSummary[];
credits: CreditBalanceSummary[];
}Subscriptions
interface UpsertSubscriptionRequest {
userId: string;
planId: string;
customLimits?: PlanLimits;
endsAt?: string;
// Align relative-period counters with an external billing provider.
// Omit to preserve any previously-set anchor; pass null to clear it.
cycleStart?: string | null;
}
interface UpsertSubscriptionResponseData {
subscriptionId: string;
userId: string;
planId: string;
startedAt: string;
cycleAnchorAt: string | null;
}Plan structure
type PeriodType = 'daily' | 'weekly' | 'monthly' | 'lifetime';
type PeriodAnchor = 'subscription_start' | 'calendar';
type LimitUnit = 'count' | 'tokens' | 'seconds' | 'cents';
interface MatchRule {
event: string; // exact ('image.flux-pro') or glob ('image.*')
metadata?: Record<string, string>; // every key/value must match (values support globs)
}
interface LimitGroup {
id: string;
label: string;
unit: LimitUnit;
quota: number;
matches: MatchRule[]; // event matches the group if ANY rule matches
}
interface PlanLimits {
groups: LimitGroup[];
}Analytics - capture / identify / alias
type AnalyticsPropertyValue = string | number | boolean | null;
type PersonProfile = Record<string, AnalyticsPropertyValue>;
interface AnalyticsProperties {
$set?: PersonProfile; // overwrite the person profile
$set_once?: PersonProfile; // first-write-wins on the person profile
[key: string]: AnalyticsPropertyValue | PersonProfile | undefined;
}
// POST /api/v1/capture
interface AnalyticsCaptureRequest {
// Omit for anonymous aggregate (hybrid mode, pre-login). Pass a real user
// id post-login. Never pass a generated anonymous UUID - that needs browser
// storage and a cookie banner.
distinctId?: string;
event: string;
properties?: AnalyticsProperties;
timestamp?: string;
}
interface AnalyticsCaptureResponseData {
eventId: string; // 'anv_…' anonymous · 'aev_…' identified
isAnonymous: boolean; // true → routed to analytics_anonymous_events
personId?: string; // identified events only
isReserved?: boolean; // identified events only
}
// POST /api/v1/identify
interface IdentifyOptions {
mergeAnonymousId?: string; // REQUIRES consentGiven: true
consentGiven?: boolean;
}
interface AnalyticsIdentifyRequest {
distinctId: string;
properties?: PersonProfile; // $set
propertiesOnce?: PersonProfile; // $set_once
mergeAnonymousId?: string;
consentGiven?: boolean;
}
interface AnalyticsIdentifyResponseData {
personId: string;
merged: boolean;
}
// POST /api/v1/alias
interface AliasOptions {
consentGiven?: boolean; // required true when either id is anonymous (anon_ prefix)
}
interface AnalyticsAliasRequest {
distinctId: string;
alias: string;
consentGiven?: boolean;
}
interface AnalyticsAliasResponseData {
personId: string;
}Analytics - privacy / GDPR (secret key only)
// POST /api/v1/opt-out · POST /api/v1/opt-in
interface AnalyticsOptRequest {
distinctId: string;
}
// GET /api/v1/opted-out?distinctId=…
interface AnalyticsOptedOutResponseData {
optedOut: boolean;
}
// POST /api/v1/delete-person (GDPR Art. 17 - right to erasure)
interface AnalyticsDeletePersonRequest {
distinctId: string;
}
interface AnalyticsDeletePersonResponseData {
jobId: string;
}
// GET /api/v1/deletion-status?jobId=…
type DeletionJobStatus = 'queued' | 'in_progress' | 'completed' | 'failed';
interface AnalyticsDeletionStatusResponseData {
status: DeletionJobStatus;
startedAt?: string;
completedAt?: string;
errorMessage?: string;
}
// POST /api/v1/export-person (GDPR Art. 15 / 20 - access & portability)
interface AnalyticsExportPersonRequest {
distinctId: string;
}
interface AnalyticsExportPersonResponseData {
downloadUrl: string;
expiresAt: string; // ISO 8601 - URL valid 24h
format: 'json';
}Reserved-event taxonomy
interface ReservedEventSpec {
name: string;
category: 'identity' | 'onboarding' | 'paywall' | 'checkout'
| 'subscription' | 'feature' | 'trial';
description: string;
conventionalProperties: { key: string; description: string; example?: string }[];
}
declare const RESERVED_EVENTS: readonly ReservedEventSpec[];
type ReservedEventName = (typeof RESERVED_EVENTS)[number]['name'];
declare function isReservedEvent(name: string): name is ReservedEventName;Error envelope
type ErrorCode =
| 'not_found'
| 'invalid_key'
| 'requires_secret_key'
| 'limit_reached'
| 'unmatched_event'
| 'no_subscription'
| 'workspace_limit_reached'
| 'invalid_request'
| 'reservation_expired'
| 'reservation_not_pending'
| 'already_canceled'
| 'internal_error'
| 'not_implemented'
// Thrown when a consent-gated analytics operation (anonymous→identified
// merge) is attempted without consentGiven: true.
| 'consent_required';
interface ApiError { code: ErrorCode; message: string; }
type ApiResponse<T> = { ok: true; data: T } | { ok: false; error: ApiError };ID prefixes
| Prefix | Resource |
|---|---|
ws_ | Workspace |
app_ | App |
plan_ | Plan |
lg_ | Limit group |
sub_ | Subscription |
evt_ | Metering event |
cnt_ | Counter |
rsv_ | Reservation |
aev_ | Identified analytics event |
anv_ | Anonymous analytics event |
pid_ | Analytics person |
cau_ | Consent-audit entry |
del_ | Deletion job |
anon_ | Anonymous-session id (from getAnonymousId()) |