-- v3.0 Public API: API keys and rate limiting CREATE TABLE IF NOT EXISTS api_keys ( id SERIAL PRIMARY KEY, tenant_id INTEGER NOT NULL, name VARCHAR(200) NOT NULL, -- e.g. "Integration Acme Corp" key_hash VARCHAR(64) NOT NULL, -- SHA-256 hash of the API key key_prefix VARCHAR(8) NOT NULL, -- first 8 chars for display scopes JSONB DEFAULT '["read"]'::jsonb, -- ["read", "write", "admin"] rate_limit_rpm INTEGER DEFAULT 60, -- requests per minute rate_limit_rpd INTEGER DEFAULT 10000, -- requests per day is_active BOOLEAN DEFAULT TRUE, last_used_at TIMESTAMPTZ, expires_at TIMESTAMPTZ, created_by INTEGER REFERENCES employees(id), created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE UNIQUE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash); CREATE INDEX IF NOT EXISTS idx_api_keys_tenant ON api_keys(tenant_id, is_active); -- API request log for analytics and abuse detection CREATE TABLE IF NOT EXISTS api_request_logs ( id BIGSERIAL PRIMARY KEY, api_key_id INTEGER REFERENCES api_keys(id), tenant_id INTEGER, method VARCHAR(10) NOT NULL, path TEXT NOT NULL, status_code INTEGER, response_time_ms INTEGER, ip_address INET, user_agent TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_api_logs_key ON api_request_logs(api_key_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_api_logs_tenant ON api_request_logs(tenant_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_api_logs_path ON api_request_logs(path, created_at DESC); -- Rate limit counters (in-memory/redis preferred, but table as fallback) CREATE TABLE IF NOT EXISTS api_rate_limit_counters ( id SERIAL PRIMARY KEY, api_key_id INTEGER NOT NULL REFERENCES api_keys(id) ON DELETE CASCADE, window_start TIMESTAMPTZ NOT NULL, window_type VARCHAR(10) NOT NULL, -- 'minute', 'day' request_count INTEGER DEFAULT 0, UNIQUE(api_key_id, window_start, window_type) ); CREATE INDEX IF NOT EXISTS idx_rate_limit_window ON api_rate_limit_counters(api_key_id, window_type, window_start);