Skip to content

Full Config Reference

config/config.yaml is the single source of truth, loaded once at startup from a fixed path (<project root>/config/config.yaml). Every string value supports ${ENV_VAR} (required) or ${ENV_VAR:default} (optional) interpolation.

Minimal Configuration

Only llm and database are required:

llm:
  provider: "anthropic"
  anthropic:
    api_key: "${ANTHROPIC_API_KEY}"
    model: "claude-sonnet-4-20250514"

database:
  provider: "trino"
  trino:
    host: "${TRINO_HOST}"
    port: 443
    user: "${TRINO_USER}"
    password: "${TRINO_PASSWORD}"
    catalog: "hive"
    schema: "default"
    http_scheme: "https"

app

app:
  name: "SQLatte"
  host: "0.0.0.0"
  port: 8000

llm

llm:
  provider: "anthropic"   # anthropic | gemini | vertexai
  anthropic:
    api_key: "${ANTHROPIC_API_KEY}"
    model: "claude-sonnet-4-20250514"
    max_tokens: 4096
  gemini:
    api_key: "${GEMINI_API_KEY}"
    model: "gemini-pro"
    max_tokens: 1000
  vertexai:
    project_id: "${VERTEXAI_PROJECT_ID}"
    location: "${VERTEXAI_LOCATION:europe-west1}"
    model: "gemini-2.5-pro"
    credentials_path: "${GOOGLE_APPLICATION_CREDENTIALS}"
    # or inline for containers: credentials_json: "${VERTEXAI_CREDENTIALS_JSON}"
    model_routing:
      intent_detection: "gemini-2.5-flash"
      chat: "gemini-2.5-flash"
      sql: "gemini-2.5-pro"
      insights: "gemini-2.5-flash"
      ops_insights: "gemini-2.5-flash"

model_routing (top-level, works across providers) routes each task type to a different model/provider to balance cost and accuracy — see LLM Providers.

database

database:
  provider: "trino"   # trino | postgresql | mysql | bigquery
  trino:
    host: "${TRINO_HOST}"
    port: 443
    user: "${TRINO_USER}"
    password: "${TRINO_PASSWORD}"
    catalog: "${TRINO_CATALOG:hive}"
    schema: "${TRINO_SCHEMA:default}"
    http_scheme: "https"
    timeout: 300   # optional, seconds — server-side query cancellation
  postgresql:
    host: "${POSTGRES_HOST}"
    port: 5432
    database: "${POSTGRES_DB}"
    user: "${POSTGRES_USER}"
    password: "${POSTGRES_PASSWORD}"
    schema: "${POSTGRES_SCHEMA:public}"
    min_connections: 1
    max_connections: 10
    timeout: 300   # optional, seconds — server-side query cancellation
  mysql:
    host: "${MYSQL_HOST}"
    port: 3306
    database: "${MYSQL_DB}"
    user: "${MYSQL_USER}"
    password: "${MYSQL_PASSWORD}"
    autocommit: true
    timeout: 300   # optional, seconds — server-side query cancellation
  bigquery:
    project_id: "${BQ_PROJECT_ID}"
    dataset: ""
    location: "US"
    credentials_path: "${GOOGLE_APPLICATION_CREDENTIALS}"
    # or: credentials_json: ""
    timeout: 300
    max_results: 10000

See Database Providers for field-by-field notes, including how each provider cancels a timed-out query.

mcp

mcp:
  sse:
    enabled: true   # exposes GET /mcp/sse + POST /mcp/messages/

  # Notify when an AI/MCP-generated query looks sensitive. Fires on either
  # trigger — a risk_score above the threshold, or the query referencing a
  # column matching an enabled MCP Masking field_pattern. Disabled by
  # default; every check is best-effort and never blocks the query.
  security_alerts:
    enabled: false
    risk_threshold: 70       # 0-100, see sql_validator.risk_score()
    apply_to: ["mcp"]        # which widget_type values to alert on
    notify_emails: []        # requires email.enabled: true; always logs regardless

See MCP Network Setup. Local (stdio) MCP mode needs no config here — it authenticates via SQLATTE_TOKEN/env vars passed to sqlatte_mcp_server.py directly.

admin

admin:
  enabled: true
  username: "${ADMIN_USERNAME:admin}"
  password: "${ADMIN_PASSWORD}"

Fails closed if password is empty — see Admin Panel Authentication.

ldap

ldap:
  enabled: false
  server: "${LDAP_SERVER:ldaps://ldap.example.com:636}"
  use_ssl: true
  connect_timeout: 5
  # Direct bind (simplest, no service account):
  user_dn_template: "uid={username},ou=people,dc=example,dc=com"
  # Or search+bind with a service account:
  # bind_dn: "${LDAP_BIND_DN}"
  # bind_password: "${LDAP_BIND_PASSWORD}"
  # base_dn: "${LDAP_BASE_DN}"
  # user_search_filter: "(uid={username})"   # or "(sAMAccountName={username})" for AD
  # Optional: restrict login to one LDAP group
  # required_group_dn: "cn=sqlatte-users,ou=groups,dc=example,dc=com"
  # group_member_attr: "member"   # "member" (OpenLDAP) | "memberOf" (AD) | "memberUid" (POSIX)

Disabled by default. When enabled, used by admin login (tried first, falling back to admin.username/password if the directory is unreachable or the bind fails) and, optionally, the Assistant login gate (plugins.assistant_login, below). Also editable from the Admin Panel's LDAP / SSO tab, with optional persistence to config_db. See Admin Panel Authentication.

plugins.assistant_login

plugins:
  assistant_login:
    enabled: false
    ttl_hours: 8   # session lifetime after a successful LDAP login

Optional LDAP login gate in front of the SQLatte Assistant (the legacy /query widget), which otherwise has no login at all. Requires ldap.enabled: true. On success this is an identity check only — the issued session still uses the server's own database config, not per-user credentials (unlike plugins.auth, below).

config_db

config_db:
  enabled: false
  type: postgresql
  postgresql:
    host: "${CONFIG_DB_HOST:localhost}"
    database: "${CONFIG_DB_NAME:sqlatte_config}"
    user: "${CONFIG_DB_USER:sqlatte}"
    password: "${CONFIG_DB_PASSWORD}"
  # Generate with: python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
  encryption_key: "${CONFIG_DB_ENCRYPTION_KEY}"

When enabled, runtime config changes, API tokens, mask rules, and audit logs persist to PostgreSQL instead of process memory — required for anything beyond single-instance/dev use. encryption_key encrypts stored credentials at rest.

plugins.auth

plugins:
  auth:
    enabled: false
    session_ttl_minutes: 480
    max_workers: 40
    db_provider: "trino"
    db_host: "${TRINO_HOST}"
    db_port: 443
    allowed_catalogs:
      - name: "hadoop"
        allowed_schemas: ["hive"]
    allowed_db_types: ["trino"]

    # Trino only, on by default: reject generated SQL that references a
    # catalog other than the one the session/token is scoped to. No effect
    # without allowed_catalogs configured or on non-Trino providers.
    enforce_catalog_lock: true

    # Trino only, off by default: catalog-less tokens scoped by
    # allowed_catalogs instead of one fixed catalog — enables the MCP
    # discover_tables tool and the /auth/discover* endpoints. See
    # MCP Overview and API Tokens & Admin Auth.
    enable_discovery_tokens: false

    auto_session:
      enabled: false
      ttl_hours: 1
      label: "auto-session"
      # allowed_origins: []

Multi-tenant per-user database connections. See Security Overview and Discovery Tokens.

analytics

analytics:
  enabled: false
  backend: "postgresql"
  postgresql:
    host: "${ANALYTICS_DB_HOST:localhost}"
    port: 5432
    database: "${ANALYTICS_DB_NAME:sqlatte_analytics}"
    user: "${ANALYTICS_DB_USER:sqlatte}"
    password: "${ANALYTICS_DB_PASSWORD}"

See Analytics Setup.

scheduler / email / export

scheduler:
  enabled: false
  timezone: "UTC"
  max_concurrent_jobs: 10
  job_timeout_seconds: 300
  keep_history_days: 30
  max_executions_per_schedule: 100

email:
  enabled: false
  provider: "smtp"
  smtp:
    host: "${SMTP_HOST}"
    port: 587
    user: "${SMTP_USER:}"
    password: "${SMTP_PASSWORD:}"
    from_email: "${SMTP_FROM_EMAIL}"
    from_name: "SQLatte Reports"
  templates:
    success_subject: "✅ {{schedule_name}} - {{date}}"
    failure_subject: "❌ Failed: {{schedule_name}}"
  max_emails_per_day: 1000
  max_recipients_per_email: 10

export:
  formats: [csv, excel, html]
  max_rows: 10000
  max_file_size_mb: 25
  filename_template: "{{schedule_name}}_{{date}}_{{time}}.{{format}}"

See Scheduled Queries and Email Settings.

insights

insights:
  enabled: true
  mode: hybrid   # llm_only | statistical_only | hybrid
  max_insights: 3
  include_statistical: true

ops_agent / alarms / jira

ops_agent:
  enabled: false
  ai_insights: true
  ai_insights_max: 5
  provider: "bigquery_ops"
  config:
    projects:
      - project_id: "${BQ_OPS_PROJECT_ID}"
        region: "${BQ_OPS_REGION:europe-west1}"
        credentials_path: "${BQ_OPS_CREDENTIALS_PATH}"
    allowed_operations:
      - get_expensive_queries
      - forecast_monthly_costs
      - find_public_datasets
      - check_slot_saturation
      # ... see Ops Console page for the full list

alarms:
  enabled: false
  storage_path: "config/alarms.json"
  default_recipients: ["${ALARM_DEFAULT_RECIPIENT}"]

jira:
  enabled: false
  url: "${JIRA_URL}"
  email: "${JIRA_EMAIL}"
  api_token: "${JIRA_API_TOKEN}"
  project_key: "${JIRA_PROJECT_KEY:DATA}"
  issue_type: "Bug"
  priority: "High"

See BigQuery Ops Console for the full allowed_operations list and alarm behavior.

rate_limiting

rate_limiting:
  enabled: false
  strategy: sliding_window   # sliding_window | token_bucket | fixed_window
  key_type: session_id       # session_id | ip | user_id
  requests_per_window: 30
  window_seconds: 60
  protected_paths: ["/query", "/auth/query"]
  exclude_paths: ["/health", "/static", "/admin"]
  # Optional per-path limits, taking priority over the defaults above for
  # matching paths — a path here doesn't need to also be in protected_paths.
  path_overrides:
    "/auth/query":
      requests_per_window: 10
      window_seconds: 60
    "/auth/discover":
      requests_per_window: 20
      window_seconds: 60

query

query:
  default_limit: 100
  max_limit: 1000
  timeout: 300
  require_session: true   # 401 unauthenticated callers on the legacy /query endpoint
  # If false, the assistant generates SQL but does not run it — only affects
  # the interactive assistant (/query, /auth/query); scheduled queries and
  # MCP tool calls always execute regardless of this flag.
  execute_generated_sql: true
  # Ask the user to confirm (instead of just displaying or silently running
  # the generated SQL) before executing it. See POST /query/confirm-execute
  # and POST /auth/query/confirm in the API Reference.
  confirm_before_execute: false

database:
  bigquery:
    price_per_tb_usd: 6.25   # optional — adds a $ estimate to the confirm prompt (BigQuery only)

confirm_before_execute applies to the interactive Assistant and Authenticated Agent widgets only — programmatic access (MCP tools, API tokens) always executes immediately. price_per_tb_usd lives alongside the rest of database.bigquery (see Database Providers), not under query.

cors

cors:
  allow_origins: ["*"]
  allow_credentials: true
  allow_methods: ["*"]
  allow_headers: ["*"]

Restrict allow_origins to specific domains before embedding widgets in production.

ui.sections

ui:
  sections:
    assistant: true
    demo: true
    analytics: false
    schedules: true
    dashboards: true
    bigquery_ops: true   # or "bigquery-ops" — both spellings accepted
    admin: true
    tokens: true

Toggles which tabs appear in the standalone dashboard interface, independent of whether the underlying feature is enabled. home is always shown and isn't a toggleable key. Any key omitted from ui.sections defaults to true.

prompts

Four prompts — intent_detection, barista_personality, sql_generation, insights_generation (plus ops_insights_generation when the Ops Console is on) — are defined here as the defaults, and editable at runtime from the admin panel. See Runtime Prompts.