webapi API reference
A JSON-API conventions layer over the bundled web module. web is the HTTP framework - routing, :param captures, middleware, cookies, sessions, CORS, ETag; webapi adds the pieces every JSON API re-implements by hand on top of it: a uniform error envelope, request validation (reusing validate), versioned route mounting, pluggable bearer auth and rate limiting (the app supplies a verifier and a counter as func values, so this module depends on no jwt / store), content negotiation, and pagination.
It is a value-semantic builder finished once, before the server runs: webapi.new() -> add mounts, an authenticator, routes with a Spec -> webapi.install($api, $app, apiGuard). Routes and the authenticator / limiter are func values; the one small guard shim in the entry program exists only to bind the built Api into web's middleware chain (Jennifer has no closures yet, so the shim captures $api through a top-level binding):
func apiGuard(ctx as web.Context) { return webapi.guard($api, $ctx); }The Spec-evaluation core (webapi.evaluate) is a pure function of (Spec, Identity, data), testable without a server. Needs the default jennifer binary (web needs net); unavailable under jennifer-tiny.
Import with import "webapi.j" as webapi;. See the webapi guide for prose and examples.
Functions
webapi.alias(a as Api, version as int)
Additionally serve version at the bare root, so /v1/deck is also reachable as /deck.
Parameters
a{Api}- the builder to extendversion{int}- the version to also mount at the root
Returns {Api} - a new Api with the root alias added
webapi.authenticator(a as Api, handler as func)
Set the authenticator: an entry-program func(token as string) -> webapi.Identity value. The module calls it for a Bearer route; it never learns how a token is verified.
Parameters
a{Api}- the builder to extendhandler{func}- the verifier func value
Returns {Api} - a new Api with the authenticator set
webapi.delete(a as Api, pattern as string, handler as func, spec as Spec)
Register a DELETE route with a Spec.
Parameters
a{Api}- the builder to extendpattern{string}- the route patternhandler{func}- the entry-program handler func valuespec{Spec}- the route's specification
Returns {Api} - a new Api with the route added
webapi.denied(ctx as web.Context, message as string)
Send a 403 error envelope.
Parameters
ctx{web.Context}- the request contextmessage{string}- the error message
webapi.deprecate(a as Api, version as int, sunset as string)
Mark every mount of version deprecated with a sunset date, surfaced in the discovery document.
Parameters
a{Api}- the builder to extendversion{int}- the version to deprecatesunset{string}- the sunset date (e.g. an ISO date)
Returns {Api} - a new Api with the version marked deprecated
webapi.discovery(a as Api, registry as string, specVersion as string)
Build the discovery document from the route table, so the advertised versions and features cannot drift from what is actually served: {"registry", "spec", "apis": [{version, basePath, deprecated, sunset}], "features": [name, ...]}. features is the set of route feature labels.
Parameters
a{Api}- the built APIregistry{string}- the registry / service name to advertisespecVersion{string}- the spec version string to advertise
Returns {json.Value} - the discovery document
webapi.evaluate(spec as Spec, identity as Identity, data as map of string to string)
The pure core: decide whether a request bearing identity and data may proceed under spec, without touching the engine. Returns proceed=true, or proceed=false with the status / message (and validation failures) to answer. Order: auth (401) -> scopes (403) -> validation (422). Rate limiting is applied by the guard (it is stateful), not here.
Parameters
spec{Spec}- the route specificationidentity{Identity}- the authenticated caller (ok=false when none)data{map of string to string}- the request data to validate
Returns {Decision} - proceed, or a status + message to answer
webapi.fail(ctx as web.Context, status as int, message as string)
Send the uniform error envelope {"error": message} at status.
Parameters
ctx{web.Context}- the request contextstatus{int}- the HTTP statusmessage{string}- the error message
webapi.failWith(ctx as web.Context, status as int, message as string, failures as list of validate.Failure)
Send the error envelope with field-level failures detail: {"error": message, "failures": [{field, rule, message}, ...]}.
Parameters
ctx{web.Context}- the request contextstatus{int}- the HTTP statusmessage{string}- the error messagefailures{list of validate.Failure}- the per-field failures
webapi.feature(a as Api, feature as string)
Set an explicit discovery feature label for the most recently added route (defaults to "METHOD /pattern"), so the discovery document reads meaningfully.
Parameters
a{Api}- the builder whose last route to labelfeature{string}- the feature name
Returns {Api} - a new Api with the last route's feature set
Throws
{Error}- kind "webapi" when there is no route to label
webapi.formData(ctx as web.Context)
The form body (application/x-www-form-urlencoded) as a string map.
Parameters
ctx{web.Context}- the request context
Returns {map of string to string} - the form fields
webapi.get(a as Api, pattern as string, handler as func, spec as Spec)
Register a GET route with a Spec.
Parameters
a{Api}- the builder to extendpattern{string}- the route pattern (:paramcaptures allowed)handler{func}- the entry-program handler func valuespec{Spec}- the route's specification
Returns {Api} - a new Api with the route added
webapi.guard(a as Api, ctx as web.Context)
The Spec-enforcing middleware body. Wire it from an entry-program shim named in install: func apiGuard(ctx) { return webapi.guard($api, $ctx); }. It matches the request to its route, authenticates, checks scopes, validates, and rate-limits, answering the request and returning false on any failure. A request that matches no API route passes through (returns true).
Parameters
a{Api}- the built APIctx{web.Context}- the request context
Returns {bool} - true to proceed to the handler, false when it has answered
webapi.identity(a as Api, ctx as web.Context)
Inside a handler: the authenticated identity for this request (re-derived via the authenticator). Returns a zero Identity (ok: false) for a public route or an absent credential.
Parameters
a{Api}- the built APIctx{web.Context}- the request context
Returns {Identity} - the caller's identity
webapi.install(a as Api, app as web.App, guard as func)
Register every route on the web.App, once per mount path (via web.mount), and wire the Spec-enforcing guard as a before middleware. guard must be an entry-program func value that calls webapi.guard($api, $ctx); the shim is only needed to bind the Api (Jennifer has no closures yet for the guard to capture it directly).
Parameters
a{Api}- the built APIapp{web.App}- the web router to register ontoguard{func}- the entry-program guard shim func value
Returns {web.App} - a new App with the routes and guard installed
webapi.jsonData(ctx as web.Context, fields as list of string)
The named top-level JSON body fields, flattened to strings (a nested or array-valued field is omitted; read those with web.bodyJson).
Parameters
ctx{web.Context}- the request contextfields{list of string}- the JSON keys to read
Returns {map of string to string} - the present scalar fields
webapi.limiter(a as Api, handler as func)
Set the rate limiter: an entry-program func(key as string, limit as int) -> bool value (true = allowed). Keyed on the identity's subject when authenticated, else the remote address.
Parameters
a{Api}- the builder to extendhandler{func}- the limiter func value
Returns {Api} - a new Api with the limiter set
webapi.mount(a as Api, version as int, path as string)
Serve this version's routes under path, returning a new Api. A version may be mounted at several paths (mount then alias); each is served by install.
Parameters
a{Api}- the builder to extendversion{int}- the version label (surfaced in discovery)path{string}- the base path to mount under
Returns {Api} - a new Api with the mount added
webapi.new()
A fresh, empty API builder.
Returns {Api} - an API with no routes, mounts, or auth
webapi.notFound(ctx as web.Context, message as string)
Send a 404 error envelope.
Parameters
ctx{web.Context}- the request contextmessage{string}- the error message
webapi.onError(app as web.App, handler as func)
Register a web.onError envelope handler on app, so an uncaught throw becomes a 500 in the same shape rather than a bare engine error. Pair with install; the original error is still logged by web.
Parameters
app{web.App}- the router to extendhandler{func}- an entry-programfunc(e as Error)value that callswebapi
Returns {web.App} - a new App with the error handler set
webapi.page(ctx as web.Context, defaultLimit as int, maxLimit as int)
Parse and clamp the offset / limit query parameters into a Page. limit defaults to defaultLimit and is clamped to [1, maxLimit]; offset is clamped to >= 0, so a client cannot ask for the whole table or a bad window.
Parameters
ctx{web.Context}- the request contextdefaultLimit{int}- the limit when none is suppliedmaxLimit{int}- the largest allowed limit
Returns {Page} - the clamped window
webapi.patch(a as Api, pattern as string, handler as func, spec as Spec)
Register a PATCH route with a Spec.
Parameters
a{Api}- the builder to extendpattern{string}- the route patternhandler{func}- the entry-program handler func valuespec{Spec}- the route's specification
Returns {Api} - a new Api with the route added
webapi.post(a as Api, pattern as string, handler as func, spec as Spec)
Register a POST route with a Spec.
Parameters
a{Api}- the builder to extendpattern{string}- the route patternhandler{func}- the entry-program handler func valuespec{Spec}- the route's specification
Returns {Api} - a new Api with the route added
webapi.public()
A zero Spec: a public JSON route with no scopes, rules, or rate limit. Keeps an unauthenticated route a one-liner: webapi.get($a, "/x", h, webapi.public()).
Returns {Spec} - the zero specification
webapi.put(a as Api, pattern as string, handler as func, spec as Spec)
Register a PUT route with a Spec.
Parameters
a{Api}- the builder to extendpattern{string}- the route patternhandler{func}- the entry-program handler func valuespec{Spec}- the route's specification
Returns {Api} - a new Api with the route added
webapi.queryData(ctx as web.Context, fields as list of string)
The named query parameters as a string map (absent names omitted).
Parameters
ctx{web.Context}- the request contextfields{list of string}- the parameter names to read
Returns {map of string to string} - the present query values
webapi.sendJson(ctx as web.Context, status as int, value as json.Value)
Send an envelope-consistent JSON response.
Parameters
ctx{web.Context}- the request contextstatus{int}- the HTTP statusvalue{json.Value}- the response body
webapi.sendPage(ctx as web.Context, items as json.Value, p as Page, total as int)
Send a paged list response: {"items": [...], "offset", "limit", "total"}. items must already be a json.Value list (the page slice the handler built).
Parameters
ctx{web.Context}- the request contextitems{json.Value}- the page's items, as a JSON listp{Page}- the page windowtotal{int}- the total item count across all pages
webapi.unauthorized(ctx as web.Context, message as string)
Send a 401 error envelope with a WWW-Authenticate: Bearer header.
Parameters
ctx{web.Context}- the request contextmessage{string}- the error message
webapi.validated(a as Api, ctx as web.Context)
Inside a handler: the request data the route's rules were checked against (query + body scalars). The guard already validated it, so a handler that got this far can trust it. Takes the Api because Jennifer has no per-request state to stash it in.
Parameters
a{Api}- the built APIctx{web.Context}- the request context
Returns {map of string to string} - the request data
webapi.wants(ctx as web.Context)
What the caller wants from the Accept header: "json" or "html". JSON is the default when the header is absent or * / *.
Parameters
ctx{web.Context}- the request context
Returns {string} - "json" or "html"
Structs
webapi.Api
The built API description: its routes, mount points, and the authenticator / limiter func values. Value-semantic; every builder returns a fresh Api. The authenticator / limiter are entry-program func values, called through their home interpreter so they resolve their own imports and can construct a webapi.Identity across the module boundary.
| Field | Type | Description |
|---|---|---|
routes | list of RouteDef | the registered routes |
mounts | list of Mount | the version mount points |
authenticator | func | func(token as string) -> Identity (only when hasAuthenticator) |
hasAuthenticator | bool | whether an authenticator is set (else every route is public) |
limiter | func | func(key as string, limit as int) -> bool (only when hasLimiter) |
hasLimiter | bool | whether a rate limiter is set (else no limiting) |
webapi.Decision
The outcome of evaluating a Spec against a request: proceed, or halt with the status + message (and any validation failures) to answer. The return of the pure webapi.evaluate.
| Field | Type | Description |
|---|---|---|
proceed | bool | true if the request may run the handler |
status | int | the HTTP status to answer when not proceeding |
message | string | the error message to answer |
failures | list of validate.Failure | the field-level failures (validation only) |
webapi.Identity
The result of authenticating a request. ok is false when the credential was absent or rejected; the other fields describe the caller when it is true.
| Field | Type | Description |
|---|---|---|
ok | bool | whether the credential was accepted |
subject | string | a stable identifier for the caller |
display | string | a human-readable label |
scopes | list of string | the permissions this caller holds |
webapi.Mount
One version mount point. Exported only because Api.mounts exposes a list of them; programs build these through webapi.mount / alias, never directly.
| Field | Type | Description |
|---|---|---|
version | int | the version label |
path | string | the base path it serves under ("" = root) |
sunset | string | the deprecation date ("" = not deprecated) |
webapi.Page
A page window parsed from a request's offset / limit query parameters, clamped by webapi.page.
| Field | Type | Description |
|---|---|---|
offset | int | the zero-based start index |
limit | int | the page size |
webapi.RouteDef
One registered route. Exported only because Api.routes exposes a list of them; programs build these through webapi.get / post / ..., never directly.
| Field | Type | Description |
|---|---|---|
method | string | the HTTP method |
pattern | string | the un-prefixed route pattern |
handler | func | the entry-program handler func value |
feature | string | the discovery feature label (defaults to "METHOD /pattern") |
spec | Spec | the route's specification |
webapi.Spec
The metadata attached to a route: what it needs and what it makes. A zero Spec (from webapi.public()) is an unauthenticated JSON route with no rules.
| Field | Type | Description |
|---|---|---|
summary | string | a one-line human description (used in discovery) |
auth | Auth | the authentication requirement |
scopes | list of string | the permissions the identity must hold |
rules | map of string to list of validate.Rule | per-field request validation |
rateLimit | int | allowed requests per identity per window (0 = unlimited) |
produces | Produces | the response content type policy |
Enums
webapi.Auth
How a route authenticates. None is public (no credential); Bearer requires an Authorization: Bearer <token> the authenticator accepts.
webapi.Produces
What a route produces, driving content negotiation. Json always answers JSON, Html always HTML, Negotiate picks from the request's Accept.