Point Power BI at a REST API, not a database driver
Power BI normally reaches a database through a driver on your machine and, for scheduled refresh, an on-premises gateway. A Query Streams endpoint is a plain HTTPS URL, so Power BI Desktop and the Power BI service read it with the built-in Web connector. The portal writes the Power Query M for you: Export → Power Query M (.pq), paste, add your key, done. The same file works in Excel.
What you’ll end up with
A Power BI table fed by a saved SQL query over HTTPS, refreshable in the service without an on-premises gateway, because the endpoint looks like any web API. The endpoint itself looks like this in the portal console:
| Key | Value | Description |
|---|---|---|
city string Optional | Seattle | Callable filter from the saved query |
X-API-Key | qsapi_AB•••••••••• | Your secret token |
{
"city": "Seattle"
}| Header | Value | Description |
|---|---|---|
X-API-Key | qsapi_AB•••••••••• | Your secret token. One per caller, revoke anytime. |
Content-Type | application/json | The parameters travel as a JSON body |
Accept | application/json | JSON back; ask for text/csv and you get CSV |
curl -X POST "https://api.querystreams.com/v1/endpoints/8f2c4d1e-6b3a-4f7e-9c21-5d0a7b3e2f19" \
-H "X-API-Key: qsapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"city": "Seattle"}'{
"columns": [ { "name": "customer_id", "type": "integer" }, { "name": "name", "type": "string" }, { "name": "city", "type": "string" } ],
"rows": [
{ "customer_id": 1042, "name": "Northwind Traders", "city": "Seattle" },
{ "customer_id": 1057, "name": "Alpine Outfitters", "city": "Denver" },
{ "customer_id": 1063, "name": "Harbour Foods", "city": "Halifax" }
],
"meta": { "row_count": 3, "agent_duration_ms": 41, "request_id": "req_7Hq2mKd9" }
}| Header | Value | Description |
|---|---|---|
content-type | application/json; charset=utf-8 | The envelope on the Body tab |
content-length | 412 | Bytes returned; the Size badge above |
The reason drivers exist is that Power BI has to speak each database’s wire protocol from wherever it runs, which is why scheduled refresh needs a gateway sitting inside your network. With an endpoint, the Agent that already sits with the database runs the query and api.querystreams.com answers Power BI over HTTPS, so there is nothing for Power BI to install and nothing for the service to tunnel into.
What you’ll need
A Query Streams account
Every plan has the REST API. Free gives you one endpoint, two keys and 250 rows per call, which is enough to build this guide. Sign up at my.querystreams.com.
The Agent on a computer that can reach your own database, with the connection added
Often the database server itself or an always-on computer on the same network.
A saved query in Query Builder
The endpoint wraps exactly one saved query. Write the SELECT, run it once, save it. Add a Dynamic Filter (a @variable in the WHERE clause) if callers should be able to filter.
Who can create endpoints? Owners, Admins and Creators. If the API item is missing from your top bar, an Admin can switch it on for your user under Account → Permissions. Anyone you share an endpoint with only needs a Query Streams account, on any plan.
Plus Power BI Desktop, or Excel 2016 or later. If you do not have an endpoint yet, the Database REST API hub takes you from a saved query to a URL in four steps.
Connect Power BI in four steps
Export the Power Query M file
In API open your endpoint, click Export and choose Power Query M (.pq). The file is a complete
let … inexpression with the endpoint URL and your callable parameters already in place. It looks like this:Export · Power Query M (.pq)let Source = Json.Document(Web.Contents( "https://api.querystreams.com/v1/endpoints/8f2c4d1e-6b3a-4f7e-9c21-5d0a7b3e2f19", [Headers = [#"X-API-Key" = "qsapi_YOUR_KEY"], Query = [compression = "none"]])), rows = Source[rows], table = Table.FromRecords(rows) in table
The export always asks for
compression=none, because Power Query cannot decode LZ4 frames; JSON over gzip is what Power BI expects.Paste it into Power BI
In Power BI Desktop choose Home → Transform data → New Source → Blank Query, open the Advanced Editor, replace its contents with the file and click Done. In Excel the path is Data → Get Data → From Other Sources → Blank Query, then the same Advanced Editor.
Add your key
Replace the placeholder with your 49-character key starting
qsapi_from the endpoint’s Sharing tab. When Power BI asks how to connect, choose Anonymous: the key travels in theX-API-Keyheader the query already sets, so no Power BI credential is needed.Load and refresh
Click Close & Apply. The
rowsarray becomes your table with one column per query column. Publish to the Power BI service and set a refresh schedule: because the source is a web URL, no on-premises gateway is required. Each refresh is one call on the endpoint’s Activity tab.
That’s it. Power BI reads live rows over HTTPS, the database stays behind your firewall, and the report owner never sees the SQL or a database login.
The request Power BI makes
Under the hood it is the same call you can make with curl. Parameters marked Callable can be passed from Power Query as Query = [city = "Seattle"]:
curl "https://api.querystreams.com/v1/endpoints/8f2c4d1e-6b3a-4f7e-9c21-5d0a7b3e2f19?city=Seattle" \ -H "X-API-Key: qsapi_YOUR_KEY"
{
"columns": [ { "name": "customer_id", "type": "integer" }, { "name": "name", "type": "string" }, { "name": "city", "type": "string" } ],
"rows": [
{ "customer_id": 1042, "name": "Northwind Traders", "city": "Seattle" },
{ "customer_id": 1057, "name": "Alpine Outfitters", "city": "Denver" },
{ "customer_id": 1063, "name": "Harbour Foods", "city": "Halifax" }
],
"meta": { "row_count": 3, "agent_duration_ms": 41, "request_id": "req_7Hq2mKd9" }
}Rows come back as objects keyed by column name, with the column list and a meta block that tells you how many rows there are, whether the result was clipped, and the request id you can quote to support. The same call as a POST puts the parameters in a JSON body, which is the right choice for long lists of values:
curl -X POST "https://api.querystreams.com/v1/endpoints/8f2c4d1e-6b3a-4f7e-9c21-5d0a7b3e2f19" \
-H "X-API-Key: qsapi_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"params": {"city": "Seattle"}}'?format=csv- CSV instead of JSON, header row included.
Accept: text/csvdoes the same. Streaming endpoints return NDJSON (one JSON object per line) with a final{"_summary": true, "row_count": N}line; check that last line. ?sample=true- At most five rows, not billed. Good for wiring up a client.
?compression=lz4- An LZ4-frame body for high-volume callers with a decoder; the Code tab has the decoder snippet per language. Plain gzip happens on its own when your client sends
Accept-Encoding: gzip. Authorization: Bearer qsapi_…- Accepted as an alias for
X-API-Key. Use it from browser code.
Beyond Power BI
Excel and Power BI
Export → Power Query M (.pq) gives you a ready-made query. Paste it into the Advanced Editor (Excel: Data → Get Data → From Other Sources → Blank Query; Power BI: Home → Transform data → New Source → Blank Query) and replace the placeholder with your qsapi_ key.
Postman, Insomnia, Hoppscotch
The same Export menu downloads a Postman v2.1 collection, an Insomnia v5 export or a Hoppscotch collection with the parameters filled in and a placeholder for the key.
Your own code
The Code tab has working samples for cURL, Python, JavaScript, TypeScript, Java, C#, Go, PHP, PowerShell and Ruby. Export → OpenAPI 3.1 spec feeds any client generator or Swagger UI.
n8n, Zapier, Make, cron
Create a Service key for the tool, paste the URL and the key into its HTTP node, and schedule it. Each tool gets its own key, so you can revoke one without touching the others.
What the plans allow
| Plan | Endpoints | Keys per endpoint | People per endpoint | Rows per call (static) | Rows per call (streaming) | Calls per minute, per key |
|---|---|---|---|---|---|---|
| Free | 1 | 2 | 1 | 250 | 250 | 3 |
| Personal Core | 2 | 3 | 2 | 5,000 | 250,000 | 15 |
| Personal Plus | 5 | 5 | 5 | 10,000 | 500,000 | 30 |
| Business Core | 5 | 10 | 10 | 50,000 | 1,000,000 | 300 |
| Business Plus | 10 | 20 | 25 | 100,000 | 5,000,000 | 600 |
| Business Max | 25 | 30 | 50 | 250,000 | 10,000,000 | 600 |
| Enterprise Core | 50 | 50 | 100 | 500,000 | 25,000,000 | 1,000 |
| Enterprise Plus | 100 | 100 | 250 | 1,000,000 | 50,000,000 | 1,000 |
| Enterprise Max | 250 | 200 | 500 | 2,500,000 | 100,000,000 | 1,500 |
Every call also has a fixed 60-second query timeout and an 8 MiB response cap, and the bytes it returns count against your plan’s monthly data allowance, the same allowance the Excel and Google Sheets add-ins use. Free has one endpoint and 5 MB a month, which comfortably covers a small table called many times a day. See the pricing page for the allowances by plan.
Free is a plan you keep. It is not a trial that expires. See exactly what it includes on the pricing page.
For a report, keep the query to the columns the visuals need and let the refresh schedule do the rest; Power BI reads the whole result on every refresh, so the static row cap of your plan is the ceiling for a single table.
Keeping it safe
The SQL stays yours
Callers get rows. They never see the query, the table names or the connection details, and they cannot change what the endpoint runs.
One key, one endpoint, one caller
A key works for exactly one endpoint. Give each person or tool their own, and every call is logged against it. Revoke one and the others keep working; revocation takes effect within about five minutes.
Read-only, on your side of the wall
The Agent runs the one saved SELECT and nothing else, from inside your network, over its own outbound connection. There is no port to open and no credential on the internet.
Every call in the ledger
The Activity tab shows each call with time, recipient, status, rows, bytes and duration, including refused ones. Set an Expiring lifespan for a temporary share and the endpoint switches itself off on the date.
When something doesn’t work
| Status | Code | What to do |
|---|---|---|
| 401 | API_KEY_REQUIRED / API_KEY_INVALID | The header is missing or the key is wrong. Send X-API-Key: qsapi_…, the whole 49 characters. Keys can be copied again from the endpoint’s Sharing tab. |
| 403 | API_KEY_ENDPOINT_MISMATCH | A valid key used on a different endpoint. Each key belongs to one endpoint. |
| 403 | API_KEY_REVOKED / API_ENDPOINT_EXPIRED | The key was revoked or the endpoint reached its expiry date. Mint a new key or restore the endpoint. |
| 400 | API_PARAM_NOT_OVERRIDABLE | You sent a parameter the owner did not mark Callable. The body lists the allowed names. |
| 414 | API_QUERY_TOO_LARGE | The GET query string passed 8 KB or 200 list values. Send the same parameters as a POST JSON body. |
| 413 | API_OUTPUT_TOO_LARGE | The result passed the 8 MiB cap. Narrow the query, or switch the endpoint to Streaming. |
| 429 | API_RATE_LIMITED | Too many calls this minute for the key or the endpoint. Wait for the Retry-After seconds, or raise Rate limit under API Settings → Defaults within your plan. |
| 429 | API_DATA_REALM_EXHAUSTED | The owner’s monthly data allowance is used up. It resets with the billing month; upgrade for more. |
| 503 | API_AGENT_NOT_CONNECTED | The Agent behind your own database is offline. Check it under Data → Data Agents; calls succeed again as soon as it reconnects. |
| 504 | API_QUERY_TIMEOUT | The query took longer than 60 seconds. Add a filter or an index, or save a narrower query. |
Every error body has the shape {"error": {"code": "…", "message": "…"}} and every response carries an X-QS-Request-Id header. Quote the id when you write to support.
Two Power Query specifics: a DataSource.Error naming 401 means the key in the M file is wrong or was revoked; Expression.Error: The field ‘rows’ of the record wasn’t found means the endpoint returned an error envelope, so run the URL in the portal console to read it.
Questions people ask
Does my data get copied to Query Streams?
No. The Agent runs the query where your data is and sends only the rows the call asked for. Nothing is synced or cached in between; every call runs the query.
Can callers change the SQL or ask for other tables?
No. An endpoint runs one saved query. Callers can only set the parameters you marked Callable, and the values are bound safely: text is quoted and escaped, numbers are range-checked, lists are expanded.
How do I hand this to a partner?
On the endpoint’s Sharing tab choose People and enter their email. They get a magic link, sign in with that address (a Free account is fine) and mint their own key; you see their calls by name on the Activity tab. For a server or an automation tool, create an API key with a label instead.
What happens when I edit the saved query?
The endpoint runs the new SQL on the next call and its OpenAPI spec regenerates. The URL and the keys do not change. If you add or remove a filter, revisit Lock params so callers see the right parameters.
Does the Free plan really cover this?
Yes. Free is a plan you keep, not a trial that expires. It includes one endpoint, two keys and 250 rows per call. See exactly what it includes on the pricing page.
Does scheduled refresh in the Power BI service really work without a gateway?
Yes. The service can refresh any Web.Contents source that is reachable on the public internet with anonymous or embedded-header authentication. api.querystreams.com is such a source; your database is never contacted by Power BI.
Can I use DirectQuery?
No. Web sources in Power BI are Import mode. Use the refresh schedule, or keep the query narrow and refresh more often within your plan’s rate limit.
Can Tableau or Looker Studio use the same endpoint?
Any tool with a web or REST connector can, and Export → OpenAPI 3.1 spec describes the endpoint for connector builders. Choose ?format=csv for tools that prefer CSV.
Related guides
Setup trouble?
- Agent shows Offline, or Test connection and Browse are greyed out? Check that the Agent is online → Is the Agent online?

