Midwess
LearnAPI

POST /query is the main PgPaw endpoint.

curl -i -X POST http://127.0.0.1:8080/query \
  -H "content-type: application/json" \
  -d '{"sql":"select id, email from users where id = 7"}'

Request

{
  "sql": "select id, email from users where id = 7"
}

The SQL must be one read-only PostgreSQL SELECT over replicated tables. See SQL classifier.

Public response

When every touched table is public, PgPaw materializes the query and returns a snapshot cursor:

HTTP/1.1 303 See Other
Location: /q/{hash}/{version}
Cache-Control: no-store

The response body is empty. Follow Location with GET to read the JSON body.

Private response

When any touched table is private, PgPaw requires a bearer token.

curl -i -X POST http://127.0.0.1:8080/query \
  -H "content-type: application/json" \
  -H "authorization: Bearer $TOKEN" \
  -d '{"sql":"select id, title from documents order by id"}'

Response:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, no-store

Private rows are returned inline because the result depends on the caller's role and claims. Private rows are not stored in PgPaw's shared query cache and do not receive a /q/{hash}/{version} cursor URL.

See Cache-Control for the complete public/private header matrix.

Handler gates

If an Authorization header is present, PgPaw parses it before the query is classified. Public queries do not require a token, but a malformed token still causes 401.

Logs

Accepted queries emit logs like:

level=INFO target=pgpaw::http::query event=query_classified fingerprint=9a4f tables=users live=false scope=public
level=INFO target=pgpaw::http::query event=query_snapshot scope=public fingerprint=9a4f tables=users version=42 cursor=/q/9a4f/42 response=redirect snapshot_bytes=128

PgPaw logs the SQL fingerprint and tables, not raw SQL text.