The qp command-line client
File a query request from a terminal or CI, wait for approval and download the masked result with qp, the command-line client for the QueryProxy REST API.
qp is a command-line client for the REST API. It files a query
request, waits until a reviewer approves it and the worker has run it, and
downloads the result. It is one static binary with nothing to install around it,
so it fits a terminal as well as a CI job. It lives in its own repository,
QueryProxy/qp.
qp can do only what the API allows. It cannot approve a request, and in
particular it cannot approve its owner’s own request or override anything.
Nothing runs until a reviewer approves it, exactly as for a request filed in the
web UI, and the SQL guards and masking apply as usual.
Install
Download the archive for your platform from the
releases page. Binaries exist for
Linux, macOS and Windows, on amd64 and arm64. Put qp on your PATH.
The release workflow signs the checksum file keylessly with cosign. Verify the download before you use it:
cosign verify-blob \
--bundle checksums.txt.sigstore.json \
--certificate-identity-regexp '^https://github.com/QueryProxy/qp/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
checksums.txt
sha256sum --ignore-missing -c checksums.txt
Authenticate
Create a personal access token in the web UI (Profile). A token has scopes and an expiry; give it only what the pipeline needs:
| To use | The token needs |
|---|---|
qp submit |
requests:submit (this also covers looking up a connection by name) |
qp submit --wait |
additionally requests:read, and results:read for the result |
qp status |
requests:read |
qp results |
results:read, plus requests:read for --format json |
export QP_URL=https://queryproxy.example.com
export QP_TOKEN='12|qp_...'
--url and --token override the environment. Prefer the environment: a flag
value shows up in the process list. qp never prints the token and scrubs it
from error messages. A plain http:// URL works, with a warning unless it points
at the local machine, but sends the token unencrypted. qp does not follow
redirects, so set QP_URL to the final address.
Commands
qp submit
qp submit --conn <id|name> --file report.sql --reason "monthly report" --wait
| Flag | Meaning |
|---|---|
--conn |
Connection id, or its exact name (a number is always taken as an id) |
--file |
File with the SQL; - reads standard input |
--reason |
Why the statement is needed; the approver sees it |
--title |
Short title, optional |
--wait |
Wait for the approval and the run, then print the result |
--timeout |
With --wait: give up after this long. Default 30m; 0 waits forever |
--poll-interval |
With --wait: how often to ask the server. Default 3s |
--format, --output |
With --wait: as for qp results |
Progress goes to standard error. Without --wait, standard output carries only
the request id, so id=$(qp submit ...) works. With --wait, it carries only the
result. A timeout or Ctrl-C stops the waiting but does not cancel the request:
it stays on the server, and qp status <id> follows it.
qp status
qp status <id> prints the request, as JSON with --json. For a finished
request it exits with the same codes as qp submit --wait.
qp results
qp results <id> downloads the stored result of a completed request, already
masked by the connection’s masking rules.
--format csv(the default) writes the result as the server sends it.--format jsonwrites one array of objects keyed by column name, streamed. Values keep their exact text; duplicate column names give duplicate keys.--output <file>writes to a file instead of standard output. The file gets mode0600, also when it already exists.
If the server cut the result at its row ceiling, qp says so on standard error.
qp version prints the version.
Exit codes
| Code | Meaning |
|---|---|
0 |
Completed successfully |
1 |
Usage, configuration, network or API error: a bad token, an unknown connection, SQL the guard refused |
2 |
The request was rejected |
3 |
The request failed to run, or was cancelled |
4 |
--timeout ran out before the request finished |
130 |
Interrupted |
In a pipeline
- run: qp submit --conn prod-reporting --file sql/report.sql --reason "nightly report $GITHUB_RUN_ID" --wait --timeout 2h --output report.csv
env:
QP_URL: ${{ vars.QP_URL }}
QP_TOKEN: ${{ secrets.QP_TOKEN }}
The job stays blocked until a reviewer decides, so give --timeout a value that
fits how fast your team approves, and use a token that expires soon.
Next steps
- REST API and personal access tokens for the endpoints behind
qp. - Approval workflow for who approves and how.
- Production hardening for token hygiene.