The HTTP API surface
One binary, one surface
Section titled “One binary, one surface”A single Henceforth server process serves everything over HTTP: the versioned API, the published contract artifacts that describe it, and the optional Studio, App, and Login experience surfaces. There is no separate API gateway or docs server – one binary, one listening socket, one surface.
Versioned API under /v1/
Section titled “Versioned API under /v1/”Every route in the API lives under a single version prefix, /v1/,
applied once at router composition rather than edited into individual
route strings. A prefix scoped per family instead – /v1/data/...
alongside /v1/metadata@main/... – would collide with the
metadata@{branchId} first path segment that cart-branch routes already
use, so the version has to sit outside that segment, at the root.
A fixed set of paths is exempt from the prefix, and nothing else is:
/health,/readyand/status– operational probes, not contract./healthis liveness;/readyanswers200only on the active instance, so a standby process answers503there./.well-known/jwks.jsonand/.well-known/henceforth/schema/{file}– RFC 8615 well-known URIs are unversioned by definition./app/,/studio/,/login/– the SPA mounts. These are experience surfaces, not API./studio/exists only in a build with thestudiocargo feature, which is on by default.- The bare redirects into those mounts:
/and/loginredirect to/login/,/appredirects to/app/, and (in a build with Studio)/studioredirects to/studio/.
GET /v1/surfaces is API, not a surface: it is the transport-neutral surface
hub, the list of surfaces the caller may use, which the sign-in page and
hf surface list both read (see
Experience surfaces).
An unprefixed request whose first path segment is one of the routes the
binary served, unprefixed, before the version prefix landed (auth,
data, metadata, iam, hfx, surfaces, plan, apply) is a hard
cut, not a soft fallback: it answers a typed 404 carrying the error
code API_VERSION_REQUIRED, the same structured error envelope every
other API error uses – it is very likely an old client that never
learned about the prefix. Every other unmatched path, including a
typo’d version segment (/v2/data, /vl/data) or a route that never
existed (/dashboard), gets a plain NOT_FOUND instead: a blanket
API_VERSION_REQUIRED for any unmatched path was rejected (#915
Amendment 5) because it would mislabel an ordinary typo as a version
problem. Source: #915 Amendment 5,
docs/standards/api-conventions.md.
The published contract
Section titled “The published contract”A running server publishes the exact documents it was built from, not a live introspection of its current state:
/.well-known/henceforth/openapi.json– the committed OpenAPI document./.well-known/henceforth/schema/{type}.json– the committed JSON Schema for each metadata document type.
Both are public, need no credential, and are cached for an hour. A module
pins the contract version it targets through its manifest’s
platform.apiVersion, and the compiler rejects a module built against a
version the running server does not support – the module and the server
it runs against never silently disagree about the shape of the contract
between them.
See Fetch the API contract from a running server for the exact requests.
Credentials
Section titled “Credentials”Henceforth’s own credential API – issuing the JWTs that authenticate
every other request – lives under /v1/auth/, and the public keys used
to verify those JWTs are published at /.well-known/jwks.json. There is
no refresh-token route in v0: a JWT is valid until it expires
(tokenExpiryMinutes in Settings), and getting a new one means
authenticating again with a password or an SSH signature. A session code
does not do that: POST /v1/auth/session itself requires a valid JWT, and
the code it returns only delegates that existing session to a browser:
GET /v1/auth/session/exchange shows which account the code signs in as, and
POST /v1/auth/session/redeem consumes it. It cannot revive an expired one.
The root segment /oauth2/ is reserved, unused today, for a possible
future OAuth 2.0 / OpenID Connect authorization server discoverable via
RFC 8414. /v1/auth/ is a credential API that borrows OAuth’s vocabulary
(tokens, grants, expiry) without implementing an OAuth authorization
server’s protocol – a vocabulary choice, not a protocol commitment, and
the reason it lives under the version prefix like any other route rather
than at the reserved root.
Cross-origin access
Section titled “Cross-origin access”Cross-origin access is off by default: a server with no
api.cors.allowedOrigins configured emits no access-control-* response
header at all, on any route.
Setting api.cors.allowedOrigins installs one policy that covers every
response the server produces – there is no per-route CORS
configuration. Matching is exact on scheme, host, and port: no wildcard
origin, and no wildcard subdomain. http:// is accepted only for
loopback hosts, because browsers treat a loopback origin as potentially
trustworthy even without TLS; every other origin must use https://.
There is no credentialed CORS mode yet. A bearer token travels in the
Authorization header, which CORS’s credentialed-request rules do not
gate; a cookie-based session, which those rules would gate, is a later
decision.
Configuration
Section titled “Configuration”settings.json5 is read once, at server startup. Changing it – adding
an allowed origin, changing a token’s expiry – takes effect only after a
restart.
Reference
Section titled “Reference”- Settings – every
settings.json5key, includingapi.cors.allowedOriginsand its validation rules. - HTTP API reference – every route this surface serves, generated from the committed OpenAPI document.
- Error codes – every code the typed error
envelope can carry, including
API_VERSION_REQUIRED.