Login is the default and stays the default. An app is reachable without a session only when both of these are true:
1

The manifest asks for it

visibility: public with requireLogin: false.
2

An owner has enabled it for the organization

allowPublicApps is off in the default policy, so a public manifest is refused at plan time until someone turns it on.
An app is never accidentally public.

The two fields

visibility and requireLogin describe two different audiences, and the pair is what the gateway acts on: Only the last row serves anonymous requests, and only a public app may set requireLogin: false.

Subjects and roles

A subject has exactly one of user, group, or serviceAccount, plus an optional role. Roles are admin, editor, user, or viewer; the default is user. metadata.owner is lowercased during normalization and always added as an app admin, so you cannot lock yourself out of your own app with a manifest. Bindings can also be changed without a redeploy:
The manifest and the CLI write to the same binding table. A redeploy reconciles bindings the manifest declares; grants made out of band for subjects the manifest never named are yours to manage.

What the handler sees

context.user carries a verified ID, email, app role, groups, and assurance — or is null.
An anonymous request carries no identity token at all. context.user is null rather than a synthetic guest user, precisely so a handler cannot mistake one for a real person. Always branch on null before reading .email.
A visitor who does hold a session keeps their own identity, role, and bindings on a public app. Anonymity is the fallback, not a downgrade applied to everyone.

Previews are never public

Only the production environment opens to the world. A preview keeps the login redirect even when production serves anonymously, because visibility is read from the production revision and a preview is unreviewed work at a guessable hostname.

Limits on anonymous traffic

A public app’s volume is not bounded by headcount, so two organization-policy limits apply to it:
maxMonthlyRequestsPerApp is off by default so an existing organization keeps one pooled budget. Set it when you make an app public. Without it, a scraped public app can spend the whole organization’s monthly request quota and take the organization’s internal apps down with it.

Auditing

Anonymous traffic is metered like any other request but is not written to the audit log one row per request — there is no identity to record. Denials, deploys, and every control-plane action are audited as before.

Widening visibility is an approval gate

Moving private → organization → public in production triggers VISIBILITY_WIDENED. The deployment pauses in awaiting_approval rather than failing. See Approvals.

What the gateway does on every request

1

Resolve

Hostname or path prefix to an environment.
2

Authenticate

OIDC authorization code + PKCE, or local password. Skipped only for the anonymous public case.
3

Authorize

Evaluate bindings and roles against the revision’s access block.
4

Strip

Remove any client-supplied Tiny-* platform header.
5

Mint

Issue a short-lived identity token bound to this app’s audience.
6

Wake

Resume the deployment if it was sleeping.
7

Proxy, meter, audit

Forward, then record usage and the audit event.