Skip to content

Data model — entity-relationship diagram

Generated from a canonical source

This page is a read-only projection of docs/architecture/data-model-erd.md. Edit the canonical file, then run npm --prefix tools/project-knowledge-derive run derive.

@generated — mechanically parsed from apps/api/src/schema.sql by tools/erd-derive/. Do not hand-edit — regenerate with npx tsx tools/erd-derive/derive.ts whenever the schema changes. CI (erd-derive-ci.yml) fails if the committed ERD drifts from a fresh derive, which is what makes the derives_from contract above real.

85 tables, 75 foreign-key relationships. Every attribute is shown (not just keys) so this doubles as a full schema reference; PK/FK annotations mark primary and foreign keys (a column that is both is shown as PK). Cross-cutting context: see ARCHITECTURE.md §3 — Systems of Record for which entities BigCommerce owns vs. which this app owns.

How to read this page

Tables are grouped into domains — one small erDiagram per domain instead of a single 85-table diagram. A table with a foreign key into another domain still shows that edge: the other domain's table appears as a bare box (name only, no attributes) inside this domain's diagram — a stub. Look up the stub's full attributes in its own domain section (the caption under each diagram names where). A domain with more than 14 tables splits into two alphabetical parts so no single diagram grows unreadable again.

Domain index

Domain Tables
Core subscription lifecycle 20
Payments & processors 6
Dunning & retries 5
Extensions 7
B2B 5
Promotions & discounts 9
Notifications & email 13
Webhooks & integrations 4
Admin/CS & audit 6
Imports & migration 1
Platform & misc 9

Core subscription lifecycle

20 tables — split into 2 alphabetical parts for readability.

Part 1 (alphabetical)

erDiagram
    category_inclusions {
        TEXT store_hash PK
        INTEGER bc_category_id PK
        TEXT note
        TEXT created_at
        INTEGER created_by
        INTEGER is_deleted
    }
    charges {
        TEXT id PK
        TEXT subscription_id FK
        TEXT payment_method_id FK
        INTEGER amount_cents
        TEXT currency
        TEXT status
        TEXT chain_position
        INTEGER cycle_number
        TEXT scheduled_at
        TEXT attempted_at
        TEXT succeeded_at
        TEXT processor_transaction_id
        INTEGER bc_order_id
        TEXT failure_code
        TEXT failure_message
        INTEGER retry_attempt
        TEXT idempotency_key
        TEXT created_at
        TEXT updated_at
        INTEGER created_by
        INTEGER updated_by
        INTEGER is_deleted
        TIMESTAMP next_retry_at
        TEXT last_retry_at
        INTEGER refund_amount_cents
        TEXT refund_reason
        TEXT skip_reason
        TEXT order_overrides
        TEXT charge_origin
        INTEGER tax_cents
        TEXT tax_source
        INTEGER refund_tax_cents
        TEXT external_ref
    }
    customers {
        TEXT id PK
        TEXT store_hash FK
        INTEGER bc_customer_id
        TEXT email
        TEXT payment_customer_ref
        TEXT consent_metadata
        TEXT created_at
        TEXT updated_at
        INTEGER created_by
        INTEGER updated_by
        TEXT email_status
        TEXT name
        INTEGER is_deleted
        TEXT deleted_at
        TEXT anonymized_at
        INTEGER marketing_opt_out
        INTEGER lifecycle_email_opt_out
        INTEGER profiling_opt_out
        TEXT sms_phone_e164
        TEXT sms_consent_status
        TEXT sms_consent_at
        TEXT sms_consent_source
        TEXT external_ref
    }
    eligibility_rules_custom_fields {
        TEXT id PK
        TEXT store_hash
        TEXT field_name
        TEXT field_value
        INTEGER created_by
        TEXT created_at
    }
    eligibility_rules_dependency {
        TEXT id PK
        TEXT store_hash
        TEXT target_product_id
        TEXT required_prior_product_id
        INTEGER created_by
        TIMESTAMP created_at
    }
    eligibility_rules_mutex {
        TEXT id PK
        TEXT store_hash
        TEXT product_a_id
        TEXT product_b_id
        TIMESTAMP created_at
    }
    entitlement_events {
        TEXT id PK
        TEXT store_hash
        TEXT entitlement_id FK
        TEXT from_status
        TEXT to_status
        TEXT reason
        TEXT trigger_ref
        TEXT created_at
    }
    entitlements {
        TEXT id PK
        TEXT store_hash
        TEXT subscription_id FK
        TEXT plan_id
        TEXT entitlement_key
        TEXT status
        TEXT provider
        TEXT provider_external_ref
        TEXT granted_at
        TEXT revoked_at
        TEXT created_at
        TEXT updated_at
    }
    manual_charges {
        TEXT id PK
        TEXT charge_id FK
        TEXT subscription_id FK
        TEXT store_hash
        INTEGER amount_cents
        TEXT currency
        TEXT reason
        TEXT status
        TEXT failure_code
        TEXT failure_message
        INTEGER initiated_by
        TEXT initiated_at
        TEXT resolved_at
    }
    payment_methods {
        TEXT id PK
        TEXT customer_id FK
        TEXT processor_connection_id FK
        TEXT payment_method_ref
        TEXT network_transaction_id
        TEXT card_brand
        TEXT card_last4
        INTEGER card_exp_month
        INTEGER card_exp_year
        INTEGER is_default
        TEXT status
        TEXT created_at
        TEXT updated_at
        INTEGER created_by
        INTEGER updated_by
        INTEGER is_deleted
    }
    stores ||--o{ customers : "store_hash"
    customers ||--o{ payment_methods : "customer_id"
    processor_connections ||--o{ payment_methods : "processor_connection_id"
    stores ||--o{ category_inclusions : "store_hash"
    subscriptions ||--o{ charges : "subscription_id"
    payment_methods ||--o{ charges : "payment_method_id"
    subscriptions ||--o{ entitlements : "subscription_id"
    entitlements ||--o{ entitlement_events : "entitlement_id"
    charges ||--o{ manual_charges : "charge_id"
    subscriptions ||--o{ manual_charges : "subscription_id"

Cross-domain stub entities — full attributes live in: processor_connections (Payments & processors), stores (Core subscription lifecycle — part 2), subscriptions (Core subscription lifecycle — part 2).

Part 2 (alphabetical)

erDiagram
    plan_channel_sales_mode {
        TEXT plan_id PK
        INTEGER bc_channel_id PK
        TEXT sales_mode
        TEXT created_at
        TEXT updated_at
    }
    plan_entitlement_configs {
        TEXT id PK
        TEXT store_hash
        TEXT plan_id
        TEXT entitlement_key
        TEXT provider
        TEXT provider_config
        TEXT created_at
    }
    plan_intervals {
        TEXT id PK
        TEXT plan_id FK
        TEXT interval
        INTEGER interval_count
        INTEGER sort_order
        TEXT created_at
    }
    plan_variant_eligibility {
        TEXT plan_id PK
        INTEGER bc_variant_id PK
        TEXT created_at
    }
    plans {
        TEXT id PK
        TEXT store_hash FK
        INTEGER bc_product_id
        INTEGER bc_variant_id
        TEXT name
        INTEGER amount_cents
        TEXT currency
        TEXT interval
        INTEGER interval_count
        INTEGER trial_days
        TEXT status
        TEXT created_at
        TEXT updated_at
        INTEGER created_by
        INTEGER updated_by
        INTEGER commitment_cycles
        INTEGER max_cycles
        INTEGER lock_price_at_creation
        TEXT pricing_strategy
        INTEGER price_list_id
        INTEGER accepting_new_subscribers
        INTEGER is_deleted
        INTEGER box_size
        TEXT box_eligible_products
        INTEGER box_customization_window_days
        INTEGER billing_anchor_month
        INTEGER billing_anchor_day
        TEXT channel_ids
        TEXT customer_group_ids
        TEXT country_codes
        TEXT price_list_ids
        INTEGER min_qty
        INTEGER max_qty
        INTEGER cycle_discount_pct
        TEXT cycle_discount_scope
        INTEGER cycle_discount_count
        INTEGER free_shipping_eligible
        TEXT oos_renewal_policy
        TEXT proration_policy
        TEXT sales_mode
        INTEGER minimum_term_cycles
        INTEGER minimum_term_days
        TEXT early_cancel_policy
        INTEGER early_termination_fee_cents
        TEXT shipping_recalc_policy
        TEXT shipping_method_label
        INTEGER shipping_flat_cents
        INTEGER discount_pct
        TEXT discount_base
        TEXT shipping_interval_unit
        INTEGER shipping_interval_count
        TEXT processor_connection_id FK
        TEXT cycle_discounts
        TEXT volume_tiers
    }
    stores {
        TEXT store_hash PK
        INTEGER bc_store_id
        TEXT installed_at
        TEXT uninstalled_at
        TEXT scope_grant
        TEXT metadata
        TEXT access_token_encrypted
        TEXT webhook_secret_encrypted
        INTEGER test_mode_enabled
        INTEGER nti_freshness_days
        INTEGER nudge_lead_days
        TEXT capture_timing
        TEXT country_code
        INTEGER auth_window_days_override
        INTEGER bundle_window_days
        INTEGER allow_mixed_bundle
        INTEGER sync_order_edits_to_subscription
        INTEGER admin_shell_v2
        INTEGER reactivation_grace_days
        TEXT stencil_script_uuid
        TEXT storefront_token_encrypted
        INTEGER storefront_token_expires_at
        INTEGER default_sub_status_id
        INTEGER bundle_anchor_day
        TEXT contract_cancel_guard_mode
        INTEGER checkout_supports_custom_fields
        TEXT default_discount_base
        INTEGER require_subscribe_consent
    }
    subscription_actors {
        TEXT id PK
        TEXT store_hash
        TEXT subscription_id FK
        TEXT customer_id
        TEXT role
        TEXT notification_prefs
        TEXT processor_connection_ref
        TEXT created_at
        TEXT updated_at
    }
    subscription_change_requests {
        TEXT id PK
        TEXT store_hash FK
        TEXT subscription_id FK
        TEXT customer_id FK
        TEXT change_type
        TEXT change_payload
        TEXT status
        INTEGER reviewer_user_id
        TEXT reviewer_notes
        TEXT expires_at
        TEXT applied_at
        TEXT created_at
        TEXT updated_at
    }
    subscriptions {
        TEXT id PK
        TEXT store_hash FK
        TEXT customer_id FK
        TEXT plan_id FK
        TEXT payment_method_id FK
        TEXT processor_connection_id FK
        TEXT status
        TEXT mit_subtype
        TEXT current_period_start
        TEXT current_period_end
        TEXT next_charge_at
        TEXT cancel_at
        TEXT cancelled_at
        TEXT cancel_reason
        TEXT metadata
        TEXT created_at
        TEXT updated_at
        INTEGER created_by
        INTEGER updated_by
        TEXT chosen_interval
        INTEGER chosen_interval_count
        INTEGER quantity
        INTEGER bc_variant_id
        TEXT delivery_cadence_rule
        INTEGER created_from_order_id
        INTEGER created_from_line_item_id
        INTEGER test_mode
        INTEGER locked_price_cents
        TEXT gift_from_customer_id
        TEXT gift_origin_subscription_id
        TEXT last_nti_refreshed_at
        TIMESTAMP term_end_at
        INTEGER pending_renewal_decision
        INTEGER cycles_completed
        TEXT starts_at
        INTEGER is_deleted
        INTEGER channel_id
        TEXT anchor_date
        TEXT country_code
        INTEGER bundle_opt_in
        TEXT shipping_address_hash
        INTEGER term_length_days
        TEXT active_promotion_ids
        INTEGER notice_period_days
        TIMESTAMP lock_expires_at
        TEXT external_ref
        TEXT legacy_cancellation_reason
        TEXT imported_from
        INTEGER trial_ending_soon_sent
        TEXT company_id
        TEXT company_name
        TEXT parent_company_id
    }
    users {
        TEXT store_hash PK
        INTEGER bc_user_id PK
        TEXT email
        INTEGER is_owner
        TEXT first_seen_at
        TEXT last_seen_at
        TEXT email_history_jsonb
    }
    stores ||--o{ users : "store_hash"
    plans ||--o{ plan_intervals : "plan_id"
    plans ||--o{ plan_variant_eligibility : "plan_id"
    stores ||--o{ plans : "store_hash"
    processor_connections ||--o{ plans : "processor_connection_id"
    subscriptions ||--o{ subscription_actors : "subscription_id"
    stores ||--o{ subscription_change_requests : "store_hash"
    subscriptions ||--o{ subscription_change_requests : "subscription_id"
    customers ||--o{ subscription_change_requests : "customer_id"
    stores ||--o{ subscriptions : "store_hash"
    customers ||--o{ subscriptions : "customer_id"
    plans ||--o{ subscriptions : "plan_id"
    payment_methods ||--o{ subscriptions : "payment_method_id"
    processor_connections ||--o{ subscriptions : "processor_connection_id"
    plans ||--o{ plan_channel_sales_mode : "plan_id"

Cross-domain stub entities — full attributes live in: customers (Core subscription lifecycle — part 1), payment_methods (Core subscription lifecycle — part 1), processor_connections (Payments & processors).

Payments & processors

6 tables.

erDiagram
    accounting_connections {
        INTEGER id PK
        TEXT store_hash
        TEXT provider
        TEXT endpoint_url
        TEXT credential_hash
        TEXT ar_account_code
        TEXT revenue_account_code
        INTEGER is_active
        INTEGER is_deleted
        TEXT created_at
        TEXT updated_at
        TEXT created_by
        TEXT updated_by
    }
    accounting_post_attempts {
        INTEGER id PK
        INTEGER accounting_connection_id FK
        TEXT store_hash
        TEXT charge_id
        TEXT journal_payload_json
        TEXT status
        INTEGER attempt_count
        INTEGER last_response_code
        TEXT last_response_body
        TEXT last_attempted_at
        TEXT next_retry_at
        TEXT provider_journal_id
        TEXT idempotency_key
        TEXT created_at
    }
    authorizations {
        TEXT id PK
        TEXT charge_id FK
        TEXT processor_connection_id FK
        TEXT processor_transaction_id
        TEXT status
        TEXT capture_mode
        INTEGER amount_cents
        TEXT currency
        TEXT authorized_at
        TEXT captured_at
        TEXT expires_at
        TEXT created_at
        TEXT updated_at
    }
    processor_connections {
        TEXT id PK
        TEXT store_hash FK
        TEXT processor
        TEXT account_ref
        TEXT status
        TEXT config
        TEXT created_at
        TEXT updated_at
        INTEGER is_deleted
        INTEGER is_default
        TEXT routing_channel_ids
    }
    store_credit_applications {
        TEXT id PK
        TEXT store_credit_id FK
        TEXT charge_id FK
        TEXT subscription_id FK
        TEXT store_hash
        INTEGER amount_cents
        TEXT status
        TEXT applied_at
        TEXT reversed_at
        TEXT reversed_reason
    }
    store_credits {
        TEXT id PK
        TEXT store_hash
        TEXT customer_id
        TEXT subscription_id FK
        INTEGER balance_cents
        TEXT currency
        TEXT source
        TEXT reason
        TEXT expires_at
        TEXT status
        INTEGER issued_by
        TEXT issued_at
        TEXT updated_at
    }
    stores ||--o{ processor_connections : "store_hash"
    charges ||--o{ authorizations : "charge_id"
    processor_connections ||--o{ authorizations : "processor_connection_id"
    subscriptions ||--o{ store_credits : "subscription_id"
    store_credits ||--o{ store_credit_applications : "store_credit_id"
    charges ||--o{ store_credit_applications : "charge_id"
    subscriptions ||--o{ store_credit_applications : "subscription_id"
    accounting_connections ||--o{ accounting_post_attempts : "accounting_connection_id"

Cross-domain stub entities — full attributes live in: charges (Core subscription lifecycle — part 1), stores (Core subscription lifecycle — part 2), subscriptions (Core subscription lifecycle — part 2).

Dunning & retries

5 tables.

erDiagram
    dunning_policies {
        TEXT id PK
        TEXT store_hash FK
        INTEGER stage_index
        REAL delay_hours
        TEXT on_exhaustion
        TEXT email_template_key
        INTEGER grace_period_days
        TIMESTAMP created_at
        TIMESTAMP updated_at
    }
    exceptions {
        TEXT id PK
        TEXT store_hash
        TEXT charge_id FK
        TEXT status
        TEXT resolved_by
        TEXT resolution_note
        TIMESTAMP resolved_at
        TIMESTAMP created_at
        TEXT exception_type
        TEXT subscription_id
        TEXT payload
        TEXT processor_dispute_id
        TIMESTAMP evidence_due_by
    }
    network_updater_events {
        TEXT id PK
        TEXT processor_event_id
        TEXT processor
        TEXT payment_method_ref
        TEXT payment_method_id FK
        TEXT store_hash FK
        INTEGER applied
        TEXT raw_payload
        TEXT received_at
    }
    reconciliation_drifts {
        TEXT id PK
        TEXT run_id FK
        TEXT store_hash FK
        TEXT drift_type
        TEXT charge_id
        TEXT subscription_id
        TEXT detail
        TEXT created_at
    }
    reconciliation_runs {
        TEXT id PK
        TEXT store_hash FK
        TEXT started_at
        TEXT finished_at
        INTEGER charges_scanned
        INTEGER drift_count
        TEXT status
        TEXT error_detail
    }
    stores ||--o{ reconciliation_runs : "store_hash"
    reconciliation_runs ||--o{ reconciliation_drifts : "run_id"
    stores ||--o{ reconciliation_drifts : "store_hash"
    stores ||--o{ dunning_policies : "store_hash"
    charges ||--o{ exceptions : "charge_id"
    payment_methods ||--o{ network_updater_events : "payment_method_id"
    stores ||--o{ network_updater_events : "store_hash"

Cross-domain stub entities — full attributes live in: charges (Core subscription lifecycle — part 1), payment_methods (Core subscription lifecycle — part 1), stores (Core subscription lifecycle — part 2).

Extensions

7 tables.

erDiagram
    bundle_charges {
        TEXT bundle_id PK
        TEXT charge_id PK
        TEXT role
        INTEGER included
        INTEGER shipping_allocation_cents
    }
    bundle_plan_items {
        TEXT id PK
        TEXT store_hash
        TEXT plan_id
        TEXT variant_id
        INTEGER quantity
        REAL discount_pct
        INTEGER position
        INTEGER active
        TEXT created_at
        TEXT updated_at
    }
    bundles {
        TEXT id PK
        TEXT store_hash FK
        TEXT status
        TEXT anchor_date
        INTEGER bc_order_id
        INTEGER window_days
        INTEGER is_deleted
        TEXT created_at
        TEXT updated_at
    }
    curation_cycle_picks {
        TEXT id PK
        TEXT store_hash
        TEXT subscription_id FK
        TEXT plan_id
        TEXT cycle_period_start
        TEXT variant_id
        INTEGER quantity
        TEXT rotation_rule_id
        TEXT picked_at
    }
    curation_rotation_rules {
        TEXT id PK
        TEXT store_hash
        TEXT plan_id
        TEXT variant_id
        INTEGER quantity
        INTEGER position
        INTEGER active
        TEXT created_at
        TEXT updated_at
    }
    delivery_instances {
        TEXT id PK
        TEXT subscription_id FK
        TEXT store_hash FK
        TEXT scheduled_at
        TEXT status
        TEXT override_payload
        TEXT materialized_at
        TEXT created_at
        TEXT updated_at
        INTEGER is_deleted
        TEXT charge_id
        INTEGER bc_order_id
        TEXT claimed_at
    }
    subscription_extensions {
        TEXT subscription_id PK
        TEXT store_hash
        TEXT extension_type PK
        TEXT extension_data
        INTEGER extension_version
        TIMESTAMP created_at
        TIMESTAMP updated_at
    }
    subscriptions ||--o{ delivery_instances : "subscription_id"
    stores ||--o{ delivery_instances : "store_hash"
    subscriptions ||--o{ subscription_extensions : "subscription_id"
    stores ||--o{ bundles : "store_hash"
    bundles ||--o{ bundle_charges : "bundle_id"
    charges ||--o{ bundle_charges : "charge_id"
    subscriptions ||--o{ curation_cycle_picks : "subscription_id"

Cross-domain stub entities — full attributes live in: charges (Core subscription lifecycle — part 1), stores (Core subscription lifecycle — part 2), subscriptions (Core subscription lifecycle — part 2).

B2B

5 tables.

erDiagram
    allotment_debits {
        TEXT id PK
        TEXT grant_id FK
        TEXT store_hash
        INTEGER amount
        TEXT reference_type
        TEXT reference_id
        TIMESTAMP debited_at
    }
    allotment_grants {
        TEXT id PK
        TEXT store_hash
        TEXT customer_id
        TEXT org_id
        TEXT unit_type
        INTEGER amount_per_period
        INTEGER current_balance
        TEXT refresh_cadence
        TEXT rollover_policy
        INTEGER rollover_cap
        TIMESTAMP next_refresh_at
        TIMESTAMP expires_at
        TIMESTAMP created_at
        TEXT status
        TIMESTAMP revoked_at
        TEXT revoked_by
        TEXT revoke_reason
        TIMESTAMP suspended_at
        TEXT suspended_by
    }
    plan_customer_group_sales_mode {
        TEXT plan_id PK
        INTEGER bc_customer_group_id PK
        TEXT sales_mode
        TEXT created_at
        TEXT updated_at
    }
    usage_cycle_totals {
        TEXT id PK
        TEXT store_hash
        TEXT subscription_id
        TEXT meter_name
        TEXT cycle_period_start
        REAL total_quantity
        INTEGER event_count
        TEXT last_updated_at
    }
    usage_events {
        TEXT id PK
        TEXT store_hash
        TEXT subscription_id FK
        TEXT meter_name
        REAL quantity
        TEXT cycle_period_start
        TEXT idempotency_key
        TEXT reported_at
        TEXT metadata
    }
    allotment_grants ||--o{ allotment_debits : "grant_id"
    subscriptions ||--o{ usage_events : "subscription_id"
    plans ||--o{ plan_customer_group_sales_mode : "plan_id"

Cross-domain stub entities — full attributes live in: plans (Core subscription lifecycle — part 2), subscriptions (Core subscription lifecycle — part 2).

Promotions & discounts

9 tables.

erDiagram
    product_discount_base_overrides {
        TEXT store_hash PK
        INTEGER bc_product_id PK
        TEXT discount_base
        TEXT created_at
        TEXT updated_at
    }
    product_exclusions {
        TEXT store_hash PK
        INTEGER bc_product_id PK
        TEXT reason
        TEXT created_at
        INTEGER created_by
        INTEGER is_deleted
    }
    promotion_settings {
        INTEGER id PK
        TEXT store_hash
        INTEGER bc_promotion_id
        INTEGER subscription_only
        TEXT created_at
        TEXT updated_at
        INTEGER is_deleted
        TEXT stacking_rule
    }
    referral_codes {
        TEXT id PK
        TEXT store_hash
        TEXT customer_id
        TEXT subscription_id FK
        TEXT code
        INTEGER credit_amount_cents
        TEXT currency
        TEXT status
        TEXT created_at
    }
    referral_credits {
        TEXT id PK
        TEXT store_hash
        TEXT referral_code_id FK
        TEXT subscription_id FK
        TEXT referral_event_id
        INTEGER amount_cents
        TEXT currency
        TEXT status
        TEXT applied_charge_id
        TEXT reversed_reason
        TEXT created_at
        TEXT applied_at
        TEXT reversed_at
    }
    referral_events {
        TEXT id PK
        TEXT store_hash
        TEXT referral_code_id FK
        TEXT referred_customer_id
        TEXT event_type
        TEXT charge_id
        TEXT created_at
    }
    subscription_discounts {
        TEXT id PK
        TEXT subscription_id FK
        INTEGER percent
        INTEGER applies_to_cycle
        TEXT expires_at
        TEXT created_at
        TEXT applied_at
        TEXT reason
        INTEGER cycle_min
        INTEGER cycle_max
    }
    subscription_promotion_applications {
        TEXT id PK
        TEXT subscription_id FK
        TEXT charge_id FK
        TEXT promotion_id FK
        TEXT source
        INTEGER amount_cents
        TEXT discount_type
        INTEGER cycle_index_applied
        TEXT applied_at
    }
    subscription_promotions {
        TEXT id PK
        TEXT store_hash
        TEXT name
        TEXT code
        TEXT application_window
        TEXT config
        TEXT eligibility_rules
        TEXT discount_type
        TEXT stacking_rule
        TEXT lock_policy
        TEXT applies_to
        INTEGER recurring
        TEXT status
        INTEGER max_redemptions
        INTEGER current_redemptions
        TEXT starts_at
        TEXT ends_at
        TEXT created_at
        TEXT updated_at
        TEXT archived_at
        INTEGER is_deleted
    }
    subscriptions ||--o{ subscription_discounts : "subscription_id"
    stores ||--o{ product_exclusions : "store_hash"
    subscriptions ||--o{ referral_codes : "subscription_id"
    referral_codes ||--o{ referral_events : "referral_code_id"
    referral_codes ||--o{ referral_credits : "referral_code_id"
    subscriptions ||--o{ referral_credits : "subscription_id"
    subscriptions ||--o{ subscription_promotion_applications : "subscription_id"
    charges ||--o{ subscription_promotion_applications : "charge_id"
    subscription_promotions ||--o{ subscription_promotion_applications : "promotion_id"
    stores ||--o{ product_discount_base_overrides : "store_hash"

Cross-domain stub entities — full attributes live in: charges (Core subscription lifecycle — part 1), stores (Core subscription lifecycle — part 2), subscriptions (Core subscription lifecycle — part 2).

Notifications & email

13 tables.

erDiagram
    email_domains {
        TEXT store_hash PK
        TEXT domain PK
        TEXT resend_domain_id
        TEXT dkim_record
        TEXT dkim_record_name
        TEXT verification_status
        INTEGER dmarc_aligned
        TIMESTAMP last_verification_attempt_at
        TIMESTAMP verified_at
        TIMESTAMP created_at
        TEXT send_state
        TEXT send_state_reason
        TEXT send_state_updated_at
        REAL last_bounce_rate
        REAL last_complaint_rate
        INTEGER last_sample_size
    }
    email_idempotency {
        INTEGER id PK
        TEXT store_hash
        TEXT idempotency_key
        TEXT template_key
        TEXT recipient
        TEXT source_event_id
        TEXT provider_message_id
        TEXT created_at
    }
    email_send_log {
        TEXT id PK
        TEXT send_key
        TEXT event_id FK
        TEXT recipient_email
        TEXT template_id
        TEXT locale
        TEXT vendor_message_id
        TEXT vendor_status
        TEXT bounce_class
        TEXT attempted_at
        TEXT delivered_at
        TEXT bounced_at
    }
    email_sends {
        INTEGER id PK
        TEXT store_hash
        TEXT subscription_id
        TEXT customer_email
        TEXT template_key
        TEXT provider
        TEXT provider_message_id
        TEXT status
        TEXT last_event_at
        TEXT last_processed_event_id
        TEXT bounce_type
        TEXT bounce_subtype
        TEXT error_message
        TEXT created_at
        TEXT updated_at
        INTEGER is_deleted
    }
    email_suppressions {
        INTEGER id PK
        TEXT store_hash
        TEXT customer_email
        TEXT reason
        TEXT evidence_event_id
        TEXT created_at
    }
    email_templates {
        TEXT id
        TEXT store_hash PK
        TEXT template_key PK
        TEXT format
        TEXT subject
        TEXT body_source
        TEXT variables_used
        INTEGER is_default
        TIMESTAMP created_at
        TIMESTAMP updated_at
    }
    klaviyo_connections {
        INTEGER id PK
        TEXT store_hash
        TEXT api_key_encrypted
        TEXT api_revision
        TEXT event_map_json
        INTEGER is_active
        INTEGER is_deleted
        TEXT created_at
        TEXT updated_at
        TEXT created_by
        TEXT updated_by
    }
    klaviyo_delivery_attempts {
        INTEGER id PK
        INTEGER klaviyo_connection_id FK
        TEXT store_hash
        TEXT event_id
        TEXT our_event_type
        TEXT klaviyo_metric_name
        TEXT request_body_json
        TEXT status
        INTEGER attempt_count
        INTEGER last_response_code
        TEXT last_response_body
        TEXT last_attempted_at
        TEXT next_retry_at
        TEXT created_at
    }
    slack_destinations {
        INTEGER id PK
        TEXT store_hash FK
        TEXT webhook_url_encrypted
        TEXT channel_hint
        INTEGER is_active
        TEXT created_at
        TEXT updated_at
    }
    slack_dispatch_attempts {
        INTEGER id PK
        TEXT store_hash
        TEXT kind
        TEXT metric
        TEXT payload_json
        TEXT status
        INTEGER attempt_count
        INTEGER last_response_code
        TEXT last_response_body
        TEXT last_attempted_at
        TEXT next_retry_at
        TEXT idempotency_key
        TEXT created_at
    }
    sms_sends {
        INTEGER id PK
        TEXT store_hash
        TEXT subscription_id
        TEXT customer_id
        TEXT to_phone_e164
        TEXT template_key
        TEXT provider
        TEXT provider_message_sid
        TEXT status
        TEXT error_code
        TEXT error_message
        TEXT created_at
        TEXT updated_at
        INTEGER is_deleted
    }
    subscriber_preference_definitions {
        TEXT id PK
        TEXT store_hash FK
        TEXT pref_key
        TEXT pref_label
        TEXT pref_type
        TEXT options
        TEXT excludes_variant_map
        INTEGER required
        INTEGER display_order
        INTEGER active
        TIMESTAMP created_at
        TIMESTAMP updated_at
    }
    subscriber_preferences {
        TEXT store_hash PK
        TEXT customer_id PK
        TEXT prefs
        TIMESTAMP created_at
        TIMESTAMP updated_at
    }
    events ||--o{ email_send_log : "event_id"
    stores ||--o{ subscriber_preference_definitions : "store_hash"
    customers ||--o{ subscriber_preferences : "customer_id"
    klaviyo_connections ||--o{ klaviyo_delivery_attempts : "klaviyo_connection_id"
    stores ||--o{ slack_destinations : "store_hash"

Cross-domain stub entities — full attributes live in: customers (Core subscription lifecycle — part 1), events (Webhooks & integrations), stores (Core subscription lifecycle — part 2).

Webhooks & integrations

4 tables.

erDiagram
    events {
        TEXT id PK
        TEXT store_hash FK
        TEXT type
        TEXT subscription_id FK
        TEXT charge_id FK
        TEXT customer_id FK
        INTEGER actor_user_id
        TEXT actor_kind
        TEXT payload
        TEXT created_at
        TEXT queue_published_at
        TEXT target_type
        TEXT target_id
        TEXT resource_type
        TEXT resource_id
        TEXT summary
        TEXT operation_type
        TEXT request_id
        TEXT ip_address
        TEXT fields_changed
    }
    installed_extensions {
        TEXT store_hash PK
        TEXT extension_id PK
        TEXT name
        TEXT status
        TEXT granted_scopes
        TEXT admin_panels
        TIMESTAMP installed_at
        TIMESTAMP uninstalled_at
    }
    webhook_delivery_attempts {
        INTEGER id PK
        INTEGER webhook_subscription_id FK
        TEXT event_type
        TEXT event_payload_json
        TEXT status
        INTEGER attempt_count
        INTEGER last_response_code
        TEXT last_response_body
        TEXT last_attempted_at
        TEXT next_retry_at
        TEXT idempotency_key
        TEXT created_at
    }
    webhook_subscriptions {
        INTEGER id PK
        TEXT store_hash
        TEXT endpoint_url
        TEXT events_json
        TEXT secret_hash
        INTEGER is_active
        INTEGER is_deleted
        TEXT created_at
        TEXT updated_at
        TEXT created_by
        TEXT updated_by
        TEXT field_subscriptions
        TEXT query_filter
        TEXT payload_projection
    }
    stores ||--o{ events : "store_hash"
    subscriptions ||--o{ events : "subscription_id"
    charges ||--o{ events : "charge_id"
    customers ||--o{ events : "customer_id"
    webhook_subscriptions ||--o{ webhook_delivery_attempts : "webhook_subscription_id"

Cross-domain stub entities — full attributes live in: charges (Core subscription lifecycle — part 1), customers (Core subscription lifecycle — part 1), stores (Core subscription lifecycle — part 2), subscriptions (Core subscription lifecycle — part 2).

Admin/CS & audit

6 tables.

erDiagram
    alert_thresholds {
        INTEGER id PK
        TEXT store_hash FK
        TEXT metric
        INTEGER window_minutes
        TEXT comparator
        INTEGER threshold_value
        INTEGER is_active
        TEXT created_at
        TEXT updated_at
        TEXT email_alert_address
        TEXT email_digest_address
    }
    consent_records {
        TEXT id PK
        TEXT subscription_id FK
        TEXT store_hash
        TEXT consent_version
        TEXT consent_text_ref
        TEXT disclosed_terms
        TIMESTAMP consented_at
        TEXT actor
        TEXT channel
        TIMESTAMP created_at
    }
    cs_tool_sessions {
        TEXT id PK
        TEXT store_hash
        TEXT subscription_id
        INTEGER operator_id
        TEXT expires_at
        TEXT used_at
        TEXT ip_addr
        TEXT created_at
    }
    helpdesk_connections {
        TEXT id PK
        TEXT store_hash FK
        TEXT provider
        TEXT account_subdomain
        TEXT account_username
        TEXT api_token_encrypted
        TEXT status
        TEXT config
        TEXT created_at
        TEXT updated_at
        INTEGER created_by
        INTEGER updated_by
        INTEGER is_deleted
    }
    helpdesk_tickets {
        TEXT id PK
        TEXT store_hash
        TEXT subscription_id
        TEXT provider
        TEXT external_ticket_id
        TEXT trigger_reason
        TEXT status
        TEXT context_json
        TEXT created_at
        TEXT updated_at
    }
    operations {
        TEXT id PK
        TEXT store_hash FK
        TEXT type
        TEXT reference_type
        TEXT reference_id
        TEXT metadata
        INTEGER done
        TEXT error
        TEXT result
        INTEGER attempts
        TEXT created_at
        TEXT updated_at
        TEXT completed_at
        INTEGER is_deleted
        INTEGER max_attempts
        TIMESTAMP locked_until
    }
    stores ||--o{ operations : "store_hash"
    stores ||--o{ helpdesk_connections : "store_hash"
    stores ||--o{ alert_thresholds : "store_hash"
    subscriptions ||--o{ consent_records : "subscription_id"

Cross-domain stub entities — full attributes live in: stores (Core subscription lifecycle — part 2), subscriptions (Core subscription lifecycle — part 2).

Imports & migration

1 tables.

erDiagram
    subscription_import_jobs {
        TEXT id PK
        TEXT store_hash
        INTEGER operator_id
        TEXT status
        INTEGER total_rows
        INTEGER valid_rows
        INTEGER error_rows
        TEXT csv_payload
        TEXT preview_json
        TEXT result_json
        TEXT created_at
        TEXT confirmed_at
        TEXT completed_at
    }

Platform & misc

9 tables.

erDiagram
    api_keys {
        TEXT id PK
        TEXT store_hash FK
        TEXT name
        TEXT key_prefix
        TEXT key_hash
        TEXT scopes
        TEXT created_at
        INTEGER created_by
        TEXT last_used_at
        TEXT revoked_at
    }
    custom_field_definitions {
        TEXT id PK
        TEXT store_hash
        TEXT scope
        TEXT scope_id
        TEXT field_key
        TEXT field_type
        TEXT field_label
        INTEGER required
        TEXT validation_regex
        TEXT options
        TEXT subscriber_visibility
        INTEGER display_order
        TIMESTAMP created_at
        INTEGER is_archived
        TIMESTAMP archived_at
    }
    experiment_assignments {
        TEXT store_hash PK
        TEXT experiment_id PK
        TEXT subject_key PK
        TEXT variant_key
        TIMESTAMP assigned_at
        TIMESTAMP converted_at
    }
    experiments {
        TEXT id PK
        TEXT store_hash
        TEXT experiment_key
        TEXT surface
        TEXT status
        TEXT variants
        TIMESTAMP created_at
    }
    idempotency_keys {
        TEXT key PK
        TEXT store_hash PK
        TEXT request_fingerprint
        INTEGER response_status
        TEXT response_body
        TEXT response_headers
        TIMESTAMP created_at
    }
    job_configuration {
        TEXT cron_name PK
        TEXT store_hash PK
        TEXT config_key PK
        TEXT config_value
        INTEGER config_version
        TIMESTAMP updated_at
    }
    magic_link_tokens {
        TEXT token_hash PK
        TEXT store_hash
        TEXT customer_id
        TIMESTAMP expires_at
        TIMESTAMP consumed_at
        TIMESTAMP created_at
    }
    portal_custom_domains {
        TEXT store_hash PK
        TEXT domain PK
        TEXT status
        TEXT expected_cname_target
        TIMESTAMP last_verification_attempt_at
        TEXT last_verification_detail
        TIMESTAMP verified_at
        TIMESTAMP created_at
    }
    sandboxes {
        TEXT id PK
        TEXT owner_store_hash
        TEXT sandbox_store_hash
        TEXT api_key_id
        TEXT status
        INTEGER test_subscription_count
        TIMESTAMP created_at
        TIMESTAMP destroyed_at
    }
    stores ||--o{ api_keys : "store_hash"

Cross-domain stub entities — full attributes live in: stores (Core subscription lifecycle — part 2).