← Query Gate
API
Tokens

Run Query Gate from your own code

Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page ranks the architecture by scan economics and the pricing shapes, audits every view for engine-level isolation and every compute handle for its caps, and checks the schema contract and credentials, then hands it all to the run as facts. The run writes the access blueprint or the customer contract; the page then holds it to its own facts (qgreply.js).

Base URL and the response shape

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }

The token is minted for this app (the guest call takes {"slug":"query-gate"}), so no slug header is needed afterwards. Send it as Authorization: Bearer YOUR_TOKEN. The run body is the input object, sent bare.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required key (task, product, surface, facts) is missing. The page always sends all five of task, product, surface, carry, facts; carry is an empty string for blueprint.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: both lanes are metered and need a personal sign-in.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field comes first

Two lanes, chosen by task, in the order the work runs - for the team building it, then for the customers connecting:

A missing or unknown task is answered as blueprint. Both lanes send the same five fields. The facts are the page's checks, one row per line, and a line starting "must:" is a rule the reply has to hold. The page's handoff carries the blueprint's decisions into carry for the contract lane.

The input

FieldRequiredWhat
taskyesblueprint or contract. If it is missing or neither, do blueprint and set "task": "blueprint".
productyesthe product and the demand, the problem, where the data lives, tenants, table size and scan frequency, consumers and clients, schema and CI, connectivity, credentials and pricing intent; "not given" means not given.
surfaceyesevery view with its isolation mechanism, identity and probe; every compute handle with its caps and who pays; every planned schema change.
carrysend it (empty for blueprint)contract task only: the blueprint's decisions, when the blueprint task ran first; may be empty.
factsyesthe page's checks, one line each as area | id | status | evidence, the verdict and the rankings. A line starting "must:" applies to your reply.
retry_notenooptional; if present, your previous reply was unusable - follow it.

The rules the model applies

From the source skill, @samber/sql-jdbc-access-design - the only standard the run applies:

House rules

The replies

blueprint

Write the SQL access design: the state the surface must reach before customers connect - where the facts show a failing view or handle, write the policy or cap that replaces it, not today's broken state (the failures belong in caveats).

  1. summary - one sentence, at most 40 words: the architecture, and what must hold before any customer gets a handle.
  2. problem - scope (one sentence: which problem this is, for this product), consumers (short strings: each consumer and its query shape).
  3. architecture - rung (share, replica or isolated, exactly as the facts rank first), promote_when (one sentence: the measurable condition in this product's terms that would move it up the ladder (share, then replica, then per-tenant isolation) to the next rung - or, when the rung is already per-tenant isolation, the condition under which a cheaper rung would do), why (one sentence naming the moves that ranked it).
  4. isolation - one object per view, exactly the facts' list and order: view (as written), policy (one sentence: the engine policy this view must have - a secure or dynamic view or row-level security), identity (the identity the policy filters on), probe (one sentence: the probe run as another tenant, which must return zero foreign rows).
  5. schema - layer (one sentence: the curated mart this product serves), rule (one sentence: the additive-only rule as it applies to these tables), ci (one sentence: how this product's CI blocks a breaking change).
  6. connectivity - rung (postgres, driver or dialect, exactly as the facts read it), setup (one sentence: what this product publishes so its clients connect).
  7. governance - one object per compute handle, exactly the facts' list and order: handle (as written), timeout, concurrency, spend (each a short phrase giving the cap to set on this handle - a proposed starting value where the input gives none, said to be proposed; never none).
  8. credentials - order (short strings, best first), bi (one sentence: how this product's BI clients authenticate), service (one sentence: how its services and agents authenticate).
  9. pricing - shape (flat, consumption or passthrough, exactly as the facts rank first), meter (the business unit metered, empty for a flat gate), why (one sentence).
  10. caveats - at most 4 short strings; [] when there is nothing to say.
{
  "task": "blueprint",
  "summary": "one sentence",
  "problem": { "scope": "one sentence", "consumers": [ "a consumer and its query shape" ] },
  "architecture": { "rung": "share", "promote_when": "one sentence", "why": "one sentence" },
  "isolation": [ { "view": "as written", "policy": "one sentence", "identity": "the identity", "probe": "one sentence" } ],
  "schema": { "layer": "one sentence", "rule": "one sentence", "ci": "one sentence" },
  "connectivity": { "rung": "postgres", "setup": "one sentence" },
  "governance": [ { "handle": "as written", "timeout": "a cap", "concurrency": "a cap", "spend": "a cap" } ],
  "credentials": { "order": [ "best first" ], "bi": "one sentence", "service": "one sentence" },
  "pricing": { "shape": "flat", "meter": "", "why": "one sentence" },
  "caveats": [ ]
}

The page holds it to its own facts (check() in qgreply.js; any warning makes the reply flagged): the reply answers the task it was sent; no web link; no backticks; no credential inside a connection string; a summary of at most 40 words; the page's architecture rung with a promotion condition; exactly the page's views in order, each with an engine policy (never an application-layer filter), the right identity (share-aware on a warehouse, a verified token otherwise) and a probe run as another tenant; exactly the page's handles in order, each with a timeout, a concurrency cap and a spend cap; the page's connectivity rung; short-lived tokens first; the page's pricing shape, metered on a business unit and never warehouse credits; a curated mart, the additive-only rule and CI enforcement; at most four caveats.

contract

Write what customers read: the data contract and one connection page per client.

  1. summary - one sentence, at most 40 words.
  2. contract - schema (the columns, types and nullability customers may rely on), quality (the completeness thresholds the data meets), freshness (the refresh window - the input's interval when it gives one), availability (an uptime figure and a latency figure, each said to be proposed when the input gives none) - one or two sentences each, for this product's tables.
  3. breaking - short strings: what counts as a breaking change - a rename, a type change, a drop - in this product's terms; an example may name this product's columns, but never describe a change the input shows as staged as if it were made in place.
  4. changes - one object per planned breaking change, exactly the facts' list and order: change (the change as the facts name it), steps (four short strings in order: add, deprecate, migrate, remove - each saying what happens for this change; for a drop with no replacement, the add step says nothing is added).
  5. pages - one object per client, exactly the facts' list and order: client (as written), url (the connection URL template, no credential in it; host and database as placeholders unless the input names them; protocol defaults such as the standard port and a TLS-required flag are fine), driver (the driver or connector), tls (TLS stated as required), auth (a token, OAuth or key-pair method - never a password).
  6. caveats - at most 4 short strings; [] when there is nothing to say.
{
  "task": "contract",
  "summary": "one sentence",
  "contract": { "schema": "one or two sentences", "quality": "one or two sentences", "freshness": "one or two sentences", "availability": "one or two sentences" },
  "breaking": [ "a rename of a column or table" ],
  "changes": [ { "change": "as the facts name it", "steps": [ "add ...", "deprecate ...", "migrate ...", "remove ..." ] } ],
  "pages": [ { "client": "as written", "url": "a template", "driver": "a driver", "tls": "TLS required", "auth": "a method" } ],
  "caveats": [ ]
}

The page requires: the reply answers the task it was sent; the same text rules; all four contract parts, with the refresh interval when the page has one; a rename, a type change and a drop named as breaking; exactly the page's planned breaking changes in order, each with the four steps add, deprecate, migrate, remove; exactly the page's clients in order, each page with a URL template, a driver, TLS stated as required and token, OAuth or key-pair auth - never a password.

Worked examples

Three runs from the page's free samples (invented companies): the blueprint for an analytics SaaS filtering in app code, the contract for the same product carrying that blueprint, and the blueprint for a billing SaaS exposing its live Postgres. Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped unedited - but the facts and long strings are shortened in this display only, and every cut is listed under the example. The last line is the page's own check of the full reply.

blueprint - An analytics SaaS filtering in app code

The page read it as: no customer gets a handle yet; architectures share, isolated, replica; pricing consumption, passthrough, flat; connectivity driver; 2 of 5 gates; failing checks: isolation, schema, cost.

Input (facts is 2560 characters; the first 1396 are shown):

{
 "task": "blueprint",
 "product": "Product: Graphwren\nWhat customers want and why: A product-analytics SaaS; enterprise customers want to point Tableau and Power BI at their own event data instead of exporting CSVs.\nProblem: Customers query their own data (this skill's full scope); data lives in: A cloud warehouse; tenants: 420 (pooled); largest scanned table: 60 GB; scans a day per typical customer: 3\nConsumers: BI tools yes, data engineers no, embedded apps or agents no; arbitrary customer-authored SQL: no; worst-case query threatens the shared cluster: no; clients to support: Tableau, Power BI\nSchema exposed: a curated mart; contract enforcement: None - convention only; mart refresh: not given; engine speaks the Postgres wire protocol: no; connectivity planned: Your own certified JDBC/ODBC driver; credentials: Short-lived tokens (key-pair JWT or OAuth)\nPricing intent: tier; compute paid by: us (provider); customer volume spread: narrow; our cost is consumption-based: no; a compounding asset; hard ship date: no; as-of: 2026-11-01",
 "surface": "Views and policies (view | mechanism | identity | probed):\n- shared.events_v | app WHERE tenant_id = ? in the query service (an application-layer WHERE filter) | identity: CURRENT_ROLE() (CURRENT_ROLE or CURRENT_USER) | probed as a consumer: NO\n- shared.sessions_v | secure view joined to the entitlement table (engine policy (secure or dynamic view, row-level security)) | identity: current_account() (a share-aware identity function) | probed as a consumer: yes\n\nCompute handles (handle | timeout | concurrency | spend | who pays):\n- Reader account per customer | timeout yes | concurrency cap yes | spend cap NO | compute paid by us\n- Shared BI warehouse | timeout yes | concurrency cap NO | spend cap yes | compute paid by us\n\nPlanned schema changes (kind | object | how):\n- add | events.utm_campaign | additive",
 "carry": "",
 "facts": "The page's checks (3 fail, 0 warn, 4 pass, 2 n/a); verdict: no customer gets a handle yet:\n- Problem, architecture and price | problem | pass | Customers query their own data (this skill's full scope)\n- Problem, architecture and price | consumers | na | BI analysts (Tableau, Power BI, Looker) - design for the most demanding: the BI analysts\n- Problem, architecture and price | architecture | na | Zero-copy warehouse share > Per-tenant compute isolation > Replicated copy or read endpoint - a compounding mandate moves the isolation rung up\n- Problem, architecture and price | pricing | pass | Consumption or volume-tiered - you pay the consumers' compute - the passthrough posture arrives with it, and a spend monitor on every account with it; a compounding mandate promotes consumption early - retrofitting a meter under a shipped flat price is a repricing\n- Isolation and schema | isolation | fail | shared.events_v: an application-layer WHERE filter - one missed filter leaks rows, CURRENT_ROLE/CURRENT_USER go NULL across a share, never probed as another tenant\n- Isolation and schema | schema | fail | a curated mart; the contract lives only in a doc - enforce it in CI; 1 planned change, every breaking one staged\n- Connectivity, governance and credentials | connectivity | pass | your own driver: accurate SQLSTATEs, signed per-tool connectors that expire, and first-line support routed\n\n[... 1164 more characters cut from this display ...]"
}

Reply (long strings shortened for display - see the cuts below):

{
 "task": "blueprint",
 "summary": "A zero-copy warehouse share for Tableau and Power BI, held until shared.events_v becomes a secure view on current_account() probed as another tenant, each reader account gains a spend cap and the Shared BI warehouse a concurrency cap.",
 "problem": {
  "scope": "Graphwren's enterprise customers query their own event data from Tableau and Power BI instead of exporting CSVs, which is this skill's full scope across 420 pooled tenants.",
  "consumers": [
   "BI analysts in Tableau: dashboards and extracts over their own events and sessions, about 3 scans a day on a table of up to 60 GB",
   "BI analysts in Power BI: the same reviewed dashboard queries, with no customer-authored SQL"
  ]
 },
 "architecture": {
  "rung": "share",
  "promote_when": "Move to per-tenant compute isolation once a tenant's scans a day climb from today's 3 toward dashboard-refresh frequency on the 60 GB events table, once customer-authored SQL or agents are admitted, or once one tenant keeps tripping the [... 26 more characters cut from this display]",
  "why": "The event data already sits in a cloud warehouse, a typical customer scans only 3 times a day, the consumers are BI tools with no arbitrary SQL and no query threatens the shared cluster, so the share ranks first, with the compounding [... 37 more characters cut from this display]"
 },
 "isolation": [
  {
   "view": "shared.events_v",
   "policy": "Rebuild shared.events_v as a secure view joined to the private entitlement table, the same pattern shared.sessions_v already uses, replacing the query-service tenant filter entirely.",
   "identity": "current_account()",
   "probe": "Query shared.events_v from a second test tenant's reader account and require zero rows belonging to any other tenant, then rerun after every policy change."
  },
  {
   "view": "shared.sessions_v",
   "policy": "Keep shared.sessions_v as a secure view joined to the entitlement table, with no direct grant on the underlying sessions table.",
   "identity": "current_account()",
   "probe": "Keep the passing consumer-side probe and rerun it as another tenant after every policy change, still requiring zero foreign rows."
  }
 ],
 "schema": {
  "layer": "Serve the curated Graphwren mart behind shared.events_v and shared.sessions_v, never the live operational schema.",
  "rule": "The events and sessions tables are additive-only: new columns such as events.utm_campaign go in freely, while no column is renamed, retyped or dropped in place but added, deprecated, migrated and only then removed.",
  "ci": "A CI check diffs each mart migration against the published contract and fails the build on any rename, type change or drop, replacing today's convention-only doc."
 },
 "connectivity": {
  "rung": "driver",
  "setup": "Graphwren publishes a connection page each for Tableau and Power BI naming the URL template, its certified JDBC/ODBC driver and signed connector with the connector's expiry, TLS as required, and OAuth sign-in."
 },
 "governance": [
  {
   "handle": "Reader account per customer",
   "timeout": "keep its existing statement timeout on every reader account",
   "concurrency": "keep its existing concurrency cap per reader account",
   "spend": "add a monthly spend cap on every reader account that suspends queries at the limit, with the amount a proposed starting point to set, not an input figure"
  },
  {
   "handle": "Shared BI warehouse",
   "timeout": "keep its existing statement timeout",
   "concurrency": "add a per-tenant concurrency cap, proposed starting point 4 concurrent queries per tenant",
   "spend": "keep its existing spend cap, and move a tenant that keeps tripping it to its own compute rather than raising it"
  }
 ],
 "credentials": {
  "order": [
   "short-lived tokens (OAuth with PKCE, key-pair JWT)",
   "managed secrets",
   "static passwords, never issued"
  ],
  "bi": "Tableau and Power BI users sign in with OAuth with PKCE for a token scoped to their own tenant over required TLS, falling back to a key-pair service account only where a tool cannot refresh, with self-service revocation in Graphwren.",
  "service": "No service or agent consumers are in scope today; any that arrive authenticate with a tenant-scoped key-pair JWT, never a secret written into a connection string."
 },
 "pricing": {
  "shape": "consumption",
  "meter": "event rows queried per month",
  "why": "Graphwren pays for every reader account's compute and the compounding mandate promotes the meter early, since retrofitting one under a shipped flat tier would be a repricing, so it is a paid add-on metered on events with a spend monitor on [... 15 more characters cut from this display]"
 },
 "caveats": [
  "No customer gets a handle until shared.events_v passes its probe and every reader account has a spend cap.",
  "The mart refresh interval is not given, so freshness cannot be promised yet.",
  "The spend and concurrency values are proposed starting points, not figures from Graphwren."
 ]
}

Cut from this display only (the saved reply and the check use the full text): architecture.promote_when (262 characters, 236 shown); architecture.why (270 characters, 233 shown); pricing.why (253 characters, 238 shown).

What step 5 prints for it: share: A zero-copy warehouse share for Tableau and Power BI, held until shared.events_v becomes a secure view on current_account() probed as another tenant, each reader account gains a spend cap and the Shared BI warehouse a concurrency cap.

The page's check: kept - nothing to flag

contract - An analytics SaaS filtering in app code

The input carries the saved blueprint's decisions (R.laneForm fills carry from the blueprint reply), as the page's handoff does. Breaking changes planned: none; clients: Tableau, Power BI.

Input (facts is 2259 characters; the first 1396 are shown):

{
 "task": "contract",
 "product": "Product: Graphwren\nWhat customers want and why: A product-analytics SaaS; enterprise customers want to point Tableau and Power BI at their own event data instead of exporting CSVs.\nProblem: Customers query their own data (this skill's full scope); data lives in: A cloud warehouse; tenants: 420 (pooled); largest scanned table: 60 GB; scans a day per typical customer: 3\nConsumers: BI tools yes, data engineers no, embedded apps or agents no; arbitrary customer-authored SQL: no; worst-case query threatens the shared cluster: no; clients to support: Tableau, Power BI\nSchema exposed: a curated mart; contract enforcement: None - convention only; mart refresh: not given; engine speaks the Postgres wire protocol: no; connectivity planned: Your own certified JDBC/ODBC driver; credentials: Short-lived tokens (key-pair JWT or OAuth)\nPricing intent: tier; compute paid by: us (provider); customer volume spread: narrow; our cost is consumption-based: no; a compounding asset; hard ship date: no; as-of: 2026-11-01",
 "surface": "Views and policies (view | mechanism | identity | probed):\n- shared.events_v | app WHERE tenant_id = ? in the query service (an application-layer WHERE filter) | identity: CURRENT_ROLE() (CURRENT_ROLE or CURRENT_USER) | probed as a consumer: NO\n- shared.sessions_v | secure view joined to the entitlement table (engine policy (secure or dynamic view, row-level security)) | identity: current_account() (a share-aware identity function) | probed as a consumer: yes\n\nCompute handles (handle | timeout | concurrency | spend | who pays):\n- Reader account per customer | timeout yes | concurrency cap yes | spend cap NO | compute paid by us\n- Shared BI warehouse | timeout yes | concurrency cap NO | spend cap yes | compute paid by us\n\nPlanned schema changes (kind | object | how):\n- add | events.utm_campaign | additive",
 "carry": "Architecture: Zero-copy warehouse share - promote when Move to per-tenant compute isolation once a tenant's scans a day climb from today's 3 toward dashboard-refresh frequency on the 60 GB events table, once customer-authored SQL or agents are admitted, or once one tenant keeps tripping the Shared BI warehouse caps.\nSchema: Serve the curated Graphwren mart behind shared.events_v and shared.sessions_v, never the live operational schema.; The events and sessions tables are additive-only: new columns such as events.utm_campaign go in freely, while no column is renamed, retyped or dropped in place but added, deprecated, migrated and only then removed.; CI: A CI check diffs each mart migration against the published contract and fails the build on any rename, type change or drop, replacing today's convention-only doc.\nConnectivity: Your own certified JDBC/ODBC driver - Graphwren publishes a connection page each for Tableau and Power BI naming the URL template, its certified JDBC/ODBC driver and signed connector with the connector's expiry, TLS as required, and OAuth sign-in.\nCredentials: short-lived tokens (OAuth with PKCE, key-pair JWT) > managed secrets > static passwords, never issued; BI: Tableau and Power BI users sign in with OAuth with PKCE for a token scoped to their own tenant over required TLS, falling back to a key-pair service account only where a tool cannot refresh, with self-service revocation in Graphwren.\nPricing: Consumption or volume-tiered - event rows queried per month",
 "facts": "The page's checks (3 fail, 0 warn, 4 pass, 2 n/a); verdict: no customer gets a handle yet:\n- Problem, architecture and price | problem | pass | Customers query their own data (this skill's full scope)\n- Problem, architecture and price | consumers | na | BI analysts (Tableau, Power BI, Looker) - design for the most demanding: the BI analysts\n- Problem, architecture and price | architecture | na | Zero-copy warehouse share > Per-tenant compute isolation > Replicated copy or read endpoint - a compounding mandate moves the isolation rung up\n- Problem, architecture and price | pricing | pass | Consumption or volume-tiered - you pay the consumers' compute - the passthrough posture arrives with it, and a spend monitor on every account with it; a compounding mandate promotes consumption early - retrofitting a meter under a shipped flat price is a repricing\n- Isolation and schema | isolation | fail | shared.events_v: an application-layer WHERE filter - one missed filter leaks rows, CURRENT_ROLE/CURRENT_USER go NULL across a share, never probed as another tenant\n- Isolation and schema | schema | fail | a curated mart; the contract lives only in a doc - enforce it in CI; 1 planned change, every breaking one staged\n- Connectivity, governance and credentials | connectivity | pass | your own driver: accurate SQLSTATEs, signed per-tool connectors that expire, and first-line support routed\n\n[... 863 more characters cut from this display ...]"
}

Reply (long strings shortened for display - see the cuts below):

{
 "task": "contract",
 "summary": "Graphwren's contract for its curated event mart, with connection pages for Tableau and Power BI over its certified JDBC/ODBC driver and OAuth; no customer receives a connection until shared.events_v and the reader-account spend caps pass.",
 "contract": {
  "schema": "Customers read the curated Graphwren mart through shared.events_v and shared.sessions_v, never the live operational schema. Columns are only ever added, and a CI check against this published contract fails any mart migration that renames, [... 22 more characters cut from this display]",
  "quality": "Each view returns only the rows of the reading customer's own account, verified by a probe from another tenant after every policy change. Column names and types match this contract on every mart load.",
  "freshness": "The mart refresh interval is not set yet, so Graphwren promises no freshness window until it publishes one here.",
  "availability": "Graphwren proposes a monthly availability target for the Tableau and Power BI endpoints, to be published here once agreed; it is a proposed target, not a measured figure."
 },
 "breaking": [
  "a rename of any column or view in shared.events_v or shared.sessions_v",
  "a type change of any published column in the events or sessions tables",
  "a drop of a column or view that Tableau or Power BI workbooks already read"
 ],
 "changes": [],
 "pages": [
  {
   "client": "Tableau",
   "url": "jdbc:graphwren://{host}:{port}/{database}",
   "driver": "Graphwren's certified JDBC driver with its signed Tableau connector, renewed before the connector's expiry",
   "tls": "TLS required on every connection; unencrypted sessions are refused",
   "auth": "OAuth with PKCE issuing a short-lived token scoped to your own tenant, or a key-pair service account where a published workbook cannot refresh"
  },
  {
   "client": "Power BI",
   "url": "Server: {host}:{port}; Database: {database}",
   "driver": "Graphwren's certified ODBC driver with its signed Power BI connector, renewed before the connector's expiry",
   "tls": "TLS required on every connection; unencrypted sessions are refused",
   "auth": "OAuth with PKCE issuing a short-lived token scoped to your own tenant, or a key-pair service account where a scheduled refresh cannot sign in"
  }
 ],
 "caveats": [
  "No customer gets a connection until shared.events_v moves to a secure view on current_account() and passes its probe as another tenant.",
  "Every reader account needs a spend cap and the Shared BI warehouse a concurrency cap before launch.",
  "The planned events.utm_campaign column is additive, so it ships without staging.",
  "Freshness and availability stay open until Graphwren sets a refresh interval and agrees an availability target."
 ]
}

Cut from this display only (the saved reply and the check use the full text): contract.schema (259 characters, 237 shown).

What step 5 prints for it: Tableau jdbc:graphwren://{host}:{port}/{database} | Power BI Server: {host}:{port}; Database: {database}

The page's check: kept - nothing to flag

blueprint - A billing SaaS exposing its live Postgres

The page read it as: isolation and cost hold - fix the rest before GA; architectures replica, isolated; pricing consumption, flat, passthrough; connectivity postgres; 2 of 5 gates; failing checks: pricing, schema, credentials.

Input (facts is 2353 characters; the first 1399 are shown):

{
 "task": "blueprint",
 "product": "Product: Billwhistle\nWhat customers want and why: A subscription-billing SaaS; finance teams want psql and Power BI on their invoices and payments without waiting for exports.\nProblem: Customers query their own data (this skill's full scope); data lives in: An OLTP database only; tenants: 1800 (pooled); largest scanned table: 15 GB; scans a day per typical customer: 4\nConsumers: BI tools yes, data engineers yes, embedded apps or agents no; arbitrary customer-authored SQL: no; worst-case query threatens the shared cluster: no; clients to support: Power BI, psql\nSchema exposed: the live operational schema; contract enforcement: None - convention only; mart refresh: every 6 hours; engine speaks the Postgres wire protocol: yes; connectivity planned: A Postgres wire-protocol endpoint; credentials: A static password\nPricing intent: free; compute paid by: the customer; customer volume spread: wide; our cost is consumption-based: no; a compounding asset; hard ship date: no; as-of: 2026-11-01",
 "surface": "Views and policies (view | mechanism | identity | probed):\n- invoices | row-level security with FORCE ROW LEVEL SECURITY (engine policy (secure or dynamic view, row-level security)) | identity: tenant set from the verified token in app.tenant_id (tenant set server-side from a verified token) | probed as a consumer: yes\n- payments | row-level security with FORCE ROW LEVEL SECURITY (engine policy (secure or dynamic view, row-level security)) | identity: tenant set from the verified token in app.tenant_id (tenant set server-side from a verified token) | probed as a consumer: yes\n\nCompute handles (handle | timeout | concurrency | spend | who pays):\n- Read replica pool | timeout yes | concurrency cap yes | spend cap yes | compute paid by the customer\n\nPlanned schema changes (kind | object | how):\n- rename | invoices.amount to amount_due | IN PLACE - BREAKING\n- add | payments.settled_at | additive",
 "carry": "",
 "facts": "The page's checks (3 fail, 0 warn, 4 pass, 2 n/a); verdict: isolation and cost hold - fix the rest before GA:\n- Problem, architecture and price | problem | pass | Customers query their own data (this skill's full scope)\n- Problem, architecture and price | consumers | na | BI analysts (Tableau, Power BI, Looker); data engineers (psql, dbt, notebooks) - design for the most demanding: the BI analysts\n- Problem, architecture and price | architecture | na | Replicated copy or read endpoint > Per-tenant compute isolation - an OLTP-only stack has no warehouse to share from - the zero-copy rung is deleted, not demoted\n- Problem, architecture and price | pricing | fail | a free SQL feature is expensive to over-serve and gives up the price as a governance control\n- Isolation and schema | isolation | pass | 2 views engine-enforced and probed with zero foreign rows\n- Isolation and schema | schema | fail | the live operational schema is exposed - every internal refactor breaks a customer dashboard; the contract lives only in a doc - enforce it in CI; rename invoices.amount to amount_due in place - expand/contract instead\n- Connectivity, governance and credentials | connectivity | pass | A Postgres wire-protocol endpoint - every BI tool, psql and ORM connects through drivers they already ship\n- Connectivity, governance and credentials | cost | pass | 1 handle capped on time, concurrency and\n\n[... 954 more characters cut from this display ...]"
}

Reply:

{
 "task": "blueprint",
 "summary": "A read replica pool serving a curated mart over the Postgres wire protocol; isolation and cost caps already hold, but the mart, a CI-enforced contract, token credentials and a metered price must land before general availability.",
 "problem": {
  "scope": "Billwhistle's finance teams query their own invoices and payments from psql and Power BI instead of waiting for exports, which is this skill's full scope across 1800 pooled tenants.",
  "consumers": [
   "BI analysts in Power BI: scheduled dashboard refreshes over invoices and payments, about 4 scans a day on a table of up to 15 GB",
   "Data engineers in psql: the largest pulls of this product, wide reads over invoices and payments"
  ]
 },
 "architecture": {
  "rung": "replica",
  "promote_when": "Move to per-tenant compute isolation once customer-authored SQL or agents are admitted, once one tenant's queries keep tripping the Read replica pool caps, or once the worst psql pull starts to slow the pool for the other 1800 tenants.",
  "why": "Billwhistle's data lives only in an OLTP database, which deletes the share rung, and with no arbitrary SQL and no query threatening the cluster at about 4 scans a day, a read replica ranks ahead of per-tenant isolation."
 },
 "isolation": [
  {
   "view": "invoices",
   "policy": "Keep row-level security with FORCE ROW LEVEL SECURITY on invoices, carried onto the curated mart table so the table owner is filtered too.",
   "identity": "tenant set server-side from the verified token in app.tenant_id",
   "probe": "Rerun the passing probe as another tenant against invoices after every policy change or mart rebuild, requiring zero foreign rows."
  },
  {
   "view": "payments",
   "policy": "Keep row-level security with FORCE ROW LEVEL SECURITY on payments, carried onto the curated mart table, including the new payments.settled_at column.",
   "identity": "tenant set server-side from the verified token in app.tenant_id",
   "probe": "Rerun the passing probe as another tenant against payments after every policy change or mart rebuild, requiring zero foreign rows."
  }
 ],
 "schema": {
  "layer": "Serve a curated invoices and payments mart on the replica, refreshed every 6 hours, in place of the live operational schema so an internal refactor no longer breaks a customer dashboard.",
  "rule": "The invoices and payments mart is additive-only: payments.settled_at goes in freely, but invoices.amount is not renamed in place - amount_due is added beside it, amount is deprecated, dashboards migrate, and amount is removed last.",
  "ci": "A CI check compares each migration with the published mart contract and fails the build on any rename, type change or drop, which would have stopped the in-place rename of invoices.amount."
 },
 "connectivity": {
  "rung": "postgres",
  "setup": "Billwhistle publishes a connection page each for Power BI and psql with a URL template such as postgresql://{host}/{database}?sslmode=verify-full, the stock Postgres driver each already ships, TLS as required, and token-based sign-in."
 },
 "governance": [
  {
   "handle": "Read replica pool",
   "timeout": "keep its existing statement timeout",
   "concurrency": "keep its existing concurrency cap, moving a tenant that keeps tripping it to its own compute rather than raising it for everyone",
   "spend": "keep its existing spend cap on the customer-paid compute"
  }
 ],
 "credentials": {
  "order": [
   "short-lived tokens (key-pair JWT, OAuth with PKCE)",
   "managed secrets",
   "static passwords, retired"
  ],
  "bi": "Power BI signs in with OAuth with PKCE for a token scoped to the customer's tenant over required TLS, and psql users fetch a short-lived tenant token from Billwhistle, with self-service revocation replacing the static password.",
  "service": "Service users and data engineers' scheduled jobs move to key-pair JWTs scoped to their tenant, with no credential ever written into a connection string."
 },
 "pricing": {
  "shape": "consumption",
  "meter": "invoices and payments queried per month",
  "why": "A free feature gives up the price as Billwhistle's governance control, customer volumes span a wide range and the compounding mandate promotes the meter early, so SQL access becomes a paid add-on metered on invoices and payments."
 },
 "caveats": [
  "The in-place rename of invoices.amount to amount_due must not ship; stage it through amount_due beside amount.",
  "The stated pricing intent was free; the page ranks consumption first and a free SQL feature is ruled out.",
  "The static password must be retired before general availability."
 ]
}

What step 5 prints for it: replica: A read replica pool serving a curated mart over the Postgres wire protocol; isolation and cost caps already hold, but the mart, a CI-enforced contract, token credentials and a metered price must land before general availability.

The page's check: kept - nothing to flag

Build the input yourself

Load the engine in node (global.window = global; require("./qgkit.js"); require("./qgreply.js")), then:

const K = QueryKit, R = QueryReply;
const f = K.form(Object.assign(K.blank(), { name: "Acme", store: "warehouse", clients: "Tableau, psql",
  views: "orders_v | secure view joined to the entitlement table | current_account() | yes",
  handles: "BI warehouse | yes | yes | no | provider" }));
// f.arch and f.price (the ranked rungs and shapes, with moves), f.protoRec, f.viewRows, f.handleRows, f.breakingPlanned, f.gate
// K.probeSql(f), K.capsCsv(f) - the isolation probe and the caps table
const b = K.buildInput(f, "blueprint");                      // or "contract" (R.laneForm(form, "contract", blueprint) fills carry)
const wire = K.toWire(b.input);                              // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "blueprint");
// R.check(reply, b)                                         // {status: kept or flagged, notes}

Get a token for the steps below from /tokens.html.

Step 1 — a tiny client helper

One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="YOUR_TOKEN"   # from https://query-gate.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"query-gate"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.

# input.json: the run input object - {"task": "blueprint", "product": "Product: Graphwren
What customers want and why: ...", "surface": "Views and policies (view | mechanism | identity | probed):
- shared.events_v | ...", "carry": "", "facts": "The page's checks (...); verdict: no customer gets a handle yet:
- Problem, architecture and price | problem | pass | ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses query-gate:<task>:<inputHash>:a<attempt> - the lane is part of the key, retry_note is left out of the hash - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A blueprint reply carries summary, problem, architecture, isolation, schema, connectivity, governance, credentials, pricing and caveats; a contract reply carries summary, contract, breaking, changes, pages and caveats. The code below prints the gist of either: the architecture rung and the summary, or the connection URL template for each client.

KEY="query-gate:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r 'if .task == "contract" then (.pages[] | "\(.client): \(.url)") 
       else "\(.architecture.rung): \(.summary)" end' reply.json

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: query-gate:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json