Group 1 — Core: Users & Authentication3 tables
1.1 api_keys
Purpose: API key management for subscribers. Stores every issued API key along with its tier, rate limits, usage counters, Stripe billing references, and subscription state. Central table for authentication and authorization of all LPO/LRS API requests.
24 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the API key record |
user_id FK → users.id | uuid | — | YES | Owner of the key; references the users table |
key_hash | text | — | YES | Legacy hash field (unused in current implementation) |
name | text | — | YES | Subscriber name associated with this key |
tier | text | 'free' | NO | Subscription tier: free, pro, enterprise, unlimited, lifetime, or partner |
query_limit | integer | 1000 | NO | Maximum number of API queries allowed per month |
current_usage | integer | 0 | NO | Current month's query count; reset monthly by cron job |
last_used | timestamptz | — | YES | Timestamp of the most recent API call using this key |
created_at | timestamptz | now() | NO | When this API key was created |
revoked | boolean | false | NO | Whether this key has been disabled/revoked |
rate_limit_qps | integer | 10 | NO | Maximum queries per second allowed for this key |
burst_limit | integer | 20 | NO | Token bucket burst capacity for rate limiting |
burst_tokens | numeric | 20.0 | NO | Current token balance in the rate-limit token bucket |
api_key | text | — | NO | The actual API key string (plaintext); used for authentication |
last_success | timestamptz | — | YES | Timestamp of the last successful (non-error) API call |
minimum_wait_seconds | numeric | 0.1 | NO | Minimum time between requests when the key is being throttled |
lrs_enabled | boolean | false | NO | Whether unlimited LRS (Listener Report Service) reports are enabled |
response_format | text | 'tbc' | NO | Preferred response format: tbc (The Trinity Beast Canonical) or plain |
stripe_customer_id | text | — | YES | Stripe customer ID used for webhook event lookups |
stripe_subscription_id | text | — | YES | Main LPO subscription ID in Stripe |
stripe_lrs_subscription_id | text | — | YES | Separate LRS add-on subscription ID in Stripe |
subscription_status | text | 'active' | NO | Current subscription state: active, past_due, or canceled |
tier_effective_date | timestamptz | — | YES | When the current tier took effect (for proration tracking) |
payment_failed_at | timestamptz | — | YES | Timestamp of the first payment failure (triggers grace period logic) |
Indexes
api_keys_pkey — Primary key on id
idx_api_keys_stripe_customer_id — Partial index on stripe_customer_id WHERE stripe_customer_id IS NOT NULL
idx_api_keys_stripe_subscription_id — Partial index on stripe_subscription_id WHERE stripe_subscription_id IS NOT NULL
1.2 rate_limit_template
Purpose: Rate limit presets by subscription tier. Defines QPS, burst, query limits, and minimum wait configurations for each tier. Used by the Lambda receipt handler when creating new API keys and by the LPO server for runtime rate limiting. Single source of truth for all tier configurations — 11 tiers: 6 LPO + 5 Webhook.
8 columns 11 tiers
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the template |
tier | varchar | — | NO | Tier name: free, pro, enterprise, unlimited, lifetime, partner (LPO) or webhook_starter, webhook_standard, webhook_professional, webhook_enterprise, webhook_partner (Webhook) |
query_limit | integer | — | NO | Monthly query limit (999999999 = unlimited) |
rate_limit_qps | integer | — | NO | Queries per second limit (0 = no limit for partner) |
burst_limit | integer | — | NO | Token bucket burst capacity |
burst_tokens | numeric | — | NO | Initial token balance |
min_wait_seconds | numeric | — | NO | Minimum wait between requests in seconds (0 = no wait) |
description | text | — | YES | Human-readable description of the tier |
Indexes
rate_limit_template_pkey — Primary key on id
rate_limit_template_tier_key — Unique on tier
Current Tier Data (11 rows):
| Tier | Query Limit | QPS | Burst | Min Wait |
| LPO Subscription Tiers (6) |
| free | 1,000 | 1 | 5 | 1.0s |
| pro | 50,000 | 5 | 10 | 0.2s |
| enterprise | 500,000 | 10 | 20 | 0.1s |
| unlimited | Unlimited | 83 | 100 | 0.012s |
| lifetime | Unlimited | 83 | 100 | 0.012s |
| partner | Unlimited | 0 (none) | 0 (none) | 0 (none) |
| Webhook Associate Tiers (5) — all numbers multiples of 3 |
| webhook_starter | 999 | 3 | 6 | 0.3s |
| webhook_standard | 4,998 | 6 | 12 | 0.15s |
| webhook_professional | 9,999 | 12 | 24 | 0.06s |
| webhook_enterprise | 49,998 | 24 | 48 | 0.03s |
| webhook_partner | Unlimited | 0 (none) | 0 (none) | 0 (none) |
1.3 users
Purpose: Subscriber and donor accounts. Minimal user profile storing email, name, tier, and preferred language. Referenced by api_keys and transactions tables. Preferred language is captured from the Stripe checkout locale at subscription/donation time.
6 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the user |
email | text | — | NO | User's email address (UNIQUE) |
name | text | — | YES | User's display name |
tier | text | — | YES | Current subscription tier |
preferred_lang | varchar(5) | NULL | YES | Preferred language code (e.g., en, de, ja) — captured from Stripe checkout locale |
created_at | timestamptz | now() | NO | Account creation timestamp |
Indexes
users_pkey — Primary key on id
users_email_key — Unique constraint on email
Group 2 — Configuration3 tables
2.1 application_parameter_profiles
Purpose: System-mode parameter profiles. Stores named configuration profiles (demo, fresh-price, stress, debug, etc.) that can be applied at runtime via GET /admin/system-mode?mode=<profile_name>. Each profile defines a complete set of tuning parameters — rate limits, cache TTLs, connection pool sizes, batch settings, and logging levels. Profiles can be added or modified via SQL without code deploys. Created at startup via idempotent migration with default seed profiles.
24 columns
| Column | Data Type | Default | Nullable | Description |
id PK | serial | auto-increment | NO | Auto-increment primary key |
profile_name | text | — | NO | Unique profile identifier (UNIQUE); used in system-mode API calls |
description | text | — | YES | Human-readable description of the profile's purpose and tuning rationale |
qps | integer | 1000 | NO | Queries per second rate limit applied globally |
burst | integer | 1000 | NO | Token bucket burst capacity for rate limiting |
log_level | text | 'info' | NO | Application log level: debug, info, warn, or error |
sqs_batch_size | integer | 10 | NO | Messages per SQS SendMessageBatch call (1-10) |
sqs_flush_ms | integer | 100 | NO | SQS producer flush interval in milliseconds |
sqs_buffer_size | integer | 50000 | NO | SQS producer channel buffer capacity |
sqs_timeout_ms | integer | 3000 | NO | Per-batch SQS API call timeout in milliseconds |
db_max_open | integer | 150 | NO | Maximum open Aurora database connections |
db_max_idle | integer | 75 | NO | Maximum idle Aurora database connections |
db_conn_lifetime_min | integer | 10 | NO | Maximum lifetime of a database connection in minutes |
db_conn_idle_min | integer | 5 | NO | Maximum idle time for a database connection in minutes |
cache_ttl | integer | 9 | NO | ElastiCache price TTL in seconds before expiry |
config_poll | integer | 300 | NO | Interval in seconds between application parameter reloads |
cache_pool_size | integer | 600 | NO | ElastiCache connection pool size (must be multiple of 3 — one per container) |
cache_min_idle | integer | 120 | NO | Minimum idle ElastiCache connections pre-warmed in the pool |
cache_max_retries | integer | 1 | NO | Maximum retry attempts for failed ElastiCache operations |
cache_dial_ms | integer | 500 | NO | ElastiCache connection dial timeout in milliseconds |
cache_read_ms | integer | 500 | NO | ElastiCache read timeout in milliseconds |
cache_write_ms | integer | 500 | NO | ElastiCache write timeout in milliseconds |
created_at | timestamptz | now() | NO | When this profile was created |
updated_at | timestamptz | now() | NO | Last time this profile was modified |
Indexes
application_parameter_profiles_pkey — Primary key on id
application_parameter_profiles_profile_name_key — Unique constraint on profile_name
Seeded Profiles
demo — Low-throughput demo mode (QPS 3, TTL 30s, verbose logging)
debug — Debug mode with verbose logging and relaxed limits
fresh-price — Production mode with 13s cache TTL for near-real-time pricing
stress — Stress testing baseline (QPS 100K, TTL 300s)
stress-tcp-direct — TCP direct single-node stress testing
stress-tcp-alb — TCP via ALB multi-node stress testing
stress-udp-direct — UDP direct single-node stress testing
stress-udp-nlb — UDP via NLB multi-node stress testing
2.2 application_parameters
Purpose: Runtime configuration parameters. Stores key-value pairs that control application behavior (cache TTLs, prewarm intervals, feature flags, pool sizes, etc.). Values are stored as text and parsed by the application at load time. Synced to ElastiCache as the app:config hash.
4 columns
| Column | Data Type | Default | Nullable | Description |
id PK | integer | serial | NO | Auto-increment primary key |
key | text | — | NO | Parameter name (UNIQUE); e.g. cache_ttl_seconds, log_level |
value | text | — | NO | Parameter value stored as text; parsed by the application |
updated_at | timestamptz | now() | NO | Last time this parameter was modified |
Indexes
application_parameters_pkey — Primary key on id
application_parameters_key_key — Unique constraint on key
2.3 report_parameters
Purpose: LRS-specific configuration parameters. Same structure as application_parameters but dedicated to the Listener Report Service. Controls report limits, retention periods, and LRS feature flags.
4 columns
| Column | Data Type | Default | Nullable | Description |
id PK | integer | serial | NO | Auto-increment primary key |
key | text | — | NO | Parameter name (UNIQUE) |
value | text | — | NO | Parameter value stored as text |
updated_at | timestamptz | now() | NO | Last time this parameter was modified |
Indexes
report_parameters_pkey — Primary key on id
report_parameters_key_key — Unique constraint on key
Group 3 — Billing & Payments2 tables
3.1 payment_links
Purpose: Stripe Payment Link management for the public checkout endpoint. Stores payment links by type and tier, enabling dynamic checkout redirects. Each link tracks click counts and can be enabled/disabled without deletion. Managed via /admin/payment-links API.
11 columns
| Column | Data Type | Default | Nullable | Description |
id PK | serial | auto-increment | NO | Auto-increment primary key |
link_type | text | — | NO | Payment link type (e.g. subscription, donation, lrs-addon) |
tier | text | — | NO | Subscription tier this link applies to (e.g. pro, enterprise, unlimited, lifetime) |
display_name | text | '' | NO | Human-readable display name for the payment link |
stripe_url | text | — | NO | Full Stripe Payment Link URL |
return_url | text | '' | NO | URL to redirect to after successful payment |
enabled | boolean | true | NO | Whether this payment link is active and available for checkout |
click_count | bigint | 0 | NO | Number of times this payment link has been accessed via /checkout |
notes | text | — | YES | Internal notes about this payment link |
created_at | timestamptz | now() | NO | When this payment link was created |
updated_at | timestamptz | now() | NO | Last time this payment link was modified |
Indexes
payment_links_pkey — Primary key on id
payment_links_link_type_tier_key — Unique constraint on (link_type, tier)
idx_payment_links_type — Index on link_type
idx_payment_links_enabled — Index on enabled
3.2 transactions
Purpose: Payment records for all Stripe transactions. Covers subscriptions, donations, and LRS add-on purchases. Links to the users table and stores the Stripe session/charge IDs for reconciliation. Preferred language is captured from the Stripe checkout locale.
15 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for this transaction |
user_id FK → users.id | uuid | — | YES | User who made the payment |
type | text | — | YES | Transaction type: subscription, donation, or lrs-addon |
stripe_session_id | text | — | YES | Stripe checkout session ID |
stripe_charge_id | text | — | YES | Stripe payment intent / charge ID |
amount_usd | numeric | — | YES | Payment amount in USD |
status | text | — | YES | Transaction status: completed or refunded |
donor_name | text | — | YES | Name of the donor (for donation transactions) |
donor_email | text | — | YES | Email of the donor (for donation transactions) |
tier | text | — | YES | Subscription tier at the time of purchase |
api_key | text | — | YES | API key generated as part of this subscription purchase |
recurring | boolean | — | YES | Whether this is a recurring subscription payment |
preferred_lang | varchar(5) | NULL | YES | Language code at checkout time (e.g., en, de, ja) — from Stripe locale |
created_at | timestamptz | now() | NO | When the transaction was recorded |
updated_at | timestamptz | now() | NO | Last time the transaction record was updated |
Indexes
transactions_pkey — Primary key on id
Group 4 — Usage Logging4 tables
4.1 monthly_report_counts
Purpose: Monthly report usage aggregation. Stores a per-API-key, per-month count of LRS reports generated. Used for billing reconciliation and historical usage analytics.
4 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | — | NO | Unique identifier |
api_key_id | uuid | — | YES | API key this count belongs to |
month | date | — | YES | First day of the month this count covers |
count | integer | — | YES | Total reports generated in this month |
Indexes
monthly_report_counts_pkey — Primary key on id
4.2 report_count
Purpose: Per-API-key report limit tracking. Enforces monthly caps on LRS report generation. The reset_month column enables automatic month rollover — when the current month differs from reset_month, the counter resets to zero without a cron job. Daily counts are tracked but not enforced (subscribers choose their own pace).
8 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier |
api_key FK → api_keys.id | uuid | — | NO | API key this counter belongs to |
monthly_count | integer | 0 | NO | Number of reports generated this month (auto-resets when reset_month changes) |
daily_count | integer | 0 | NO | Number of reports generated today (resets when last_report_date changes) |
last_report_date | timestamptz | now() | NO | Timestamp of the most recent report request |
monthly_limit | integer | 10 | NO | Maximum reports allowed per month |
daily_limit | integer | 5 | NO | Maximum reports allowed per day (tracked but not enforced) |
reset_month | integer | EXTRACT(MONTH FROM NOW()) | NO | Month number (1–12) when counters were last reset — enables automatic month rollover without cron |
Indexes
report_count_pkey — Primary key on id
4.3 report_usage_logs
Purpose: Every LRS report request is logged here. Tracks which report type was requested, the output format, date/asset filters applied, row count returned, and processing time. Used for auditing, the /reports/report-usage and /reports/report-summary meta-report endpoints, and MemoryDB cache mirroring.
13 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for this report log entry |
api_key_id | text | — | YES | API key that requested the report |
report_type | text | — | YES | Report type: usage, summary, report-usage, or report-summary |
report_run_date | timestamptz | — | YES | When the report was executed |
asset_filter | text | — | YES | Asset filter applied to the report (e.g., BTC, ETH, or empty for all) |
start_date_filter | timestamptz | — | YES | Start date filter applied to the report query |
end_date_filter | timestamptz | — | YES | End date filter applied to the report query |
max_rows_returned | integer | — | YES | Number of rows returned in the report |
report_duration_ms | integer | — | YES | Report processing time in milliseconds |
output_format | text | — | YES | Output format: json, csv, tsv, or text |
additional_filters | jsonb | — | YES | Additional query filters as JSON (cached, source, etc.) |
region | text | — | YES | AWS region where the report was processed |
cluster_node | text | — | YES | Which ECS container handled the request (BeastMain, BeastMirror, BeastLRS) |
Indexes
report_usage_logs_pkey — Primary key on id
4.4 usage_logs
Purpose: Every LPO API call is logged here. Written by the QueuedWriter Lambda which drains the SQS queue in batches. Records the asset queried, price returned, source, latency, cache status, client IP, and query metadata. Primary data source for LRS usage and summary reports. Mirrored to MemoryDB for fast LRS queries.
18 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for this log entry |
user_id | text | — | YES | User ID associated with the API key |
api_key_id | text | — | YES | API key that was used for this request |
timestamp | timestamptz | now() | NO | When the request occurred (set by QueuedWriter at insert time) |
ip_address | text | — | YES | Client IP address |
query_type | text | — | YES | Type of query (e.g., price, multi-price) |
duration_ms | bigint | — | YES | Total request processing time in milliseconds |
token_count | integer | — | YES | Rate limit tokens consumed by this request |
latency_ms | bigint | — | YES | Source fetch latency in milliseconds (0 on cache hit) |
status | text | — | YES | Request outcome status (success, error, rate_limited) |
error | text | — | YES | Error message if the request failed (NULL on success) |
cached | boolean | — | YES | Whether the response was served from ElastiCache |
asset | text | — | YES | Cryptocurrency asset queried (BTC, ETH, SOL, etc.) |
cache_age_seconds | double precision | — | YES | Age of the cached price in seconds at time of request |
price | double precision | — | YES | Price returned to the caller |
source | text | — | YES | Price source: coinbase-ws, gemini-ws, kraken-ws, gateio-ws, bybit-ws, okx-ws |
cluster_node | text | — | YES | Which ECS container handled the request (BeastMain, BeastMirror, BeastLRS, BeastWebhook) |
aws_region | text | — | YES | AWS region where the request was processed (e.g., us-east-2) |
Indexes
usage_logs_pkey — Primary key on id
Group 5 — Exchange & Pricing3 tables
5.1 asset_categories
Purpose: Crypto asset category groupings. Organizes the 150 supported assets into logical categories (Layer 1, DeFi, Meme, Stablecoin, etc.) for the public GET /asset-categories endpoint. Display order controls the sort on the subscribe-listener page. Categories can be enabled/disabled without code deploys.
6 columns
| Column | Data Type | Default | Nullable | Description |
id PK | serial | auto-increment | NO | Auto-increment primary key |
category_name | text | — | NO | Category display name (e.g., "Layer 1", "DeFi", "Meme Coins") |
description | text | '' | NO | Brief description of the category |
display_order | integer | 100 | NO | Sort order for display — lower numbers appear first |
enabled | boolean | true | NO | Whether this category is visible in the API response |
created_at | timestamptz | now() | NO | When this category was created |
Indexes
asset_categories_pkey — Primary key on id
5.2 exchange_asset_map
Purpose: Exchange-to-asset symbol translations. Maps normalized asset names (BTC, ETH) to exchange-specific symbols (BTC-USD, btcusd, BTC/USD, BTCUSDT, BTC_USDT, BTC-USDT). Each exchange has 25 unique assets with zero overlap. 150 total assets across 6 exchanges. The category column powers the GET /asset-categories endpoint for grouped asset display. Managed via /admin/exchange-assets API.
6 columns
| Column | Data Type | Default | Nullable | Description |
id PK | serial | auto-increment | NO | Auto-increment primary key |
exchange_name | text | — | NO | Exchange this mapping belongs to (references exchange_feeds) |
asset | text | — | NO | Normalized asset name (BTC, ETH, SOL, etc.) |
exchange_symbol | text | — | NO | Exchange-specific symbol (BTC-USD, btcusd, BTC/USD, BTCUSDT, etc.) |
enabled | boolean | true | NO | Per-asset enable/disable switch |
category | text | 'Infrastructure' | NO | Asset category grouping (Major Currencies, DeFi, Layer 2, AI & Data, Gaming & NFTs, Meme Coins, Infrastructure) — references asset_categories |
Indexes
exchange_asset_map_pkey — Primary key on id
exchange_asset_map_exchange_name_asset_key — Unique constraint on (exchange_name, asset)
idx_exchange_asset_map_exchange — Index on exchange_name
idx_exchange_asset_map_asset — Index on asset
5.3 exchange_feeds
Purpose: Exchange WebSocket feed configurations. Table-driven exchange management — each row defines a complete WebSocket connection including endpoint, subscribe message template, JSON paths for parsing trade messages, ping requirements, and symbol formatting. Adding a new exchange is an INSERT, not a code deploy. Managed via the TBCC Exchange Manager widget and /admin/exchange-feeds API.
25 columns
| Column | Data Type | Default | Nullable | Description |
id PK | serial | auto-increment | NO | Auto-increment primary key |
exchange_name | text | — | NO | Unique exchange identifier (UNIQUE); e.g., coinbase, kraken, gateio |
display_name | text | — | NO | Human-readable name (e.g., "Gate.io", "Coinbase") |
ws_endpoint | text | — | NO | WebSocket URL (e.g., wss://ws.kraken.com/v2) |
subscribe_format | text | 'json' | NO | Subscribe message format (legacy column from initial migration) |
pair_suffix | text | 'USD' | NO | Quote currency: USD or USDT |
symbol_template | text | — | NO | How to build subscribe symbols (e.g., {ASSET}-USD, {asset}usdt) |
symbol_separator | text | '-' | NO | Separator in symbol format (-, /, _, or empty) |
parse_regex | text | — | YES | Legacy regex for parsing trade messages (superseded by JSON path columns) |
source_tag | text | — | NO | Tag in API responses and usage logs (e.g., coinbase-ws, gateio-ws) |
subscribe_template | text | — | YES | JSON template for the subscribe message with {SYMBOLS} placeholder |
trade_channel_match | text | — | YES | Rules to identify trade messages (e.g., channel=trade) |
price_path | text | — | YES | JSON path to the price field in trade messages |
symbol_path | text | — | YES | JSON path to the symbol field in trade messages |
timestamp_path | text | — | YES | JSON path to the timestamp field in trade messages |
timestamp_format | text | 'rfc3339' | YES | Timestamp format: rfc3339, unix_ms, unix_ns, unix_ms_string |
ping_interval_sec | integer | 0 | YES | Keepalive ping interval in seconds (0 = no ping needed) |
ping_message | text | — | YES | Ping payload (JSON string or plain text like "ping") |
symbol_style | text | 'standard' | YES | Subscribe message style: standard, coinbase, gemini, okx, bybit |
msg_is_array | boolean | false | YES | Whether trade messages arrive as JSON arrays (e.g., Gate.io sends arrays of trades) |
array_path | text | — | YES | JSON path to the array within the message when msg_is_array is true |
enabled | boolean | true | NO | Master switch — disable an exchange without a code deploy |
notes | text | — | YES | Operational notes (e.g., "Replaced Binance — geo-blocked from US") |
created_at | timestamptz | now() | NO | When this exchange feed was created |
updated_at | timestamptz | now() | NO | Last time this exchange feed was modified (updated on every save) |
Indexes
exchange_feeds_pkey — Primary key on id
exchange_feeds_exchange_name_key — Unique constraint on exchange_name
idx_exchange_feeds_enabled — Index on enabled for fast filtering
Group 6 — Email & Newsletters8 tables
6.1 email_drafts
Purpose: Email composition drafts. Stores in-progress emails being composed in the admin panel. References a template and sender identity, supports multiple recipients (to/cc/bcc as comma-separated text), and tracks draft status through to sent.
13 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the draft |
template_id FK → email_templates.id | uuid | — | YES | Email template this draft is based on |
sender_id FK → email_senders.id | uuid | — | YES | Verified sender identity to use for this email |
subject | varchar(500) | '' | NO | Email subject line |
content | text | '' | NO | Email body content (HTML) |
recipients | text | '' | NO | Comma-separated list of recipient email addresses |
cc | text | '' | NO | Comma-separated list of CC email addresses |
bcc | text | '' | NO | Comma-separated list of BCC email addresses |
status | varchar(20) | 'draft' | NO | Draft status: draft or sent |
created_at | timestamptz | now() | NO | When the draft was created |
updated_at | timestamptz | now() | NO | Last time the draft was modified |
sent_at | timestamptz | — | YES | When the email was sent (NULL while still a draft) |
Indexes
email_drafts_pkey — Primary key on id
6.2 email_senders
Purpose: Verified SES sender identities. Stores email addresses and display names that can be used as the "From" address in outbound emails. Each sender has a unique email and an optional reply-to address. Managed via /admin/email-senders API.
5 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the sender |
display_name | varchar(255) | — | NO | Sender display name (e.g., "Cross Power Ministries") |
email | varchar(255) | — | NO | Sender email address (UNIQUE) — must be verified in SES |
reply_to | varchar(255) | '' | NO | Reply-to email address (empty string if same as sender) |
created_at | timestamptz | now() | NO | When the sender was added |
Indexes
email_senders_pkey — Primary key on id
email_senders_email_key — Unique constraint on email
6.3 email_templates
Purpose: SES email templates. Stores reusable email templates for transactional and marketing emails sent via Amazon SES. Separate from newsletter_templates; these are used for system emails (receipts, welcome emails, etc.). Managed via /admin/email-templates API.
6 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the template |
topic | varchar(100) | — | NO | Template topic/category (e.g., subscription, donation, welcome) |
name | varchar(255) | — | NO | Template name (e.g., SubscriptionReceipt, DonationReceipt) |
subject | varchar(500) | '' | NO | Email subject line |
content | text | — | NO | HTML body content of the email template |
created_at | timestamptz | now() | NO | When the template was created |
Indexes
email_templates_pkey — Primary key on id
6.4 email_translations
Purpose: Multi-language email template field translations. Stores translated versions of SES email template fields (subject, heading, body, footer, etc.) for each supported language. 153 rows covering 9 languages × 2 templates (SubscriptionReceipt, DonationReceipt). The Lambda receipt handler looks up the subscriber's preferred_lang and uses the matching translation. Uses {{variable}} placeholders for dynamic content.
6 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the translation row |
template_name | varchar | — | NO | SES template name (e.g., SubscriptionReceipt, DonationReceipt) |
lang | varchar | — | NO | ISO language code (en, es, fr, de, pt, ja, ko, zh, ar) |
field_name | varchar | — | NO | Template field being translated (subject, heading, body, footer, etc.) |
field_value | text | — | NO | Translated content — may contain {{variable}} placeholders |
created_at | timestamptz | now() | YES | When this translation was created |
Indexes
email_translations_pkey — Primary key on id
- Unique constraint on
(template_name, lang, field_name)
6.5 newsletter_subscribers
Purpose: Newsletter mailing list. Stores subscribers from both the LPO and CPMP websites. Tracks subscription source, opt-in/opt-out status for CAN-SPAM compliance, and preferred language for localized newsletter delivery.
7 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the subscriber |
email | text | — | NO | Subscriber's email address (UNIQUE) |
name | text | — | YES | Subscriber's name |
source | text | — | YES | Where they subscribed from: lpo or cpmp |
preferred_lang | varchar | 'en' | YES | Preferred language for newsletter delivery — each subscriber receives their language version |
subscribed | boolean | true | NO | Whether the subscriber is currently opted in |
created_at | timestamptz | now() | NO | When the subscription was created |
Indexes
newsletter_subscribers_pkey — Primary key on id
newsletter_subscribers_email_key — Unique constraint on email
6.6 newsletter_templates
Purpose: Newsletter email templates. Reusable HTML templates for newsletter campaigns. Each template has a name, subject line, and full HTML body that can be used across multiple newsletter sends.
6 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the template |
name | text | — | YES | Template name for identification in the admin panel |
subject | text | — | YES | Default email subject line |
html_body | text | — | YES | Full HTML body of the newsletter template |
created_at | timestamptz | now() | NO | When the template was created |
updated_at | timestamptz | now() | NO | Last time the template was modified |
Indexes
newsletter_templates_pkey — Primary key on id
6.7 newsletter_translations
Purpose: Multi-language newsletter content. Stores translated versions of each newsletter for all 9 supported languages. Auto-generated via POST /newsletter/{id}/translate using AWS Translate, then optionally reviewed before sending. The newsletter send handler delivers each subscriber their language version based on newsletter_subscribers.preferred_lang.
9 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the translation |
newsletter_id FK | uuid | — | NO | References newsletters.id — the newsletter being translated |
lang | varchar | — | NO | ISO language code (en, es, fr, de, pt, ja, ko, zh, ar) |
subject | text | — | NO | Translated email subject line |
body_html | text | — | NO | Translated full HTML email body |
auto_translated | boolean | true | YES | Whether this was auto-generated by AWS Translate |
reviewed | boolean | false | YES | Whether a human has reviewed and approved the translation |
created_at | timestamptz | now() | YES | When this translation was created |
updated_at | timestamptz | now() | YES | Last time this translation was modified |
Indexes
newsletter_translations_pkey — Primary key on id
- Unique constraint on
(newsletter_id, lang)
6.8 newsletters
Purpose: Sent newsletter campaigns. Tracks each newsletter send including its template, delivery status, recipient count, and success/failure metrics. Status progresses from draft → sending → sent.
9 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the newsletter campaign |
template_id | uuid | — | YES | Newsletter template used for this send |
subject | text | — | YES | Subject line used for this send (may override template) |
status | text | — | YES | Campaign status: draft, sending, or sent |
recipients | integer | — | YES | Total number of intended recipients |
sent_count | integer | — | YES | Number of emails successfully sent |
failed_count | integer | — | YES | Number of emails that failed to send |
created_at | timestamptz | now() | NO | When the campaign was created |
sent_at | timestamptz | — | YES | When the campaign finished sending |
Indexes
newsletters_pkey — Primary key on id
Group 7 — Support & Engagement5 tables
7.1 demo_leads
Purpose: Demo registration captures. Stores leads from the enterprise demo request form on the website. Used by the sales pipeline for follow-up.
6 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the lead |
email | text | — | YES | Lead's email address |
name | text | — | YES | Lead's name |
company | text | — | YES | Lead's company name |
use_case | text | — | YES | Described use case for the API |
created_at | timestamptz | now() | NO | When the demo request was submitted |
Indexes
demo_leads_pkey — Primary key on id
7.2 map_pins
Purpose: CPMP mission impact map markers. Each pin represents a real-world impact event (medical aid, water well, wheelchair delivery, etc.) displayed on the interactive mission map. Supports multiple media attachments via the media_urls JSONB array.
10 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the map pin |
type | text | — | YES | Impact type: medical, water, wheelchair, freedom, provisions, word, or education |
lat | double precision | — | YES | Latitude coordinate |
lng | double precision | — | YES | Longitude coordinate |
region | text | — | YES | Geographic region name |
caption | text | — | YES | Description of the impact event shown on the pin |
media_url | text | — | YES | Legacy single media URL (superseded by media_urls) |
date | timestamptz | — | YES | Date of the impact event |
status | text | — | YES | Pin status: active or inactive |
media_urls | jsonb | — | YES | Array of media URLs (Google Drive, YouTube, direct images) |
Indexes
map_pins_pkey — Primary key on id
7.3 partner_applications
Purpose: AWS Partner application submissions. Stores applications from AWS companies requesting partner-tier API access to live crypto prices. The exchanges The Trinity Beast depends on share their data at no cost — this program passes that generosity forward to the AWS community. Applications are submitted via the public POST /partner/apply endpoint and reviewed through the TBCC Partner Management widget. Approved partners receive a tier=partner API key with no rate limiting, no monthly caps, and no billing.
16 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the application |
company_name | text | — | NO | Partner company name (e.g., Coinbase, Gemini) |
contact_name | text | — | NO | Primary contact person at the partner company |
contact_email | text | — | NO | Contact email for application correspondence |
aws_account_id | text | — | NO | Partner's 12-digit AWS account ID for PrivateLink/VPC Peering setup |
aws_region | text | — | NO | AWS region where the partner's VPC is located |
vpc_cidr | text | — | YES | Partner's VPC CIDR block (required for VPC Peering, optional for PrivateLink) |
connection_type | text | — | NO | Requested connection method: privatelink, peering, or privatelink,peering |
data_provided | text | — | YES | Description of what the partner is building and how they plan to use live crypto prices |
estimated_volume | text | — | YES | Estimated monthly API volume: under-100k, 100k-1m, 1m-10m, 10m-plus, unknown |
notes | text | — | YES | Additional notes from the applicant |
status | text | 'pending' | NO | Application status: pending, approved, rejected, or onboarded |
reviewed_at | timestamptz | — | YES | When the application was reviewed by an admin |
reviewer_notes | text | — | YES | Admin notes from the review process |
api_key_id | uuid | — | YES | Reference to the generated partner API key (set when onboarded) |
created_at | timestamptz | now() | NO | When the application was submitted |
Indexes
partner_applications_pkey — Primary key on id
idx_partner_apps_status — Index on status for filtering pending/approved applications
idx_partner_apps_email — Index on contact_email for duplicate detection
Application Lifecycle
pending → Application submitted, awaiting admin review in TBCC
approved → Admin approved, ready for infrastructure onboarding
onboarded → PrivateLink/VPC Peering configured, partner API key issued
rejected → Application declined with reviewer notes
7.4 support_replies
Purpose: Support ticket replies. Stores the conversation thread for each support ticket. Each reply is tagged with the sender type (admin or customer) and can include file attachments.
7 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for this reply |
ticket_id FK → support_tickets.id | uuid | — | NO | Parent ticket this reply belongs to |
sender | text | — | YES | Who sent the reply: admin or customer |
message | text | — | YES | Reply message body |
attachments | jsonb | — | YES | File attachments (array of URLs/metadata) |
created_at | timestamptz | now() | NO | When the reply was sent |
updated_at | timestamptz | now() | NO | Last time the reply was modified |
Indexes
support_replies_pkey — Primary key on id
Foreign Keys
ticket_id → support_tickets.id
7.5 support_tickets
Purpose: Customer support system. Stores support tickets submitted through the website or admin panel. Each ticket has a human-readable ticket number, status tracking, priority, and category classification.
12 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the ticket |
ticket_number | text | — | NO | Human-readable ticket number (UNIQUE) |
email | text | — | YES | Submitter's email address |
name | text | — | YES | Submitter's name |
subject | text | — | YES | Ticket subject line |
message | text | — | YES | Ticket body / initial message |
status | text | 'open' | NO | Ticket status: open, in-progress, resolved, or closed |
priority | text | 'normal' | NO | Ticket priority level |
category | text | — | YES | Ticket category for routing and reporting |
preferred_lang | varchar | 'en' | YES | Visitor's language at submission time — auto-captured from cpmp-lang cookie |
created_at | timestamptz | now() | NO | When the ticket was submitted |
updated_at | timestamptz | now() | NO | Last time the ticket was updated |
Indexes
support_tickets_pkey — Primary key on id
support_tickets_ticket_number_key — Unique constraint on ticket_number
Group 8 — Operations & Maintenance3 tables
8.1 cron_execution_logs
Purpose: Cron job execution tracking. General-purpose log for all scheduled jobs (not just resets). Records job name, start/end times, status, rows affected, and any error messages. Used for operational monitoring.
7 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | — | NO | Unique identifier for the execution log entry |
job_name | text | — | YES | Name of the cron job that was executed |
started_at | timestamptz | — | YES | When the job started |
completed_at | timestamptz | — | YES | When the job completed |
status | text | — | YES | Job status: success or error |
rows_affected | integer | — | YES | Number of rows affected by the job |
error_message | text | — | YES | Error details if the job failed |
Indexes
cron_execution_logs_pkey — Primary key on id
8.2 cron_reset_logs
Purpose: Monthly usage reset audit log. Records every execution of the monthly cron job that resets current_usage counters on all API keys. Tracks how many keys were reset, timing, and any errors.
7 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | — | NO | Unique identifier for the reset log entry |
reset_type | text | — | YES | Type of reset performed |
keys_reset | integer | — | YES | Number of API keys that were reset |
started_at | timestamptz | — | YES | When the reset job started |
completed_at | timestamptz | — | YES | When the reset job completed |
status | text | — | YES | Job status: success or error |
error_message | text | — | YES | Error details if the job failed |
Indexes
cron_reset_logs_pkey — Primary key on id
8.3 cron_reset_summary
Purpose: Reset summary statistics. Stores aggregate before/after usage totals for each monthly reset. Provides a quick audit trail to verify that resets completed correctly and no usage was lost.
6 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | — | NO | Unique identifier |
reset_date | date | — | YES | Date the reset was performed |
total_keys | integer | — | YES | Total number of API keys at time of reset |
total_usage_before | bigint | — | YES | Sum of all current_usage values before reset |
total_usage_after | bigint | — | YES | Sum of all current_usage values after reset (should be 0) |
created_at | timestamptz | — | YES | When this summary record was created |
Indexes
cron_reset_summary_pkey — Primary key on id
Group 9 — Website Analytics2 tables
9.1 page_events
Purpose: Website interaction event tracking. Records user interactions beyond page views — button clicks, form submissions, navigation events, language changes, error page navigation, and custom events. The event_data JSONB column stores event-specific metadata. Powers the Page Analytics dashboard click tracking and /admin/page-analytics API.
8 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the event |
page_path | varchar | — | NO | URL path where the event occurred |
event_type | varchar | — | NO | Event category (click, submit, error_nav, lang_change, etc.) |
event_target | varchar | '' | NO | Element that triggered the event (button ID, link text, etc.) |
event_data | jsonb | '{}' | NO | Event-specific metadata (e.g., {destination: "/give.html", error_code: 404}) |
visitor_id | varchar | '' | NO | Anonymous persistent visitor identifier (matches page_views) |
session_id | varchar | '' | NO | Anonymous session identifier (matches page_views) |
created_at | timestamptz | now() | NO | When the event was recorded |
Indexes
page_events_pkey — Primary key on id
9.2 page_views
Purpose: Website page view analytics. Records every page load across the CPMP website. Captures the page path, title, referrer, user agent, screen dimensions, and an anonymous visitor/session ID pair for session reconstruction. Powers the Page Analytics dashboard and /admin/page-analytics API.
11 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the page view |
page_path | varchar | — | NO | URL path of the page viewed (e.g., /subscribe-listener.html) |
page_title | varchar | '' | NO | HTML page title at time of view |
referrer | varchar | '' | NO | HTTP referrer URL (where the visitor came from) |
user_agent | varchar | '' | NO | Browser user agent string |
visitor_id | varchar | '' | NO | Anonymous persistent visitor identifier (localStorage) |
session_id | varchar | '' | NO | Anonymous session identifier (sessionStorage) |
screen_width | integer | 0 | NO | Visitor's screen width in pixels |
screen_height | integer | 0 | NO | Visitor's screen height in pixels |
country | varchar | '' | NO | Visitor's country (from CloudFront geo header) |
created_at | timestamptz | now() | NO | When the page view was recorded |
Indexes
page_views_pkey — Primary key on id
Group 10 — Webhook Push2 tables
10.1 webhook_subscriptions
Purpose: Webhook subscriber (Associate) configurations and delivery state. Each row represents one webhook subscription tied to an API key. Stores the delivery endpoints (UDP IP:port and/or HTTPS URL), selected assets, tier-based limits (interval, max assets), verification status, and Stripe billing references. The delivery engine reads active rows every 30 seconds to start/stop per-subscription push loops. Managed via /webhook/configure, /webhook/verify, /webhook/assets, and /webhook/status endpoints.
20 columns
| Column | Data Type | Default | Nullable | Description |
id PK | uuid | gen_random_uuid() | NO | Unique identifier for the webhook subscription |
api_key_id FK → api_keys.id | uuid | — | NO | API key this subscription belongs to; ON DELETE CASCADE |
tier | text | 'webhook_starter' | NO | Webhook tier: webhook_starter, webhook_standard, webhook_professional, webhook_enterprise, or webhook_partner |
delivery_method | text | 'both' | NO | Delivery mode: udp, https, or both |
udp_host | text | — | YES | Associate's UDP endpoint IP address (e.g., 203.0.113.10) |
udp_port | integer | — | YES | Associate's UDP endpoint port (1024–65535) |
https_url | text | — | YES | Associate's HTTPS endpoint URL for signed POST delivery |
interval_seconds | integer | 60 | NO | Push interval in seconds — tier-dependent (60, 15, 6, or 3) |
max_assets | integer | 9 | NO | Maximum assets allowed for this tier (9, 30, 75, or 150) |
selected_assets | text | — | YES | Comma-separated list of selected asset symbols (e.g., BTC,ETH,SOL) |
status | text | 'pending_verification' | NO | Delivery status: pending_verification, active, or suspended |
verification_token | text | — | YES | 32-byte hex token for endpoint ownership verification; doubles as HMAC signing secret for HTTPS delivery |
verified_at | timestamptz | — | YES | When the endpoint was verified via POST /webhook/verify |
stripe_customer_id | text | — | YES | Stripe customer ID for billing event correlation |
stripe_subscription_id | text | — | YES | Stripe subscription ID for tier change and cancellation handling |
subscription_status | text | 'active' | NO | Billing status: active, past_due, or canceled — updated by Lambda receipt handler on Stripe events |
monthly_push_count | integer | 0 | NO | Number of price pushes delivered this month; reset by cron |
last_delivered_at | timestamptz | — | YES | Timestamp of the most recent successful price push |
created_at | timestamptz | now() | NO | When this subscription was created (at checkout) |
updated_at | timestamptz | now() | NO | Last time this subscription was modified |
Indexes
webhook_subscriptions_pkey — Primary key on id
idx_webhook_subs_api_key_id — Index on api_key_id for fast lookup by API key
idx_webhook_subs_status — Index on status for delivery engine active-subscription queries
Foreign Keys
api_key_id → api_keys.id (ON DELETE CASCADE)
Check Constraints
tier IN ('webhook_starter', 'webhook_standard', 'webhook_professional', 'webhook_enterprise', 'webhook_partner')
delivery_method IN ('udp', 'https', 'both')
status IN ('pending_verification', 'active', 'suspended')
subscription_status IN ('active', 'past_due', 'canceled')
Tier Configuration (from rate_limit_template):
| Tier | Interval | Max Assets | Push Frequency |
| webhook_starter | 60s | 9 | 1 push/min |
| webhook_standard | 15s | 30 | 4 pushes/min |
| webhook_professional | 6s | 75 | 10 pushes/min |
| webhook_enterprise | 3s | 150 | 20 pushes/min |
| webhook_partner | 3s | 150 | 20 pushes/min |
10.2 webhook_delivery_log
Purpose: Webhook delivery attempt log. Every price push — UDP and HTTPS — is logged here with protocol, status, latency, and error details. Used by the GET /webhook/status endpoint to compute 24-hour delivery stats (total, successful, failed, avg latency). High-volume table — one row per delivery attempt per protocol per subscription per interval cycle.
8 columns
| Column | Data Type | Default | Nullable | Description |
id PK | bigserial | auto-increment | NO | Auto-increment primary key (bigserial for high-volume writes) |
webhook_subscription_id FK → webhook_subscriptions.id | uuid | — | NO | Subscription this delivery belongs to; ON DELETE CASCADE |
delivery_id | text | — | NO | Unique delivery identifier (format: {sub_id_prefix}-{unix_ms}-{sequence}) |
protocol | text | — | NO | Delivery protocol: udp or https |
status | text | — | NO | Delivery outcome: delivered or failed |
latency_ms | bigint | — | NO | End-to-end delivery latency in milliseconds |
error_message | text | — | YES | Error details on failure (NULL on success) |
delivered_at | timestamptz | now() | NO | When the delivery attempt was made |
Indexes
webhook_delivery_log_pkey — Primary key on id
idx_wdl_sub_id_delivered — Composite index on (webhook_subscription_id, delivered_at) for 24-hour stats queries
idx_wdl_status — Index on status for failure monitoring
Foreign Keys
webhook_subscription_id → webhook_subscriptions.id (ON DELETE CASCADE)