View Categories

Instant REST API for popular SQL databases — how Query Streams turns a saved query into a partner-ready endpoint

19 min read

API PLATFORM SHARE LIVE DATA

Instant REST API for popular SQL databases — without sharing credentials.

Turn any saved SQL query into a partner-ready REST endpoint with per-recipient API keys, monthly byte quotas, and a full audit trail. Static or streaming, JSON or NDJSON or CSV, LZ4 compression on the wire — all of it with no infrastructure to host and no SQL exposed to the recipient.

No infrastructure to host Per-recipient API keys Static + streaming modes LZ4 + gzip wire compression Full audit trail

Query Streams is a secure, real-time data integration platform that turns any saved SQL query into a partner-ready REST API endpoint — with per-recipient keys, monthly byte quotas, and a full audit trail. The API Platform lets you share live database data with clients, vendors, and internal teams without ever handing out database credentials, SQL code, or your network topology. Learn more at QueryStreams.com and sign up for free to mint your first shared API in five minutes.

Share live database data as a REST API — without sharing credentials

Most teams ship CSV exports, hand out read-only database accounts, or stand up a custom Express service when a partner needs live data. Each approach has the same problem: the partner ends up holding something they shouldn’t have — an inbox full of stale CSVs, a SQL credential that survives long after the engagement ends, or root access to a service no one actively maintains. The Query Streams API Platform lets you share the capability to run a saved query, not the credentials underneath it. Every recipient gets their own API key, every call is rate-limited and audited, and access can be revoked from the Portal in a single click without rotating a single database password.

Per-recipient API keys

Every recipient gets their own qsapi_* key. Revoke one without touching the others. Rotate without redeploying anything.

Credentials never leave your network

The Network Agent dials out from your environment. The recipient sees a REST endpoint — never your database hostname, password, or SQL.

Read-only by design

The Agent’s read-only enforcer rejects any non-SELECT statement before it reaches the database. There is no “DELETE by accident” path.

Static and streaming response modes

Pick a single buffered body (8 MB / 100k rows default) or a chunked transfer that pushes rows as the Agent produces them (1 GB / 10M rows default).

LZ4-frame + gzip wire compression

Optional LZ4-frame payload mode for SDK consumers (decompress in one line), plus standard Accept-Encoding: gzip on the uncompressed paths.

Postman-style sandbox built in

Test every endpoint inside the Portal before you share it. Tweak query parameters, inspect the JSON, copy the curl — no separate Postman workspace.

CORS + IP allowlists

Pin a recipient’s key to specific IPs or CIDRs, restrict browser origins per endpoint, and stack the standard two-tier rate limits underneath.

OpenAPI 3.1 spec per endpoint

Every endpoint exposes a machine-readable openapi.json. Feed it to Postman, Swagger UI, or an SDK generator like openapi-typescript.

Popular SQL databases supported

The API Platform works with the SQL databases most teams already run. Once your Network Agent is connected, every saved query against a supported database can be promoted to a shared REST endpoint — no per-database setup, no per-database SDK.

Microsoft SQL Server
Microsoft SQL Server
PostgreSQL
PostgreSQL
MySQL
MySQL
MariaDB
MariaDB
SQLite
SQLite
Microsoft Access
Microsoft Access
Oracle
Oracle
BigQuery
BigQuery
DuckDB
DuckDB
Snowflake & more

Cloud-hosted databases work the same way

The Agent connects equally well to on-premise databases and managed services like AWS RDS, Azure SQL Database, Google Cloud SQL, and Snowflake. For best latency, deploy the Agent in the same region as the database; one Query Streams account can run multiple Agents across regions and clouds.

How it works in three steps

Once your Network Agent and a supported database connector are set up, promoting a saved query to a shared API takes about five minutes:

1

Save a SQL query

Build the query in the Portal’s Query Builder against any connected database. Add filters, parameters, and a name. Anything you can SELECT is API-able.

2

Promote it to an API

Open the Install tab. Choose an endpoint type (permanent, expiring, or N-shot call budget), an inner format (JSON, NDJSON, or CSV), a response mode (static or streaming), and whether LZ4 wire compression is allowed. Save.

3

Invite recipients by email

Enter a partner’s email; they receive a magic-link claim invitation. Recipients without a Query Streams account get a Free-tier org auto-created on claim — their key is issued the second they accept.

Already use Query Streams for Excel or Google Sheets?

Then your Network Agent is already in place and your database is already connected. Promoting an existing saved query to a REST endpoint takes about a minute — you skip steps 1 and most of step 2. The same saved query can power an Excel refresh, a Sheets sidebar, and a partner-facing REST endpoint at the same time.

Pick a response mode: static or streaming

Every endpoint chooses one of two response modes when you create it. The mode controls how the bytes leave our servers; the wire format (JSON, NDJSON, CSV, or LZ4-frame) is chosen independently per call by the recipient.

Static responseMode = static

The default. We collect every row into a single buffered response, set Content-Length, and emit one body. Errors are normal HTTP status codes (200, 400, 413 API_OUTPUT_TOO_LARGE, 503).

  • Default caps: 8 MB / 100,000 rows per call
  • Platform max: 50 MB / 1,000,000 rows per call
  • Best for: dashboards, on-demand lookups, sub-10k-row queries, curl, AI tools that want one JSON blob
Streaming responseMode = stream

We return 200 OK + Transfer-Encoding: chunked as soon as the first row arrives, then push rows down the wire as the Agent produces them. Memory at our edge stays bounded regardless of result size.

  • Default caps: 1 GB / 10,000,000 rows per call
  • Platform max: 50 GB / 100,000,000 rows per call
  • Best for: n8n / Zapier flows, analytics pipelines, bulk exports, anything that benefits from time-to-first-byte

Mid-stream errors on a streaming endpoint can’t be expressed as an HTTP status code (the status was already sent with the first chunk). We surface them as a sentinel last record: {"_error":"...","rows_returned":N} for NDJSON, or # error: ..., rows_returned: N for streaming CSV. Consumer libraries should check the last record before treating the stream as complete.

Pick a wire format: JSON, NDJSON, CSV, or LZ4-frame

Inside the chosen response mode, recipients pick the wire format per call using standard HTTP headers (Accept for the inner format and Accept-Encoding for compression). One endpoint can serve all four formats; the owner can also lock the endpoint to a single format from the Install tab.

JSON application/json

A single JSON array. Default for static mode. The universal format for web clients, AI tools, and curl users.

# Python
data = requests.get(url, headers=h).json()
NDJSON application/x-ndjson

Newline-delimited JSON, one row per line. Streaming-only. Parse-as-you-go for pipelines and ETL.

# Python
for line in resp.iter_lines():
    row = json.loads(line)
CSV text/csv

RFC 4180 CSV. Works in static (one body) or streaming (header on first chunk). Open it in Excel, pandas, R, or anything that reads CSV.

# pandas
df = pd.read_csv(url, storage_options=h)
LZ4-frame application/x-lz4-frame

The standards-compliant LZ4 frame format (magic 04 22 4D 18). One frame for static; concatenated frames over chunked transfer for streaming. Bills on compressed bytes.

# Python
data = lz4.frame.decompress(resp.content)

For consumers who want maximum interop, plain JSON (or CSV) is enough — everything else is an opt-in. Wire compression (gzip) layers automatically on top of the uncompressed paths via the standard Accept-Encoding header; we never double-compress, so LZ4-frame replaces gzip rather than stacking on top of it.

The four wire combinations at a glance

Response mode and payload compression compose into four leaf paths. The endpoint owner picks the defaults in the Install tab; the recipient can override per call (within what the owner allows). Wire compression (gzip) negotiated via Accept-Encoding layers on top of the two uncompressed rows automatically.

STATIC + NONE

A single buffered application/json (or text/csv) body with Content-Length. The default path. Wire compression (gzip) negotiated automatically.

caps
8 MB / 100k rows
billing
uncompressed bytes
best for
dashboards, curl, AI tools
STATIC + LZ4

A single application/x-lz4-frame body containing one standard LZ4 frame (multi-block). Decompress in one line of consumer code.

caps
8 MB compressed (~50 MB raw)
billing
compressed bytes
best for
SDK consumers, cost-optimised
STREAM + NONE

application/x-ndjson (or streaming text/csv) over Transfer-Encoding: chunked. One row per chunk. Wire compression (gzip) negotiated automatically.

caps
1 GB / 10M rows
billing
uncompressed bytes
best for
n8n, Zapier, analytics ETL
STREAM + LZ4

application/x-lz4-frame as a sequence of concatenated standard LZ4 frames over chunked transfer. No QS-invented framing protocol.

caps
1 GB compressed (~5 GB raw)
billing
compressed bytes
best for
bulk exports, power-user SDKs
Mode + compressionWire MIME typeBody shapeDefault capsBills onUse case
static + none application/json or text/csv One buffered body, Content-Length 8 MB / 100k rows uncompressed bytes Dashboards, curl, default
static + lz4 application/x-lz4-frame Single LZ4 frame (multi-block) 8 MB compressed compressed bytes SDK consumers, cost-optimised single response
stream + none application/x-ndjson or text/csv Chunked transfer, NDJSON per row 1 GB / 10M rows uncompressed bytes n8n, Zapier, analytics pipelines
stream + lz4 application/x-lz4-frame Chunked transfer, concatenated LZ4 frames 1 GB compressed (~5 GB raw) compressed bytes Bulk export, power-user SDK, cheapest-and-fastest combo

Pass filters at call time

Any saved-query parameter the owner exposes can be set by the recipient on each call. GET requests use the querystring; POST requests use a JSON body. The Agent binds parameters as proper prepared-statement values — never as string concatenation — so a recipient cannot break out of a parameter to inject SQL.

GET with querystring parameters
# Recipient call curl -H “Authorization: Bearer qsapi_K7…ZmQ” \ “https://api.querystreams.com/v1/endpoints/customer-stats?customer=Acme&start=2026-01-01”
POST with JSON body
# Recipient call curl -X POST -H “Authorization: Bearer qsapi_K7…ZmQ” \ -H “Content-Type: application/json” \ -d ‘{“customer”: “Acme”, “start”: “2026-01-01”}’ \ “https://api.querystreams.com/v1/endpoints/customer-stats”

The endpoint owner controls which parameters are exposed (in the Install tab’s Parameters sub-tab), which are locked to a fixed value, and what the allowed set of values is. Recipients can only set parameters that the owner exposed; everything else is fixed. Multi-value parameters use repeated keys (?status=paid&status=shipped) on GET and a standard JSON array on POST.

Security posture: CORS, IP allowlists, and rate limits

Beyond per-recipient keys and read-only enforcement, every endpoint gives you four additional locks you can tighten before you share it.

Per-key IP allowlist

Pin a recipient’s key to a single IP, a CIDR range, or a list. Supports both IPv4 and IPv6. A call from outside the allowlist returns 403 IP_NOT_ALLOWED before the SQL is touched.

Per-endpoint CORS allowlist

Tell us which browser origins are allowed to call the endpoint from JavaScript. Set ["https://app.acme.com"] for a single partner, or leave CORS off entirely (the default) for server-to-server-only endpoints.

Two-tier rate limits

A token bucket on the recipient’s key (default 60 requests/min) plus a second bucket on the endpoint itself (default 120/min). A noisy recipient cannot blow up the budget for the well-behaved ones.

Monthly byte quotas

Optional per-endpoint cap on billed bytes per calendar month. When the cap is hit, calls return 429 ENDPOINT_QUOTA_EXHAUSTED until the period resets. A safe blast-radius limit for new partners.

Every endpoint ships with an OpenAPI 3.1 spec

Each endpoint exposes a machine-readable spec at GET /v1/endpoints/{id}/openapi.json. It describes the URL, the method, every parameter (with type, allowed values, default), every response shape per format (JSON, NDJSON, CSV, LZ4-frame), and every error code. Paste it into Postman or Insomnia for a generated request collection, feed it to Swagger UI for interactive docs, or run it through an SDK generator to get a typed client in your language of choice.

Generate a typed TypeScript client in one command
# 1. Fetch the OpenAPI spec for the endpoint curl -H “Authorization: Bearer $QS_KEY” \ https://api.querystreams.com/v1/endpoints/customer-stats/openapi.json \ -o customer-stats.openapi.json # 2. Generate a typed TypeScript client npx openapi-typescript customer-stats.openapi.json \ -o customer-stats.types.ts

Consume it from Power BI, Tableau, and any JSON tool

Because every endpoint returns standard JSON — with CSV and streaming NDJSON alongside it — any tool that can read a REST feed consumes your data directly, with nothing to install on the recipient’s side. Power Query is the easiest bridge into the Microsoft BI stack: in Power BI choose Get Data → From Web, paste the endpoint URL, add the Authorization header, and Power Query parses the JSON into a refreshable table that feeds your data model. (For live data inside a spreadsheet, the native Query Streams add-ons for Excel and Google Sheets are the simpler path — Power Query is there when you want the data in the BI model itself.)

Microsoft Power Query logo Power Query Get Data → From Web, paste the URL and bearer token, expand the JSON to a refreshable table
Microsoft Power BI logo Power BI Same Power Query engine — load the endpoint into your model and schedule refresh
Tableau logo Tableau Point a Web Data Connector or JSON source at the endpoint for live dashboards
Postman logo Postman Import the OpenAPI 3.1 spec, then send, inspect, and share requests in one click

It also feeds n8n, Qlik, curl, Python (requests or pandas.read_json), Insomnia, Hoppscotch — or any script or workflow that can send an HTTP request and read JSON.

Test endpoints in your browser — Postman-style, without leaving the Portal

Before you hand a key to a partner, run the endpoint yourself. The Install tab has a built-in request builder that mirrors what your recipient will see: pick the HTTP method, set query parameters, choose a sample, fire the request, inspect the response. The sandbox uses a short-lived ephemeral key under the hood, so the call goes through the exact same path a recipient’s request would — same auth, same agent dispatch, same compression negotiation, same byte accounting.

in-portal sandbox — example response (LZ4 framing decoded)
// HTTP/1.1 200 OK // Content-Type: application/json; charset=utf-8 // X-QS-Bytes-Wire: 4188 (after LZ4 framing) // X-QS-Bytes-Raw: 12942 (decoded JSON size) // X-QS-Duration-Ms: 312 [ { “customer”: “Acme Co”, “orders”: 142, “last_order”: “2026-05-12” }, { “customer”: “Globex”, “orders”: 98, “last_order”: “2026-05-19” }, { “customer”: “Initech”, “orders”: 76, “last_order”: “2026-05-18” } ]

Compression-aware billing

The byte counts in your activity ledger are measured at the egress of our API server and follow a simple, predictable rule: if the recipient opted into LZ4-frame payload compression (Accept-Encoding: lz4), the call bills on the compressed bytes that actually went down the wire; if not, the call bills on the uncompressed bytes. Standard HTTP wire compression (gzip) is pure transport optimisation and does not affect billing — that compression happens after we measured the bytes the consumer is asked to “see.”

Note on bytes-on-wire

If a call opts into LZ4-frame compression, the same JSON payload typically bills at roughly 25-40% of its uncompressed size for transactional rows, and 10-20% for analytics workloads with repeated columns — because that is the amount of data your partner’s network actually moved. The in-browser sandbox surfaces both numbers (X-QS-Bytes-Wire and X-QS-Bytes-Raw) so you can see the savings before you share an endpoint. We chose to bill on wire bytes (when LZ4 is opted into) because that is the number that matches both your data-egress costs and the recipient’s bandwidth consumption. Standard HTTP wire compression (gzip) is pure transport optimisation and does not affect billing. Calls that don’t request LZ4 continue to bill on uncompressed bytes, matching how Excel, Sheets, and the MCP Server are billed today.

Decompression on the consumer side is one line per language. Below is the canonical integration code for each environment we ship samples for:

Consumer-side LZ4-frame decompression (one-liners)
# Python (pip install lz4) import lz4.frame data = lz4.frame.decompress(resp.content) // Node.js / TypeScript (npm install lz4js) import LZ4 from ‘lz4js’; const data = LZ4.decompressFrame(buffer); // Go (github.com/pierrec/lz4/v4) data, _ := io.ReadAll(lz4.NewReader(body)) # curl + lz4 CLI curl -H “Accept-Encoding: lz4” URL | lz4 -d –

Built for partner data sharing — not internet-scale APIs

The Query Streams API Platform is designed for the use case where you know your recipients: clients, vendors, internal teams, regulators, auditors, board members. Endpoint quotas, monthly byte caps, and per-recipient rate limits all assume tens to thousands of calls per day per recipient. If your use case is a public marketing API that needs to serve millions of anonymous requests per second, you probably want an API gateway like Kong, Apigee, or AWS API Gateway sitting in front of a service you build yourself. The API Platform complements that pattern; it does not replace it.

Frequently Asked Questions

Do I need to open inbound firewall ports to expose my database as an API? +
No — the Network Agent makes a single outbound TLS connection on port 443 and inbound API requests are routed back down it, so there are no ports to open, no VPN, and no tunnel software. how the outbound-only connection works →
Can the recipient see my SQL or my database credentials? +
Never. The recipient sees the endpoint URL, the JSON response, and any query parameters you exposed as filters. The SQL itself stays inside Query Streams. The database password lives only inside the Agent’s encrypted credential store on your network and is never transmitted to our cloud.
How do I revoke a partner’s API access? +
Open the endpoint in the Portal, find the recipient in the Manage tab, click Revoke. The recipient’s next request returns 401 KEY_REVOKED. Other recipients of the same endpoint continue working with their own keys, and you don’t have to rotate the database password.
What’s the difference between static and streaming response modes? +
Static mode buffers the full result on our edge, sets Content-Length, and emits one HTTP body. Default caps are 8 MB / 100k rows; the platform max is 50 MB / 1M rows. Best for dashboards, sub-10k-row queries, and curl users. Streaming mode emits Transfer-Encoding: chunked and pushes rows down the wire as the Agent produces them. Default caps are 1 GB / 10M rows; the platform max is 50 GB / 100M rows. Best for n8n / Zapier flows, analytics pipelines, and bulk exports. You pick the mode per endpoint; the recipient picks the wire format (JSON, NDJSON, CSV, or LZ4-frame) per call.
How do I decompress LZ4-frame responses in my code? +
LZ4-frame is the standards-compliant LZ4 frame format (magic 04 22 4D 18), so every mainstream LZ4 library handles it natively. In Python: lz4.frame.decompress(resp.content). In Node.js with lz4js: LZ4.decompressFrame(buffer). In Go with github.com/pierrec/lz4/v4: lz4.NewReader(body). On the CLI: curl ... | lz4 -d -. For streaming endpoints, the response body is a sequence of concatenated LZ4 frames over Transfer-Encoding: chunked — the same frame decoder handles single-frame and concatenated-frame inputs identically when called in a streaming loop.
Can I lock an endpoint to specific IP addresses or browser origins? +
Yes, on both axes. Each recipient’s API key has an optional IP allowlist (single IPs or CIDR ranges, IPv4 and IPv6) — calls from outside the allowlist return 403 IP_NOT_ALLOWED before any SQL is run. Separately, each endpoint has an optional CORS origin allowlist — set it to ["https://app.acme.com"] if the endpoint is meant to be called from a specific partner’s frontend, or leave it off entirely (the default) for server-to-server-only endpoints. Both controls are edited from the Install tab and take effect immediately.
How is API Platform usage billed? +
Note on bytes-on-wire. If a call requests LZ4-frame compression (Accept-Encoding: lz4), it bills on the compressed wire bytes; if not, it bills on the uncompressed bytes. The same JSON payload over LZ4 typically bills at 25-40% of its uncompressed size for transactional rows and 10-20% for analytics workloads, because that is the amount of data your partner’s network actually moved. The in-browser sandbox surfaces both numbers side by side. Standard HTTP wire compression (gzip) is pure transport optimisation and does not affect billing.
What happens if a query tries to write or delete data? +
It is rejected with READONLY_VIOLATION before it ever reaches your database — the Agent’s read-only validator runs in your network and allows only SELECT statements. why every endpoint is read-only by design →
Which SQL databases does the API Platform support? +
Microsoft SQL Server, PostgreSQL, MySQL, MariaDB, SQLite, Microsoft Access, Snowflake, Oracle, BigQuery, and DuckDB — on-premise or hosted on AWS RDS, Azure SQL Database, Google Cloud SQL, or any other cloud you can reach with the Agent. Additional database engines are added regularly; check the Connector Setup category for the current list.
Can I see what each recipient actually requested? +
Yes. Every request is logged with the recipient’s key prefix, source IP, query parameters, response status, wire bytes, and duration. The activity ledger is visible in the Portal and is included in any compliance / audit pull you run from your account. Nothing is purged silently; retention follows your plan’s audit-log retention policy.
How does this compare to building an API myself with Express, FastAPI, or PostgREST? +
A hand-rolled service gives you maximum flexibility and maximum maintenance. You build the auth, the rate limiting, the per-recipient key minting, the audit ledger, the deploy pipeline, the TLS rotation, the monitoring — and you keep all of it patched. Query Streams ships that surface as a managed feature: you save a SQL query, you mint a key, you’re done. PostgREST is closer to us in concept but ties you to PostgreSQL, exposes the whole schema by default, and still leaves the deploy / TLS / auth / audit surface for you to build.
Does the API Platform need a separate subscription on top of Query Streams? +
No. The API Platform is part of every Query Streams plan; usage draws from the same monthly byte allowance that powers Excel, Google Sheets, the MCP Server, and Nova AI. Higher tiers raise the per-endpoint quotas, the number of concurrent endpoints, and the recipient cap. See the pricing page for the per-tier numbers.

Get Started

Mint your first shared API in five minutes.

Sign up free, install the Network Agent next to your database, save a SQL query, and email a recipient a magic-link claim invitation. Per-recipient keys, audit trail, and compression-aware billing are on from minute one.

Related guides: Share live database data without sharing credentials | Share query results securely | Connector Setup guides | All MCP Server guides

Category: API Platform

Tags: api-platform, rest-api, share-live-data, partner-api, per-recipient-keys, sql-to-api, instant-api, streaming-api, ndjson, lz4-compression, openapi, postgres, mysql, sql-server, snowflake

Meta Description: Instant REST API for popular SQL databases — static or streaming, JSON/NDJSON/CSV/LZ4-frame, per-recipient keys, OpenAPI 3.1, no credentials shared.

Updated on June 16, 2026

Powered by BetterDocs