Skip to content

Data model

Vumbnail has very little “database” in the traditional sense. Its real data is cached images in R2, a small live allow-list in KV, and a billing record in D1. The Postgres schema is mostly auth boilerplate.

R2 — the thumbnail store (vumbnail-thumbnails)

Section titled “R2 — the thumbnail store (vumbnail-thumbnails)”

All objects live under the thumbnails/ prefix:

thumbnails/<videoId>/clean.jpg clean variant (paying / under-threshold)
thumbnails/<videoId>/wm.jpg watermarked variant
thumbnails/_shared/error.jpg one shared graphic for all failures
thumbnails/_shared/locked.jpg one shared graphic for password-locked
thumbnails/_negative/<cacheId>.json negative-cache record (see below)
thumbnails/_resolver-success/<id:pw>.json memoized Vimeo resolution

Clean images on the public cache.vumbnail.com domain are blocked by a WAF rule; only wm.jpg and the shared objects are publicly fetchable. See Caching & delivery for the record shapes.

The hot-path config and live allow-list, shared between the thumbnail Worker (read) and the accounts Worker (write):

KeyPurpose
billing-entitlements:projectionthe live allow-list (domains/IPs/keys/UAs) read on the hot path
billing-entitlements:modestatic / shadow / runtime (currently runtime; hardcoded out of the hot path)
youtube:playlist-resolver-url(s)optional external playlist-resolver overrides

A separate KV namespace, PROCESSED_EVENTS_STORE, de-duplicates Stripe webhook events (~90-day TTL).

The billing record of truth, written by the accounts Worker:

-- latest entitlement per subscription
billing_entitlements_current (
stripe_subscription_id TEXT PRIMARY KEY,
stripe_customer_id TEXT, stripe_price_id TEXT, stripe_product_id TEXT,
customer_email TEXT,
domains_json TEXT NOT NULL, -- referer hosts → clean
ips_json TEXT NOT NULL, -- client IPs → clean
added_at TEXT NOT NULL, updated_at TEXT NOT NULL
);
-- append-only audit of every processed event
billing_entitlement_events (
stripe_event_id TEXT PRIMARY KEY,
stripe_event_type TEXT NOT NULL,
stripe_subscription_id TEXT NOT NULL,
domains_json TEXT NOT NULL, ips_json TEXT NOT NULL,
observed_at TEXT NOT NULL
);

D1 is regenerated into the KV projection, which is what the hot path actually reads. See Paid access.

Committed allow-list — apps/thumbnail/lib/customers.json

Section titled “Committed allow-list — apps/thumbnail/lib/customers.json”

A third copy of entitlements, committed to git and bundled at deploy time as the fallback when KV is unavailable. Each entry mirrors the CustomerAllowlistRecord. It is kept in sync automatically by the accounts Worker via the GitHub API (visible as chore(thumbnail): sync Stripe allowlist for sub_… commits).

The Drizzle schema is Better Auth + organization-plugin boilerplate: user, session, identity, verification, organization, member, team, team_member, invitation, passkey. It backs operator-dashboard sign-in. No Vumbnail product entity lives here — thumbnails are R2, entitlements are D1/KV.

Single source of truth for numbers that several apps depend on:

vumbnail-pricing.ts
VUMBNAIL_FREE_TIER_MONTHLY_REQUESTS = 50_000
VUMBNAIL_OUTREACH_GRACE_PERCENT = 20
VUMBNAIL_OUTREACH_MONTHLY_THRESHOLD = 60_000
// billing-entitlements.ts
BILLING_ENTITLEMENTS_PROJECTION_KEY = "billing-entitlements:projection"
BILLING_ENTITLEMENTS_MODE_KEY = "billing-entitlements:mode"
// projection "kind": "billing-entitlements/v1"