SellVia Docs — menu
DocsDatabaseTable Specifications

Table Specifications

Database/Table Specifications.md
backendUpdated Aug 23, 2026

Table Specifications

Purpose

Field-by-field schema for every table — the literal thing a migration file would implement.

users

FieldTypeNotes
iduuid, PK
clerk_idtext, uniquemaps to Clerk's user ID
emailtext, unique
created_at / updated_attimestamptz

merchant_profiles

FieldTypeNotes
iduuid, PK
user_iduuid, FK → users
business_nametext
paddle_seller_idtextPaddle seller account ID

creator_profiles

FieldTypeNotes
iduuid, PK
user_iduuid, FK → users
nichetextfixed taxonomy per 02. Search Strategy
audience_sizeinteger
engagement_ratenumericsee Domain Model's open question on self-reported vs. platform-calculated
paddle_seller_idtext
wallet_balance_centsintegerrunning accrued balance toward the payout threshold (amount TBD in PKR — see Business Rules)

offers

FieldTypeNotes
iduuid, PK
merchant_profile_iduuid, FK → merchant_profiles
nametext
price_centsinteger
currencytextUSD / EUR / GBP
categoryenumdigital / physical

campaigns

FieldTypeNotes
iduuid, PK
offer_iduuid, FK → offers
commission_ratenumericmerchant-set, no platform bounds
statusenumdraft / live / paused / ended

applications

FieldTypeNotes
iduuid, PK
campaign_iduuid, FK → campaigns
creator_profile_iduuid, FK → creator_profiles
statusenumpending / approved / rejected
locked_commission_ratenumericsnapshot of campaign.commission_rate at approval time (see State Machines' flagged reconciliation note — this field assumes "locked at approval" wins; revisit once that's settled)
FieldTypeNotes
iduuid, PK
application_iduuid, FK → applications, uniqueone link per approved application
slugtext, uniquee.g. "mia-glow"

attribution_events

FieldTypeNotes
iduuid, PK
affiliate_link_iduuid, FK → affiliate_links
typeenumclick / add_to_cart / purchase
occurred_attimestamptzused against the 30-day attribution window

sales

FieldTypeNotes
iduuid, PK
attribution_event_iduuid, FK → attribution_eventsthe purchase-type event
amount_centsinteger
currencytext
statusenumpending / verified / refunded / disputed
paddle_transaction_idtext

commissions

FieldTypeNotes
iduuid, PK
sale_iduuid, FK → sales, unique
creator_profile_iduuid, FK → creator_profiles
amount_centsinteger
clawed_backREMOVED (2026-08-07) — clawback eliminated entirely; commission is never clawed back at all, per Commission Engine

platform_fees

FieldTypeNotes
iduuid, PK
sale_iduuid, FK → sales, unique
amount_centsinteger2% of sale amount

payouts

FieldTypeNotes
iduuid, PK
recipient_typeenumcreator / merchant
recipient_iduuidpolymorphic — references creator_profiles or merchant_profiles depending on recipient_type
amount_centsinteger
statusenumpending / processing / paid / failed
paddle_payout_idtext

payout_commissions (join table)

FieldTypeNotes
payout_iduuid, FK → payouts
commission_iduuid, FK → commissionsrepresents which sales are bundled into a given creator payout

notifications

FieldTypeNotes
iduuid, PK
user_iduuid, FK → users
typetextsale_made / payout_threshold_reached / application_approved / etc.
read_attimestamptz, nullable

Open Questions

  • Whether merchant payouts (not threshold-gated, per Money Flow) need their own join table like payout_commissions, or can reference platform_fees/sales directly since they're not batched the same way as creator payouts

Update (2026-08-04): New Tables for Cost/Revenue Tracking

ai_usage_events

FieldTypeNotes
iduuid, PK
featureenummatching / screening / copy_assist
tokens_ininteger
tokens_outinteger
cost_centsinteger0 for cache hits, per 11. Analytics → AI / Token Usage Tracking
related_user_iduuid, nullableFK → users
related_entity_typetext, nullablee.g. "application", "campaign"
created_attimestamptz

infra_costs (manual-entry fallback for non-API-able costs)

FieldTypeNotes
iduuid, PK
categorytexte.g. "hosting", "clerk_subscription", "monitoring"
amount_centsinteger
period_monthdatefirst-of-month, identifies which period this cost belongs to
sourceenumapi / manual
entered_byuuid, nullableAdmin user, if manually entered
created_attimestamptz

monthly_pnl_reports

FieldTypeNotes
iduuid, PK
period_monthdate, unique
revenue_centsintegersum of platform_fees for the period
swich_fees_centsintegerrenamed 2026-08-23 from paddle_fees_cents — Swich's own transaction fees for the period; exact source (Swich API pull vs. manual entry) unconfirmed pending real integration
hosting_cost_centsintegerfrom infra_costs
ai_cost_centsintegersum of ai_usage_events.cost_cents
other_cost_centsintegerfrom infra_costs, other categories
net_pnl_centsintegercomputed: revenue − all cost fields
finalizedboolean, default falseper 11. Analytics → Automated Monthly P&L's locking recommendation
generated_attimestamptz

See 11. Analytics → AI / Token Usage Tracking, Unit Economics, and Automated Monthly P&L for the full reasoning behind these tables.

Update (2026-08-04): users.clerk_id → users.kratos_identity_id

The users table's clerk_id field (text, unique, mapping to the auth provider's user ID) is renamed kratos_identity_id, mapping to Ory Kratos's identity ID instead — reflects the 04. Security → Authentication switch from Clerk to Ory Kratos. No other schema change.

Update (2026-08-04): jobs Table Added

FieldTypeNotes
iduuid, PK
typetexte.g. "export_sales_report"
statusenumpending / processing / completed / failed
idempotency_keytext, uniqueclient-generated, prevents duplicate job creation on double-click/retry
user_iduuid, FK → users
tenant_iduuidscoped per 04. Security → Tenant Isolation Audit
paramsjsonbwhat was requested
result_urltext, nullablesigned URL once complete
error_messagetext, nullable
created_at / completed_attimestamptz

See 02. Technical Architecture → Async Job Pattern & Idempotency for the full flow this table supports.

Update (2026-08-07): Schema Changes for External-Site Tracking + Billing

sales — REVISED

FieldTypeNotes
external_order_idtextthe merchant's own order reference — NEW
reported_attimestamptzwhen the merchant's snippet reported it — NEW
statusenumpending / refunded / disputed — verified REMOVED, superseded by acceptance_status
acceptance_statusenumaccepted / rejected — NEW, replaces the old "verified" framing
billing_cycle_iduuid, FK → billing_cycles, nullableNEW, set once included in a cycle
paddle_transaction_idREMOVED — SellVia never processes the underlying sale

billing_cycles — NEW TABLE

FieldTypeNotes
iduuid, PK
merchant_profile_iduuid, FK → merchant_profiles
period_start / period_endtimestamptz
statusenumopen / pending_charge / charged / failed
total_owed_centsintegersum of commissions + platform fees for included sales
paddle_transaction_idtext, nullableset once successfully charged
retry_countinteger, default 0

merchant_profiles — UPDATED

Add: paddle_customer_id (for the card-on-file billing charge — distinct from paddle_seller_id, which now exists only for merchants who also want to receive payouts through SellVia for something else, not for the sale itself).

payouts — UPDATED

payout_commissions join table (already existed) now only includes commissions whose billing_cycle_id has reached charged — enforces the bill-first-then-pay sequencing (01. Money Flow) at the data layer, not just as a business rule someone has to remember.

See 01. Domain Model for the corresponding entity-level changes.

Update (2026-08-07): affiliate_links.discount_code Added

FieldTypeNotes
discount_codetext, uniquee.g. "MIA10" — fallback attribution signal per 05. Payment Flow, created in the merchant's own store discount system at link creation

Also: sale-report payloads (received at POST /webhooks/merchant-sales, 07. Endpoint Specifications) gain an optional discount_code_used field alongside the primary attribution reference.

Update (2026-08-07): Refund Credit Field Added

FieldTypeNotes
monthly_refund_credits_usedinteger, default 0resets each calendar month — 05. Refund Handling's 5-credit monthly cap

Added to merchant_profiles.

Update (2026-08-23): MAJOR REVISION — campaigns Merged Into offers, Paddle Fields Removed

Founder decisions, full reasoning in 02. Architecture Decision Log. This is the literal schema-level version of 01. Domain Model's 2026-08-23 revision.

campaigns table — REMOVED

Merged into offers. Migration: add commission_rate and status directly to offers, backfill from the corresponding campaigns row, then drop campaigns.

offers — REVISED

FieldTypeNotes
commission_ratenumericmerchant-set, no platform bounds — moved from campaigns
statusenumdraft / live / paused / ended — moved from campaigns
currencytextPKR only for MVP — was USD / EUR / GBP

applications — REVISED

FieldTypeNotes
offer_iduuid, FK → offersrenamed from campaign_id, now points directly at offers
locked_commission_ratenumericsnapshot of offers.commission_rate at approval time — wording only, "campaign.commission_rate" → "offers.commission_rate"

Paddle fields — REMOVED (no processor for MVP)

  • merchant_profiles.paddle_seller_id, merchant_profiles.paddle_customer_id — removed
  • creator_profiles.paddle_seller_id — removed
  • sales.paddle_transaction_id — already removed 2026-08-07, unaffected
  • billing_cycles.paddle_transaction_id — removed
  • payouts.paddle_payout_id — removed

users.clerk_id note

Already renamed kratos_identity_id (2026-08-04 update above) — unaffected by this revision.

Not yet updated to match

Correction (2026-08-23): this used to point at 03. Database → Migration Strategy as still referencing campaigns, but Migration Strategy never names any table directly — that was a wrong cross-reference. The files that actually still referenced campaigns were 03. Database → Relationships and 03. Database → Soft Delete Policy; both were reconciled 2026-08-23 alongside this doc. (ER Diagram, Constraints, Indexing Strategy, Database Design were all updated 2026-08-23 alongside this doc.)

Update (2026-08-23, later same day): Swich Confirmed — Fields Revised From the Bank-Transfer Working Default Above

Founder decision: Swich (swichnow.io) confirmed as processor, replacing the plain bank-transfer default this doc originally introduced above. Full reasoning: 02. Architecture Decision Log. The fields below are a working draft — Swich's actual API/payee-registration shape is unconfirmed pending real integration; expect these names to change once that happens.

Added to merchant_profiles:

FieldTypeNotes
swich_customer_idtext, nullableSwich's identifier for the merchant, set once billing is connected — replaces the bank_account_* fields this doc originally proposed

Added to creator_profiles:

FieldTypeNotes
swich_payee_idtext, nullableSwich's identifier for the creator as a payout recipient, set at onboarding — replaces the bank_account_* fields this doc originally proposed
payout_methodenum, nullablebank_transfer / jazzcash / easypaisa / raast — whichever method the creator registered with Swich

Added to billing_cycles, replacing the removed paddle_transaction_id:

FieldTypeNotes
swich_invoice_idtext, nullableset once Swich generates the billing-cycle payment request; distinct from the transfer confirmation below
swich_payment_referencetext, nullableset via webhook once the merchant completes payment through Swich — this is what actually flips status to charged

Added to payouts, replacing the removed paddle_payout_id:

FieldTypeNotes
swich_payout_idtext, nullableset via webhook once Swich confirms the disbursement to a creator completed

Underlying bank/mobile-wallet details (account title, account number/IBAN, bank name, or JazzCash/EasyPaisa account identifier) are collected as part of Swich's own payee-registration flow, not stored redundantly in SellVia's schema beyond what's needed for display — exact requirement pending Swich's real API docs.