create table schema_migrations ( version integer primary key, name text not null, applied_at text not null ); pragma foreign_keys = on; -- ============================================ -- Admin Users -- ============================================ create table admin_users ( id text primary key, password_hash text not null, created_at text not null, updated_at text not null ); -- ============================================ -- Admin Sessions -- ============================================ create table admin_sessions ( id text primary key, user_id text not null references admin_users(id) on delete cascade, expires_at text not null, created_at text not null ); create index idx_admin_sessions_expires on admin_sessions(expires_at); -- ============================================ -- Client API Keys -- ============================================ create table client_api_keys ( id text primary key, name text not null, prefix text not null, key text not null unique, label text, enabled integer not null default 1 check (enabled in (0, 1)), created_at text not null, last_used_at text ); create index idx_client_api_keys_key_enabled on client_api_keys(key) where enabled = 1; create index idx_client_api_keys_created_id on client_api_keys(created_at desc, id desc); -- ============================================ -- Runtime Settings -- ============================================ create table runtime_settings ( id integer primary key check (id = 1), model_aliases_json text not null default '{}', refresh_margin_seconds integer not null check (refresh_margin_seconds > 0), refresh_concurrency integer not null check (refresh_concurrency > 0), max_concurrent_per_account integer not null check (max_concurrent_per_account > 0), request_interval_ms integer not null check (request_interval_ms >= 0), rotation_strategy text not null check (rotation_strategy in ('smart', 'quota_reset_priority', 'round_robin', 'sticky')), admin_api_key text, updated_at text not null ); -- ============================================ -- Accounts -- ============================================ create table accounts ( id text primary key, email text, chatgpt_account_id text, chatgpt_user_id text, label text, plan_type text, access_token text not null, refresh_token text, access_token_expires_at text, next_refresh_at text, status text not null check (status in ('active', 'expired', 'quota_exhausted', 'refreshing', 'disabled', 'banned')), quota_json text, quota_fetched_at text, quota_limit_reached integer not null default 0 check (quota_limit_reached in (0, 1)), quota_verify_required integer not null default 0 check (quota_verify_required in (0, 1)), quota_cooldown_until text, cloudflare_cooldown_until text, added_at text not null, updated_at text not null ); create index idx_accounts_status on accounts(status); create index idx_accounts_added_id on accounts(added_at desc, id desc); create unique index ux_accounts_chatgpt_identity on accounts(chatgpt_account_id, coalesce(chatgpt_user_id, '')) where chatgpt_account_id is not null; -- ============================================ -- Account Refresh Leases -- ============================================ create table account_refresh_leases ( account_id text primary key references accounts(id) on delete cascade, owner text not null, expires_at text not null, updated_at text not null ); create index idx_account_refresh_leases_expires on account_refresh_leases(expires_at); -- ============================================ -- Account Usage Statistics -- ============================================ create table account_usage ( account_id text primary key references accounts(id) on delete cascade, request_count integer not null default 0 check (request_count >= 0), empty_response_count integer not null default 0 check (empty_response_count >= 0), input_tokens integer not null default 0 check (input_tokens >= 0), output_tokens integer not null default 0 check (output_tokens >= 0), cached_tokens integer not null default 0 check (cached_tokens >= 0), reasoning_tokens integer not null default 0 check (reasoning_tokens >= 0), total_tokens integer not null default 0 check (total_tokens >= 0), image_input_tokens integer not null default 0 check (image_input_tokens >= 0), image_output_tokens integer not null default 0 check (image_output_tokens >= 0), image_request_count integer not null default 0 check (image_request_count >= 0), image_request_failed_count integer not null default 0 check (image_request_failed_count >= 0), window_request_count integer not null default 0 check (window_request_count >= 0), window_input_tokens integer not null default 0 check (window_input_tokens >= 0), window_output_tokens integer not null default 0 check (window_output_tokens >= 0), window_cached_tokens integer not null default 0 check (window_cached_tokens >= 0), window_image_input_tokens integer not null default 0 check (window_image_input_tokens >= 0), window_image_output_tokens integer not null default 0 check (window_image_output_tokens >= 0), window_image_request_count integer not null default 0 check (window_image_request_count >= 0), window_image_request_failed_count integer not null default 0 check (window_image_request_failed_count >= 0), window_started_at text, window_reset_at text, limit_window_seconds integer check (limit_window_seconds is null or limit_window_seconds > 0), last_used_at text ); create index idx_account_usage_last_used_account on account_usage(last_used_at desc, account_id desc); -- ============================================ -- Account Model Usage Statistics -- ============================================ create table account_model_usage ( account_id text not null references accounts(id) on delete cascade, model text not null, request_count integer not null default 0 check (request_count >= 0), error_count integer not null default 0 check (error_count >= 0), input_tokens integer not null default 0 check (input_tokens >= 0), output_tokens integer not null default 0 check (output_tokens >= 0), cached_tokens integer not null default 0 check (cached_tokens >= 0), last_used_at text, primary key (account_id, model) ); create index idx_account_model_usage_last_used on account_model_usage(last_used_at desc, account_id, model); -- ============================================ -- Usage Time Buckets -- ============================================ create table usage_time_buckets ( bucket_start text not null, account_id text not null default '', model text not null default '', service_tier text not null default '', request_count integer not null default 0 check (request_count >= 0), error_count integer not null default 0 check (error_count >= 0), input_tokens integer not null default 0 check (input_tokens >= 0), output_tokens integer not null default 0 check (output_tokens >= 0), cached_tokens integer not null default 0 check (cached_tokens >= 0), first_token_latency_sum integer not null default 0 check (first_token_latency_sum >= 0), first_token_latency_count integer not null default 0 check (first_token_latency_count >= 0), latency_sum integer not null default 0 check (latency_sum >= 0), latency_count integer not null default 0 check (latency_count >= 0), max_latency_ms integer not null default 0 check (max_latency_ms >= 0), min_latency_ms integer not null default 0 check (min_latency_ms >= 0), updated_at text not null, primary key (bucket_start, account_id, model, service_tier) ); create index idx_usage_time_buckets_bucket on usage_time_buckets(bucket_start); create index idx_usage_time_buckets_model_bucket on usage_time_buckets(model, bucket_start); -- ============================================ -- Account Cookies -- ============================================ create table account_cookies ( id text primary key, account_id text not null references accounts(id) on delete cascade, domain text not null, name text not null, value text not null, path text not null default '/', expires_at text, updated_at text not null, unique(account_id, domain, name, path) ); create index idx_account_cookies_account on account_cookies(account_id); create index idx_account_cookies_account_domain on account_cookies(account_id, domain); create index idx_account_cookies_expires on account_cookies(expires_at) where expires_at is not null; -- ============================================ -- Device Fingerprints -- ============================================ create table fingerprints ( id text primary key, originator text not null, app_version text not null, build_number text not null, platform text not null, arch text not null, chromium_version text not null, user_agent_template text not null, default_headers_json text not null, header_order_json text not null, source text not null, created_at text not null, updated_at text not null ); create table fingerprint_update_history ( id text primary key, current_fingerprint_id text not null references fingerprints(id) on delete cascade, app_version text not null, build_number text not null, chromium_version text, source text not null, manifest_json text, created_at text not null ); create index idx_fingerprint_update_history_created_id on fingerprint_update_history(created_at desc, id desc); -- ============================================ -- Usage Records -- ============================================ create table usage_records ( id text primary key, request_id text, kind text not null, level text not null check (level in ('debug', 'info', 'warn', 'error')), account_id text, route text, model text, status_code integer check (status_code is null or (status_code >= 100 and status_code <= 599)), transport text, attempt_index integer check (attempt_index is null or attempt_index >= 0), upstream_status_code integer check (upstream_status_code is null or (upstream_status_code >= 100 and upstream_status_code <= 599)), failure_class text, response_id text, upstream_request_id text, latency_ms integer check (latency_ms is null or latency_ms >= 0), message text not null, metadata_json text not null, created_at text not null ); create index idx_usage_records_created_id on usage_records(created_at desc, id desc); create index idx_usage_records_kind_created on usage_records(kind, created_at desc); create index idx_usage_records_request_id on usage_records(request_id); create index idx_usage_records_account on usage_records(account_id, created_at desc) where account_id is not null; create index idx_usage_records_transport on usage_records(transport, created_at desc) where transport is not null; create index idx_usage_records_failure_class on usage_records(failure_class, created_at desc) where failure_class is not null; create index idx_usage_records_response_id on usage_records(response_id) where response_id is not null; create index idx_usage_records_upstream_request_id on usage_records(upstream_request_id) where upstream_request_id is not null; create index idx_usage_records_level_created on usage_records(level, created_at desc); create index idx_usage_records_route_created on usage_records(route, created_at desc) where route is not null; create index idx_usage_records_model_created on usage_records(model, created_at desc) where model is not null; create index idx_usage_records_status_created on usage_records(status_code, created_at desc) where status_code is not null; create index idx_usage_records_upstream_status_created on usage_records(upstream_status_code, created_at desc) where upstream_status_code is not null; -- ============================================ -- Model Plan Snapshots -- ============================================ create table model_plan_snapshots ( plan_type text primary key, models_json text not null, fetched_at text not null ); -- ============================================ -- Session Affinities -- ============================================ create table session_affinities ( response_id text primary key, account_id text not null references accounts(id) on delete cascade, conversation_id text not null, turn_state text, instructions_hash text, input_tokens integer check (input_tokens is null or input_tokens >= 0), function_call_ids_json text not null default '[]', variant_hash text, expires_at text not null, created_at text not null ); create index idx_session_affinities_conversation on session_affinities(conversation_id, created_at desc); create index idx_session_affinities_expires on session_affinities(expires_at); create index idx_session_affinities_active_order on session_affinities(expires_at, created_at, response_id); create table runtime_settings_new ( id integer primary key check (id = 1), model_aliases_json text not null default '{}', refresh_margin_seconds integer not null check (refresh_margin_seconds > 0), refresh_concurrency integer not null check (refresh_concurrency > 0), max_concurrent_per_account integer not null check (max_concurrent_per_account > 0), request_interval_ms integer not null check (request_interval_ms >= 0), rotation_strategy text not null check (rotation_strategy in ('smart', 'quota_reset_priority', 'round_robin', 'sticky')), admin_api_key text, updated_at text not null ); insert into runtime_settings_new ( id, model_aliases_json, refresh_margin_seconds, refresh_concurrency, max_concurrent_per_account, request_interval_ms, rotation_strategy, admin_api_key, updated_at ) select id, model_aliases_json, refresh_margin_seconds, refresh_concurrency, max_concurrent_per_account, request_interval_ms, case rotation_strategy when 'least_used' then 'quota_reset_priority' else rotation_strategy end, admin_api_key, updated_at from runtime_settings; drop table runtime_settings; alter table runtime_settings_new rename to runtime_settings; create table ops_error_logs ( id text primary key, request_id text, kind text not null, account_id text, route text, model text, status_code integer check (status_code is null or (status_code >= 100 and status_code <= 599)), client_status_code integer check (client_status_code is null or (client_status_code >= 100 and client_status_code <= 599)), upstream_status_code integer check (upstream_status_code is null or (upstream_status_code >= 100 and upstream_status_code <= 599)), transport text, attempt_index integer check (attempt_index is null or attempt_index >= 0), failure_class text, response_id text, upstream_request_id text, latency_ms integer check (latency_ms is null or latency_ms >= 0), message text not null, metadata_json text not null, created_at text not null ); create index idx_ops_error_logs_created_id on ops_error_logs(created_at desc, id desc); create index idx_ops_error_logs_request_id on ops_error_logs(request_id) where request_id is not null; create index idx_ops_error_logs_account on ops_error_logs(account_id, created_at desc) where account_id is not null; create index idx_ops_error_logs_route_created on ops_error_logs(route, created_at desc) where route is not null; create index idx_ops_error_logs_model_created on ops_error_logs(model, created_at desc) where model is not null; create index idx_ops_error_logs_status_created on ops_error_logs(status_code, created_at desc) where status_code is not null; create index idx_ops_error_logs_transport_created on ops_error_logs(transport, created_at desc) where transport is not null; create index idx_ops_error_logs_failure_class on ops_error_logs(failure_class, created_at desc) where failure_class is not null; create index idx_ops_error_logs_response_id on ops_error_logs(response_id) where response_id is not null; create index idx_ops_error_logs_upstream_request_id on ops_error_logs(upstream_request_id) where upstream_request_id is not null; insert into schema_migrations (version, name, applied_at) values (1, 'initial', '2026-01-01T00:00:00Z'), (2, 'rename_rotation_strategies', '2026-01-01T00:00:00Z'), (3, 'ops_error_logs', '2026-01-01T00:00:00Z');