REST API and personal access tokens
Submit query requests, follow them and read masked results from scripts and CI with the /api/v1 REST API. Token scopes, expiry, rate limits, curl examples.
Updated:
The REST API gives scripts and CI pipelines the same workflow the web UI has. A request filed through the API passes the same SQL guards, waits for the same human approval, and lands in the same audit trail as one filed in the browser. Nothing runs until a reviewer approves it.
The endpoint reference is the OpenAPI 3.1 description in the repository,
docs/openapi.yaml.
This page explains how tokens work and walks through a complete flow.
If you only need to file a request, wait for it and fetch the result from a
terminal or a pipeline, the qp command-line client does the
polling for you.
Personal access tokens
Every call carries a personal access token as a bearer token:
Authorization: Bearer <id>|qp_<secret>
- A token belongs to one user and never grants more than that user may do in the web UI. Its scopes can only narrow that.
- A token always expires. You choose the lifetime when you create it, up to
QUERYPROXY_API_TOKEN_MAX_DAYSdays (default90). - A token stops working when its owner is suspended or deleted. There are no service accounts: automation uses the token of a real user.
- A user the
QUERYPROXY_REQUIRE_2FApolicy covers, who has not enrolled in two-factor authentication yet, gets no API access: a token must not stand in for the second factor. The token works again once the user has enrolled.
Create and revoke a token
Open Profile, then API tokens. Name the token, pick its scopes and set the
number of days it lasts, then create it. The value is shown once, with a Copy
button; QueryProxy stores only its hash, and a reload hides the value for good.
Revoking a token stops it at once. Creating and revoking are audited as
token.created and token.revoked.
Creating a token asks you to confirm first:
- With a local password, enter it, or a code from your authenticator app when two-factor authentication is on. Failed attempts are rate-limited.
- An account without a local password (SSO only) must have signed in through SSO in the last 15 minutes. Otherwise the page sends you through SSO again and brings you back.
Scopes
| Scope | Allows | Who can hold it |
|---|---|---|
requests:submit |
Submit a request, cancel your own pending request | DBA or developer in a team |
requests:read |
Read a request, list connections | Every role |
results:read |
Read the stored result of a completed request | Not offered to auditors |
approvals:write |
Approve or reject requests you may review | DBA in a team |
The Profile page offers only the scopes your roles can use.
Rules the API keeps
- No approval bypass. A request waits for a human decision exactly as in the web UI.
- No overrides. An approval that would be an override (the reviewer is a
system administrator who is not a DBA of the request’s team, or is the requester) needs a
written reason. The API refuses it with
403and anoverride_urlthat points to the web form. The API has no override of its own. - A token can never approve its owner’s own request.
- Results are masked by the connection’s masking rules, as everywhere else. Column names of a result are result data: an auditor does not get them, and an auditor cannot read a result at all.
- Auditors see redacted SQL.
sql_originalandsql_preparedarenullfor a viewer who may not see literal values;sql_redactedcarries the statement with the literals replaced.
A leaked token held by a reviewer can still approve other users’ requests inside
its approvals:write scope, so give that scope sparingly and keep the lifetime
short.
Rate limits
Each token may make QUERYPROXY_API_RATE_LIMIT calls per minute (default 60).
Above that the API answers 429 with a Retry-After header. Failed
authentications are limited per address to the same number. Responses carry
X-RateLimit-Limit and X-RateLimit-Remaining.
Walkthrough with curl
The examples assume two variables. QP is your installation, and TOKEN holds
the token value.
export QP=https://queryproxy.example.com
export TOKEN='7|qp_...'
1. Find the connection. GET /api/v1/connections lists the connections you
can use, without hosts or credentials.
curl -s -H "Authorization: Bearer $TOKEN" -H 'Accept: application/json' \
"$QP/api/v1/connections"
2. Submit the request. Needs requests:submit. connection_id and sql are
required; title (up to 150 characters) and reason (up to 1000) are optional.
The answer is 201, with the request in status pending.
curl -s -X POST "$QP/api/v1/requests" \
-H "Authorization: Bearer $TOKEN" -H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"connection_id": 3, "sql": "SELECT id, status FROM orders", "title": "Orders check", "reason": "Support ticket"}'
The SQL guard can refuse the statement with 422 and a list of violations. A
connection you cannot see is reported as 404; one you can see but may not submit
to (an auditor, or a read grant for a write) is refused with 403.
3. Follow the request. Needs requests:read. Poll until status is
completed (or failed, rejected, cancelled). The statuses are pending,
approved, rejected, queued, running, completed, failed and
cancelled.
curl -s -H "Authorization: Bearer $TOKEN" -H 'Accept: application/json' \
"$QP/api/v1/requests/42"
4. Approval. A DBA of the team (not the requester) approves in the web UI, in
Slack or Teams, or through the API with the reviewer’s own token that
has approvals:write (REVIEWER_TOKEN below):
curl -s -X POST -H "Authorization: Bearer $REVIEWER_TOKEN" -H 'Accept: application/json' \
"$QP/api/v1/requests/42/approve"
curl -s -X POST "$QP/api/v1/requests/42/reject" \
-H "Authorization: Bearer $REVIEWER_TOKEN" -H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"reason": "Add a WHERE clause"}'
A decided request answers 409. The requester can withdraw a pending request
with POST /api/v1/requests/42/cancel.
5. Read the result. Needs results:read. The default is NDJSON, one JSON
array per row with values in column order (the column names are in
result.columns of the request). Send Accept: text/csv for CSV with a header
row.
curl -s -H "Authorization: Bearer $TOKEN" "$QP/api/v1/requests/42/result"
curl -s -H "Authorization: Bearer $TOKEN" -H 'Accept: text/csv' \
"$QP/api/v1/requests/42/result" -o result.csv
The response headers X-QueryProxy-Row-Count and X-QueryProxy-Truncated tell
you how many rows were stored and whether the result was cut at the row ceiling.
A request that has not completed, has failed, or whose result has been pruned
answers 409; 404 means the stored file is missing from the result disk. Every read is audited as result.downloaded.
Errors
Errors are JSON with a message. Validation errors (422) add errors, keyed
by field.
| Status | Meaning |
|---|---|
401 |
The token is missing, unknown, expired or revoked, its owner is suspended or deleted, or its owner must enrol in two-factor authentication first |
403 |
The token lacks the scope, or its owner may not do this. An override adds override_url |
404 |
No such request or connection, or a connection that is not visible to you. A request you may not view answers 403 |
409 |
The request is no longer pending, or has no stored result (not completed, failed, or pruned) |
422 |
The body is invalid, or the SQL guard refused the statement |
429 |
The token used its budget for this minute; wait Retry-After seconds |
In the audit log
API submissions, approvals, rejections, cancellations and result reads carry
channel = api. Token creation and revocation are token.created and
token.revoked. See Audit chain and SIEM export.
Settings
| Variable | Default | Meaning |
|---|---|---|
QUERYPROXY_API_TOKEN_MAX_DAYS |
90 |
Longest lifetime a token can be given, in days |
QUERYPROXY_API_RATE_LIMIT |
60 |
Calls per minute for each token, and failed authentications per address |
Next steps
- The
qpclient for a ready-made command-line client of this API. - Approval workflow for who may approve what.
- Single sign-on if your users sign in through a provider.
- Production hardening for token hygiene.