Skip to content
Jennifer Programming Language

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 extend
  • version {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 extend
  • handler {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 extend
  • pattern {string} - the route pattern
  • handler {func} - the entry-program handler func value
  • spec {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 context
  • message {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 extend
  • version {int} - the version to deprecate
  • sunset {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 API
  • registry {string} - the registry / service name to advertise
  • specVersion {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 specification
  • identity {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 context
  • status {int} - the HTTP status
  • message {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 context
  • status {int} - the HTTP status
  • message {string} - the error message
  • failures {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 label
  • feature {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 extend
  • pattern {string} - the route pattern (:param captures allowed)
  • handler {func} - the entry-program handler func value
  • spec {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 API
  • ctx {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 API
  • ctx {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 API
  • app {web.App} - the web router to register onto
  • guard {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 context
  • fields {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 extend
  • handler {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 extend
  • version {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 context
  • message {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 extend
  • handler {func} - an entry-program func(e as Error) value that calls webapi

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 context
  • defaultLimit {int} - the limit when none is supplied
  • maxLimit {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 extend
  • pattern {string} - the route pattern
  • handler {func} - the entry-program handler func value
  • spec {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 extend
  • pattern {string} - the route pattern
  • handler {func} - the entry-program handler func value
  • spec {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 extend
  • pattern {string} - the route pattern
  • handler {func} - the entry-program handler func value
  • spec {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 context
  • fields {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 context
  • status {int} - the HTTP status
  • value {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 context
  • items {json.Value} - the page's items, as a JSON list
  • p {Page} - the page window
  • total {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 context
  • message {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 API
  • ctx {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.

FieldTypeDescription
routeslist of RouteDefthe registered routes
mountslist of Mountthe version mount points
authenticatorfuncfunc(token as string) -> Identity (only when hasAuthenticator)
hasAuthenticatorboolwhether an authenticator is set (else every route is public)
limiterfuncfunc(key as string, limit as int) -> bool (only when hasLimiter)
hasLimiterboolwhether 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.

FieldTypeDescription
proceedbooltrue if the request may run the handler
statusintthe HTTP status to answer when not proceeding
messagestringthe error message to answer
failureslist of validate.Failurethe 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.

FieldTypeDescription
okboolwhether the credential was accepted
subjectstringa stable identifier for the caller
displaystringa human-readable label
scopeslist of stringthe 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.

FieldTypeDescription
versionintthe version label
pathstringthe base path it serves under ("" = root)
sunsetstringthe deprecation date ("" = not deprecated)

webapi.Page

A page window parsed from a request's offset / limit query parameters, clamped by webapi.page.

FieldTypeDescription
offsetintthe zero-based start index
limitintthe 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.

FieldTypeDescription
methodstringthe HTTP method
patternstringthe un-prefixed route pattern
handlerfuncthe entry-program handler func value
featurestringthe discovery feature label (defaults to "METHOD /pattern")
specSpecthe 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.

FieldTypeDescription
summarystringa one-line human description (used in discovery)
authAuththe authentication requirement
scopeslist of stringthe permissions the identity must hold
rulesmap of string to list of validate.Ruleper-field request validation
rateLimitintallowed requests per identity per window (0 = unlimited)
producesProducesthe 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.