Getting started

From zero to a governed endpoint

Python 3.11 or newer. Java 11+ only if you connect to H2. Everything else is a pip extra, imported only when a connection of that type is used.

Install

With pip

install · pip
$ pip install queryapigate                     # SQLite only
$ pip install "queryapigate[postgres,mysql]"   # add the drivers you need
$ pip install "queryapigate[all]"              # every driver

all installs every database driver plus flow, encryption and jwt. Add the rest by what you use:

  • Databasesmysql, postgres, clickhouse, duckdb, mongo, h2
  • Agents and sign-inmcp, jwt
  • Running several instancesredis, server
  • Tracingotel
  • Secretsencryption, aws
  • Query flow diagramsflow

The Docker image includes otel and aws. Vault and secrets from files need no extra.

With Docker

install · Docker
$ docker run -p 5000:5000 -v queryapigate-data:/data \
    -e QUERYAPIGATE_API_KEY=change-me \
    ghcr.io/anantharajuc/queryapigate:{{ docVer }}

The image includes every driver except H2, plus Redis, MCP, tracing, AWS and gunicorn, and runs non-root on a read-only root filesystem. Tags: X.Y.Z and latest; X.Y.Z-h2 adds Java for H2 and generic JDBC. Pin a version. For several instances behind Caddy with Docker Compose, see guide 32 and guide 42.

With Helm, on Kubernetes

install · Helm
$ kubectl create namespace queryapigate
$ kubectl -n queryapigate create secret generic queryapigate \
    --from-literal=QUERYAPIGATE_API_KEY=change-me \
    --from-literal=QUERYAPIGATE_SECRET_KEY=<a Fernet key>
$ helm install queryapigate oci://ghcr.io/anantharajuc/charts/queryapigate \
    --version {{ docVer }} -n queryapigate --set existingSecret=queryapigate --wait

One pod on SQLite with its store on a volume, or several on PostgreSQL and Redis; the chart refuses setups that can't work. Chart X.Y.Z runs server X.Y.Z. See guide 54, and guide 55 to drive it from a configuration file.

Verify a release first

From 0.31, the image and the chart are signed keyless with cosign, with SLSA provenance and an SBOM. Check them with cosign verify, or let an admission controller refuse anything unsigned: guide 59.

Quickstart

A small generated database (films, customers, rentals) and four worked example APIs, so the requests below work immediately. queryapigate examples unload removes them again.

quickstart
$ QUERYAPIGATE_API_KEY=demo-key queryapigate examples load   # prints one scoped key per scenario
$ QUERYAPIGATE_API_KEY=demo-key queryapigate serve            # http://127.0.0.1:5000 · Console at /console

$ curl 'http://127.0.0.1:5000/q/example_film_search?text=Harbor&page_size=2' -H 'X-API-Key: demo-key'
[{"film_id":48,"title":"Broken Harbor","category":"Action","rating":"PG-13"},
 {"film_id":44,"title":"Electric Harbor","category":"Documentary","rating":"G"}]

With Docker: docker compose up. The image is ghcr.io/anantharajuc/queryapigate.

Your first endpoint, against your own database

  1. 01
    Make a data foldermkdir my-api && cd my-api && queryapigate init writes queryapigate.db: connections, saved queries, keys, roles and the audit log, in one SQLite file.
  2. 02
    Connect a databaseIn the Console, or PATCH /api/v1/connections/<name>. Keep passwords out of the store with "${ENV_VAR}" references, or encrypt them at rest with QUERYAPIGATE_SECRET_KEY.
  3. 03
    Save a query with bound parametersPrefer :name parameters over {name} placeholders. Give each a type, range or length. Values are bound, never spliced.
  4. 04
    Call itGET /q/<name>?param=value&format=csv. Add ?stream=true for a constant-memory export.

Give a partner one query only

QUERYAPIGATE_API_KEY is the shared, full-access admin key. Anyone or anything that only needs to run queries gets a scoped key instead, limited to specific connections and, separately, to read-only access.

api-keys
$ curl -X POST http://127.0.0.1:5000/api/v1/api-keys -H 'X-API-Key: <admin key>' \
    -H 'Content-Type: application/json' \
    -d '{"name": "partner-acme", "queries": ["orders_by_customer"],
         "rate_limit": "60/minute", "expires_at": "2027-01-01", "allowed_ips": ["203.0.113.0/24"]}'

Then confirm it on the access map, or with the verify who can reach what guide.

Production

Choosing between one instance, several, or Kubernetes? See the reference architectures, with a production checklist and measured performance.

queryapigate serve is Flask's development server. In production use gunicorn with one worker and several threads, behind a TLS-terminating reverse proxy. For more capacity, run more instances on a shared PostgreSQL store and Redis.

production
$ pip install "queryapigate[server]"
$ QUERYAPIGATE_API_KEY=change-me gunicorn --bind 127.0.0.1:5000 --workers 1 --worker-class gthread \
    --threads 8 --timeout 120 "queryapigate.app:create_app()"

# several instances: shared store and shared limits/cache
$ export QUERYAPIGATE_DATABASE_URL=postgresql://queryapigate:secret@db.internal:5432/queryapigate
$ export QUERYAPIGATE_REDIS_URL=redis://redis:6379/0
$ queryapigate migrate-to-postgres   # once, copies queryapigate.db across

Behind a proxy, set QUERYAPIGATE_TRUST_PROXY to the number of proxies so rate limits apply per real client. Reverse proxy, secrets, backups and monitoring are covered in Deployment; On Kubernetes, install the Helm chart (helm install queryapigate oci://ghcr.io/anantharajuc/charts/queryapigate, guide 54); plain manifests live under deploy/kubernetes.

Configuration

Everything is an environment variable. The ones you will meet first:

QUERYAPIGATE_HOMEThe data folder. Default: current directory.
QUERYAPIGATE_API_KEYThe admin key. Unset means an open server.
QUERYAPIGATE_ALLOW_WRITESLeave unset unless you need writes.
QUERYAPIGATE_QUERY_TIMEOUTSeconds per query. Default 30.
QUERYAPIGATE_RATE_LIMITServer-wide limit per client address.
QUERYAPIGATE_SECRET_KEYEncrypts connection passwords at rest.
QUERYAPIGATE_DATABASE_URLPostgreSQL store for several instances.
QUERYAPIGATE_REDIS_URLShared cache, limits and events.

The full table, including pool size, CORS origins, host and port, is in the README.

Use these docs with AI

Your coding agent can read this documentation for itself. The QueryAPIGate documentation MCP server searches the docs, the 65 how-to guides and the blog, and returns any page, or one section of it, as markdown. It's read-only and needs no sign-in.

It answers questions about QueryAPIGate. To give agents your own data, QueryAPIGate itself is the MCP server: MCP server for AI agents →

claude code
$ claude mcp add --transport http queryapigate-docs https://queryapigate-docs.queryapigate-docs-mcp.workers.dev/mcp --scope user
codex
$ codex mcp add queryapigate-docs --url https://queryapigate-docs.queryapigate-docs-mcp.workers.dev/mcp
cursor, claude desktop, windsurf · mcp config
{
  "mcpServers": {
    "queryapigate-docs": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://queryapigate-docs.queryapigate-docs-mcp.workers.dev/mcp"]
    }
  }
}

Three tools: search_docs, get_page and list_pages. Rather point a tool at files? Every page is plain markdown too: llms.txt lists them all, and each guide's Copy page menu opens it in Claude or ChatGPT.