Skip to main content

In Another Language: The Runtime Protocol

The application runtime — reading who signed in and querying QueryMesh — exists ready-made in the TypeScript SDK (@nnumbers/zero/app and @nnumbers/zero/react). The published Go SDK is the management API client, not the runtime; an application in another language follows the runtime protocol, the contract between the platform and the application code, described on this page.

The Identity of Who Signed In​

On every request that arrives through the edge, the platform puts a compact JWS in X-NNumbers-Identity — header alg: ES256, typ: nn-identity+jwt, kid — with the payload below. With no authenticated person, the header does not come. A value sent by the browser is removed before anything else.

The application always checks:

  1. the signature, with the broker's public keys (NNUMBERS_IDENTITY_JWKS; and NNUMBERS_BROKER_URL/v1/jwks when an unknown kid arrives) — keep the keys for a few minutes and do not fetch them again on every request;
  2. iss equal to NNUMBERS_IDENTITY_ISSUER;
  3. aud equal to NNUMBERS_APP_ID — an identity handed to another application is not valid here;
  4. org equal to NNUMBERS_ORG_ID;
  5. nbf and exp, with up to 60 s of clock tolerance — the identity is valid for 5 minutes.

A header whose signature was not checked proves nothing: there are paths that reach the application without going through the edge (the same namespace, the network permissions, the internal entry). Without the header, the request is anonymous; with the header and any check failing, refuse it — never treat it as a person.

ClaimWhat it is
iss, aud, jtithe issuer, the application of this identity and its identifier
subthe person's subject in the organization's IAM
org, org_slugthe organization
iamthe issuer of the organization's IAM
sidthe session in the platform — an opaque reference, not the cookie
name, email, email_verified, preferred_usernamewhen the account has them
iat, nbf, expthe validity; exp is 5 minutes after iat

The Reserved Paths​

The paths under /.nnumbers/ are served by the platform on the application's own address — they never reach the application code:

Method and pathResponse
GET /.nnumbers/auth/login?return_to=<path>302 to the organization IAM sign-in
GET /.nnumbers/auth/callback302 back to return_to, with the session created
POST /.nnumbers/auth/logout303 to /, with the application session ended; requires Origin from the address itself
GET /.nnumbers/auth/session203 {"user": {...} | null, "organization": {"id", "slug"}}
GET /.nnumbers/v1/context203 {"app": {"id"}, "organization": {"id", "slug"}, "auth": {"login_path", "logout_path"}}

return_to only accepts a path of the address itself (starts with /, not with //). The JSON comes with 203, not 200, and Cache-Control: no-store — that is how the edge relays the platform's response, and the browser reads 203 as success.

Signing out is the application's: it ends its session, and the organization's single sign-on continues — signing in again does not ask for a password while it lasts.

The Broker, from Inside the Cluster​

NNUMBERS_BROKER_URL is reachable only from inside the cluster. The application uses it for the two operations:

Method and pathWhat it does
GET /v1/jwksthe public keys that sign the identity
POST /v1/querymesh/querywith the request's X-NNumbers-Identity, runs the query in the organization's QueryMesh on behalf of that person and returns columns and rows

The query carries the SQL and, optionally, the row cap:

POST {NNUMBERS_BROKER_URL}/v1/querymesh/query
X-NNumbers-Identity: <the request's identity, already checked>
Content-Type: application/json

{"sql": "SELECT name, regionkey FROM tpch.tiny.nation", "max_rows": 1000}
FieldWhat it is
sqlthe query (required); never goes to log
max_rowsthe cap on rows brought back, from 1 to 100,000 (default 100,000); above it, the query is cancelled in QueryMesh

The QueryMesh address is the one the organization administrator configured (zero orgs integrations set querymesh --url https://…); the application does not choose it. Who queries is the platform, with the person's session token, directly — no token exchange —, and the token does not leave the platform. With no person there is no query: no path falls back to an "application" credential.

Success returns the columns, with the Trino type, and the rows, in column order:

{
"columns": [{"name": "name", "type": "varchar"}, {"name": "regionkey", "type": "bigint"}],
"rows": [["BRAZIL", 1]]
}

Refusals come as {"code", "message"}:

StatusCodeWhenWhat to do
400VALIDATION_FAILEDno SQL, or max_rows outside 1–100,000fix the request and repeat
401USER_SESSION_REQUIREDno identity, or the session of who signed in endedtake the person to sign-in and repeat
401IDENTITY_ASSERTION_INVALIDthe identity does not check out (signature, issuer, validity, organization, application)use the identity the platform hands over; never treat it as a person
409ORGANIZATION_INTEGRATION_NOT_CONFIGUREDthe organization has no QueryMesh configured, or it is offthe organization administrator configures it
409QUERYMESH_NOT_ENABLEDthe application has not turned the querymesh capability onzero services enable querymesh <service> — takes effect immediately
502QUERYMESH_IDENTITY_REJECTEDQueryMesh did not accept the token of who signed in (issuer, audience or scope)when the realm lacks the scope QueryMesh requires, the platform administration fixes it — signing in again does not help
403QUERYMESH_ACCESS_DENIEDthe organization's data policies denied the query to this personask the data administrator for access
422 or 502QUERYMESH_QUERY_FAILED422: QueryMesh refused the query or the result went past the cap; 502: QueryMesh answered outside the protocol or asked to continue outside the configured addresscheck the query and repeat; on 502, talk to whoever administers the QueryMesh
503QUERYMESH_UNAVAILABLEQueryMesh did not answer or is busy, or the platform is at its query limitrepeat in a moment
503APPLICATION_IDENTITY_NOT_READYthe broker has no keys available right nowrepeat; if it persists, zero services doctor organization-iam <service>

What the Application Never Receives​

A client secret (it does not exist: the login client is public), a refresh token, the IAM access token — not even for QueryMesh, because who queries is the platform —, and no means of choosing the person.

Next Steps​