---
name: sqlpermit
description: Deterministic PostgreSQL statement policy checks with a signed, statement-bound execution permit.
---

# SQLPermit

SQLPermit parses a candidate statement with PostgreSQL's own grammar (libpg-query, PG 18), walks the syntax tree, and reports whether it complies with the policy you supply — read-only, schema and table allowlists, row ceilings, a function allowlist, single-statement enforcement. Detection is structural, so comments, dollar quoting, Unicode escapes, and stacked statements cannot hide a construct from it. On a compliant statement it can issue a signed Ed25519 permit, valid for seconds, bound to the sha256 of the deparsed canonical statement so it cannot be moved onto a different one. An offline reference verifier is published for your executor. What it cannot do: it never connects to your database, so it cannot see your schema, cannot resolve search_path, and cannot know the role a statement will run as. It is defence in depth alongside a least-privilege database role, not a replacement for one.

## When to use this

Use `POST https://sqlpermit.schemasure.com/v1/guard/sql` when you need: Check a PostgreSQL statement against an execution policy and optionally issue a signed permit.

## How to pay

1. Send the request without payment. You receive HTTP 402 and a base64 `PAYMENT-REQUIRED` header.
2. Decode it, sign one of the `accepts` entries with your wallet locally.
3. Retry the identical request with the `PAYMENT-SIGNATURE` header.
4. A successful response carries a `PAYMENT-RESPONSE` receipt.

Cost: $0.01 in USDC on Base mainnet. Failed calls are free.
Your private key never leaves your process.

## Request

```json
{
  "dialect": "postgresql",
  "target_pg_major": 18,
  "sql": "SELECT id, total FROM analytics.orders ORDER BY created_at DESC LIMIT 100",
  "policy": {
    "read_only": true,
    "allowed_schemas": [
      "analytics"
    ],
    "allowed_tables": [
      "analytics.orders"
    ],
    "max_rows": 1000,
    "allowed_functions": [
      "count",
      "sum",
      "avg"
    ],
    "allow_multi_statement": false
  },
  "audience": "executor:acme-prod-01",
  "issue_permit": true
}
```

## Response

```json
{
  "ok": true,
  "verdict": "pass",
  "confidence": 1,
  "risk_codes": [],
  "evidence": [],
  "result": {
    "statement_count": 1,
    "statement_types": [
      "SelectStmt"
    ],
    "referenced_schemas": [
      "analytics"
    ],
    "referenced_tables": [
      "analytics.orders"
    ],
    "referenced_functions": [],
    "classification": "read_only",
    "findings": [],
    "normalized_sql": "SELECT id, total FROM analytics.orders ORDER BY created_at DESC LIMIT 100",
    "stmt_hash": "sha256:0f4c1d8e2b7a9c5d3e6f8a1b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
    "stmt_fingerprint": "a1b2c3d4e5f60718",
    "policy_hash": "sha256:2b7a9c5d3e6f8a1b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d0f4c1d8e",
    "grammar": "pg18/libpg-query@18.1.4",
    "target_pg_major": 18,
    "obligations": {
      "max_rows": 1000,
      "statement_timeout_ms": 30000,
      "require_read_only_tx": true
    },
    "permit": "eyJhbGciOiJFZDI1NTE5Iiwia2lkIjoi…",
    "permit_expires_at": "2026-08-03T12:00:45.000Z",
    "permit_kid": "uGzLTTwi4LTsOiBLgwf2wMU7ILTecj18Y_71Y37bVD8"
  },
  "policy_version": "sqlpermit-policy-1.0.0",
  "request_hash": "sha256:1c2d0f4c1d8e2b7a9c5d3e6f8a1b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
  "data_versions": {
    "grammar": "pg18/libpg-query@18.1.4",
    "parser": "libpg-query@18.1.4",
    "pg_parse_version": "180004"
  },
  "warnings": [
    "stmt_fingerprint is ADVISORY and must not be used for authorization: libpg-query fingerprints discard literal constants, so two statements differing only in a value share one fingerprint. stmt_hash is the binding value."
  ]
}
```

## Reading the verdict

- `resolved` / `pass` / `allow` — the service answered and the answer is usable.
- `warn` — usable, but `evidence` contains findings you should act on.
- `block` — the service is telling you not to proceed. Read `risk_codes`.
- `unknown` — the service could not determine the answer. **Do not treat this as safe.**
