Client examples in API documentation
For browser sign-in to the documentation, see OAuth sign-in.
The Ruby server renders examples from the current user's API description. JavaScript and TypeScript examples cover callback and async clients; Python examples cover sync and async clients. These clients need no server configuration. Ruby, PHP, CLI, curl and HTTP examples are also available. A sample that a client cannot represent shows a limitation instead of dropping input or inventing a successful result.
Examples default to Simple. Action examples assume an initialized and, when
needed, authenticated api; initialization and authentication examples are on
separate pages. The Examples selector stays visible and offers With error
handling when the current page has examples with additional error handling
for that client. Examples without a distinct error-handling form remain Simple.
Both levels preserve the same sample values, path arguments and operation
semantics. Extra setup alone does not create another choice; built-in
limitations have one Simple panel.
See the documentation UI guide for navigation and saved
display preferences.
Examples use placeholder credentials: user / secret, example-token and
example-access-token. Replace them with your application's values. Renderers
use these public literals rather than reading credentials from the environment,
current user, profile settings or private defaults.
Token-login and challenge inputs use the authorized description's field names, types and finite machine-readable choices. Required inputs remain present, and the lifetime is explicitly fixed. Custom validation may require different values; an unrepresentable required field produces a limitation. Continuation names and inputs follow the advertised actions, while the runtime supplies and propagates intermediate tokens.
Go, Rust and TypeScript API clients require a configured profile. Their package versions are independent of HaveAPI and the API version. Without a profile, the server omits those clients from the overview and example panels. Install the matching generator gem when configuring its profile; ordinary servers do not depend on these generators.
Configure an API client
Register profiles before mounting the server. For an API client that matches the current API, configure its package identity:
api.documentation_clients('1.0',
go: {
import_path: 'example.org/widget/client'
},
rust: {
crate_name: 'widget-client'
},
typescript: {
package_name: '@example/widget-client'
}
)
api.mount('/api/')
generator_version defaults to the server's HaveAPI::VERSION. Install the
matching generator in the server's Ruby environment: haveapi-go-client,
haveapi-rust-client, or the source-distributed haveapi-typescript-client.
See the TypeScript generator guide for its
setup. An explicit generator_version requires the installed generator to report
that version; the server never downloads one. This version is independent of the
API client and API versions.
At the end of mount, the server builds one private structural model per configured API version. The model includes authentication resources, routes and changes from post-mount hooks, so canonical names and parent arguments use the complete API declaration. The private copy excludes examples, prose and parameter defaults. HTML requests use the current user's authorized description, fields and samples; they never expose the private model or its initialization diagnostics. Without a configured profile, the server loads no optional generator and hides the Go, Rust and TypeScript API client panels. The callback and async TypeScript examples remain separate and need no profile.
Automatic profiles assume that the installed API client matches the current server's generation contract. A package name or version does not prove its contents. An older API client can lack new actions or use different names after a collision or route change. Check compatibility before advertising a client; use a saved-schema override for an older or restricted API.
Optional API client versions and sources
Set module_version: 'v1.2.3' for Go, crate_version: '1.2.3' for Rust or
package_version: '1.2.3' for TypeScript when a specific package version is
needed. Omit the field or use nil to let the user's
package manager select a release. The server does not query a registry or claim
to have resolved a version. Omitted versions produce these installation hints:
go get example.org/widget/client@latest
cargo add widget-client
# Blocking Rust variant:
cargo add widget-client --features blocking
npm install @example/widget-client haveapi-client@0.30.2
Go resolves @latest by its normal rules. Cargo selects a compatible release
according to its resolver, manifest and toolchain; existing lockfiles still
apply. Explicit Cargo versions remain version requirements, without an added
exact = pin. Latest selection can change over time; supply a version to
control
the selection. Cargo never receives a literal version = "latest". Rust
examples also show their Tokio and serde_json requirements.
TypeScript accepts ordinary lowercase npm package names, including scoped names,
and optional concrete SemVer versions. Tags, version ranges, Git URLs, local
paths and custom factories are not profile options. Configure a package that
users can install; the server does not check its availability or publish it.
TypeScript examples call the package's typed properties through its public
createClient export. The profile does not detect or select the application's
installed haveapi-client runtime. The installation command above uses the
version supported by the TypeScript generator;
the examples do not report it as a configured runtime version.
For an unpublished Rust API client, supply an HTTPS Git URL and a full commit SHA:
api.documentation_clients('7.0',
rust: {
crate_name: 'vpsadmin-client',
source: {
git: 'https://github.com/vpsfreecz/vpsadmin-rust-client',
rev: 'ff876abe17e3301da62c1b6afbda055ec89db9d2'
}
}
)
The dependency hint uses that source instead of a registry release:
# Cargo.toml
vpsadmin-client = { git = "https://github.com/vpsfreecz/vpsadmin-rust-client", rev = "ff876abe17e3301da62c1b6afbda055ec89db9d2" }
# The blocking variant also sets features = ["blocking"].
source accepts only git and rev. URLs must be absolute HTTPS without
credentials, query strings, fragments or control characters; revisions must be
40 hexadecimal characters. Branches, tags, local paths and custom registries are
not supported. An explicit crate version additionally constrains the selected
source package. The server never fetches the repository or runs these user
installation commands. These settings do not regenerate or publish an API client
or establish that it matches a given API surface.
Saved-schema overrides
The existing file-based profiles remain supported:
api.documentation_clients('1.0',
go: {
import_path: 'example.org/widget/client',
module_version: 'v1.2.3',
description_path: '/srv/widget-sdk/api-1.0.json',
generator_version: '0.30.0'
},
rust: {
snapshot_path: '/srv/widget-sdk/api-1.0.snapshot.json',
generator_config_path: '/srv/widget-sdk/generator.toml',
generator_version: '0.30.0'
},
typescript: {
package_name: '@example/widget-client',
package_version: '1.2.3',
snapshot_path: '/srv/widget-sdk/api-1.0.snapshot.json',
generator_version: '0.30.0'
}
)
Paths name trusted absolute local files. description_path is Go's saved
machine-readable description, not HTML. For Rust and TypeScript, snapshot_path
selects the saved schema and capture metadata. These files select an exact API
client shape in place of the current server declaration. Keep them private and
retain them with the API client release.
Rust's legacy generator_config_path supplies the crate name/version and full
packaging configuration. It is mutually exclusive with inline crate_name and
crate_version, and can be paired with either a saved snapshot or automatic
schema. A saved snapshot can instead use inline crate identity. source works
with either identity form. The server validates the full legacy TOML; normal
inline documentation needs no license, compiler or formatter settings.
Saved files are read and validated during registration. Automatic models are initialized at the end of mount. Missing files or generators, actual generator version mismatches, unknown fields, conflicting identities and invalid schemas fail startup. Mounting also rejects unavailable API versions and mismatched Rust and TypeScript snapshot versions. Failed mounting publishes no profiles and leaves no usable server application. An older explicitly pinned generator can serve a suitable legacy file profile, but automatic/inline Rust mode requires the new factory interface and never guesses a fallback.
A saved API client can still serve examples after unrelated live additions. Each sample must match its saved route, layout and supplied parameter contracts; unavailable or stale samples show a limitation. Hidden actions, fields, defaults and samples remain hidden from the current user. The server never fetches descriptions, regenerates packages or runs a compiler/formatter during an HTML request.
Old profile settings work on the new server. Inline settings require the new server code; rolling back that code also requires restoring compatible settings. Mixed server workers can show different documentation examples without changing wire requests or persisted data.
Renderer extensions
Existing renderer positional constructors and init, auth(method, desc) and
example(sample) or request(sample)/response(sample) methods still work.
The registry binds a request-local HaveAPI::ClientExample::Context through
with_context. It holds the selected version, URLs including the API mount,
the filtered description, and the current resource, action and sample.
Its detail reader is exactly :simple or :thorough, defaulting to :simple.
Both construction and context.with(detail: ...) reject other values, including
strings and nil. Changing detail shares the existing owned data, as other
context derivations do. The base renderer's thorough? helper reads this value;
legacy positional renderer instances use simple behavior.
Construction copies and freezes Hash, Array and String data without freezing
the caller's containers. Each request owns its description snapshot.
context.with(...) creates a distinct frozen context, sharing unchanged fields
by identity and normalizing only explicitly supplied fields. A shallow-frozen
input container still needs copying; explicitly supplying an existing owned
Hash,
Array or String also copies it. Renderers must treat context-owned data as
read-only.
The profile is a shared reference; registered profiles and their adapters are
frozen separately. Other object types retain their existing reference semantics,
so context construction is not an arbitrary-object deep-freezing contract.
Subclasses of Context continue deriving through their constructors, preserving
custom initialization and extra keywords; the sharing optimization applies to
the exact base class.
Renderers may return a String or nil through their existing methods. New
renderers return either {status: :code, code: source} or
{status: :limitation, reason: text}. Source and limitation text are plain
strings, escaped once by the templates. A stable class id identifies a
variant; code selects its syntax language. available?(context) controls the
overview and every panel before rendering.
Renderer classes declare presentation with language 'python',
language_label 'Python' and, for a client variant, variant 'Async'. These
class methods also read the metadata. The language is a stable lower-case group
ID, independent of the syntax lexer and exact client ID. An unspecified language
defaults to the renderer's own id, its language label defaults to label, and
its variant is nil. Metadata is declared on each class; a subclass without its
own metadata gets those defaults. Keep client IDs and registry order stable.
The class query detail_levels_for(kind, context:, result:, auth_method: nil,
auth_description: nil) selects the meaningful levels for each example group.
result is the already-rendered Simple result. Return a frozen [:simple] or
[:simple, :thorough]; other values are errors. The base implementation returns
the inherited detail_levels declaration, which defaults to both levels for
custom renderers. A custom Simple limitation does not suppress its distinct
thorough implementation. Direct rendering at either Context detail remains
supported, independently of the choices advertised in HTML.
Ruby and PHP offer both levels for representable initialization, authentication
and actions. Ruby OAuth remains unsupported. Their constructors are lazy, so
With error handling runs setup explicitly and handles validation, API,
authentication and protocol failures. Ruby rescues ValidationError before its
ActionFailed parent and reads response errors only from an actual Response.
PHP uses its framework exceptions and getErrors() where supported, and
rethrows unknown dependency exceptions. Its OAuth session, state, PKCE, redirect
and token exchange remain part of the example.
Callback JavaScript and TypeScript offer both levels for actions. Initialization and authentication already check the ready result and offer Simple only. Async JavaScript, async TypeScript and Python offer both for representable examples.
Go's Simple actions already check transport and API failures. Both levels are available only for successful blocking actions with an authorized operation-status helper. Its initialization and credential setters make no request and offer Simple only. Rust offers both for representable code, with explicit handling of fallible construction and send results. Its authentication setters configure credentials without validating them over the network.
Typed TypeScript initialization is Simple only. Supported Basic/token
authentication and actions offer both with awaited AsyncError handling; OAuth
remains a limitation. CLI, curl and HTTP offer Simple only: the CLI reports
failures and exits nonzero, while the transcripts show protocol responses.
All built-in limitation results offer Simple only.
Go, Rust and TypeScript wrappers delegate to their configured adapter's
detail_levels_for(kind:, context:, result:, mode:, auth_method: nil,
auth_description: nil) when it implements that query. Older adapters retain
the wrapper's declared levels, including a distinct thorough result after a
Simple limitation. Those adapters may still offer identical code at both levels.
A query that returns invalid levels or raises an error does not use this fallback.
Capability queries must not render again or inspect unrelated resources.
The HTML producer renders each advertised detail once and
publishes its choices in the example group's data-example-details map. Client
metadata contains the ordered union for the current page; clients without any
example groups get Simple. Neither the group nor page choices change the saved
detail preference.
Simple action renderers use the established api and show the actual call and
relevant result. Initialize and authenticate it using the separate examples.
Python async fragments belong inside an async function; their comment makes that
context explicit. Python setup examples use a minimal async function and
asyncio.run, while sync examples run at top level. Neither level has a
__main__ guard. Thorough sync examples use top-level try/except.
Translate and validate samples before selecting presentation detail. Keep exact names, parent arguments, false, zero, empty strings, nullable values and blocking operation behavior. Do not shorten examples by dropping unsupported input or removing lines from an already rendered program.
Generated-client adapters belong to their generator gems and consume context
readers without depending on the server gem. They resolve names using the
complete private generator model and never run a compiler, formatter or
generator
in an HTML request. Keep adapter rendering read-only: configured adapters are
frozen
and shared, while authorized description snapshots belong to individual
requests.
Adapters keep their existing render(kind:, context:, mode:, ...) signature and
read context.detail directly. Test contexts must provide the reader. An older
third-party adapter can ignore this additive field and retain the wrapper's
declared choices without implementing the new capability query.
Derived contexts within one request share unchanged owned data. Supplying a
resource, action or sample still copies that supplied subtree, so a resource
with
many actions can remain costly to render.
Example source style
Generated examples follow the conventions of their output language. These rules apply to initialization, authentication and action examples, including inherited callback, sync, async and blocking variants. Keep Simple and With error handling consistent where both are supported.
| Language | Source conventions |
|---|---|
| PHP | Four-space blocks and array nesting; single quotes for ordinary static strings without interpolation; spaces around concatenation. Keep escaped double quotes where control strings or newline output require them. |
| Ruby | Two-space blocks and single quotes for plain authored strings. Keep dump, inspect and PP encoding for sample data. |
| JavaScript / TypeScript | Two-space blocks, double-quoted strings and import specifiers, semicolons, and conventional spacing after commas and colons and inside import/object braces. Preserve safe computed keys and typed property access. |
| Python | Four-space suites and the existing double-quoted data encoding. Omit empty optional import slots; use one blank line before module-level executable code and two around top-level async definitions. |
| Go | Tabs, gofmt-compatible spacing and separate standard-library/client import groups. Keep native double-quoted string encoding. |
| Rust | Four-space blocks, conventional Rust spacing/imports and the existing string encoder in both async and blocking examples. |
| CLI / curl | Preserve shell continuations. Serialize data first, then encode every complete dynamic shell argument with Shellwords.escape; this includes URLs, headers, option names and values. |
| HTTP | Preserve the protocol/JSON transcript, especially the header/body separator. Shell quoting rules do not apply. |
At known fragment boundaries, omit absent fragments and separate logical blocks with one useful blank line, subject to the language rules above. Avoid leading or trailing blank padding, duplicate separators and whitespace on empty lines. Keep formatting in the owning language renderer or adapter. Do not normalize whole rendered programs or custom-renderer output with a shared regex or formatter, or add a runtime formatter dependency. Tests may use native formatters.
For CLI and curl, keep authored shell syntax separate from argument data. Encode each argument once after its value has been serialized; surrounding raw data with single or double quotes is insufficient. Check generated commands with temporary fake executables: require successful execution, exact captured argument values and order, and no unintended command execution. Parse captured curl JSON to verify that shell encoding preserves the complete request body.
CLI and curl examples can show response output after the executable command.
Keep every physical response line inert when the complete block is pasted into a
shell. Normalize CRLF and bare CR to LF only within display-only response
fragments, then prefix every resulting line with #. Do not normalize or
comment the request command, and do not use shell escaping as a substitute for
commenting each physical response line. Regression tests must execute the
complete displayed block with temporary fake executables, preserve the exact
request arguments and curl JSON, and prove that hostile multiline response
values stay visible without being evaluated.
Keep encoded data separate from authored syntax. Preserve runtime values,
Unicode, apostrophes, backslashes, dollar signs, control bytes, empty values,
false, zero and null. PHP control strings may need double quotes with dollar
signs, backslashes and quotes escaped and PHP-valid control escapes; JSON
\u escapes are not a PHP string encoder. Never turn a newline into literal
backslash-n or let wrapper indentation change literal bytes.
Formatting changes must preserve public sample credentials, request inputs, error handlers and control flow, direct-call/custom-adapter compatibility, and meaningful detail choices. Do not add panels solely to show different formatting.
When changing a renderer:
- Inspect relevant initialization, authentication and action output for affected variants and both direct detail inputs. Cover absent optional fragments and literal edge cases where the change can affect them.
- Use existing server example specs for PHP literal round trips and real handlers, Ruby syntax, Python AST/separation, detail behavior and custom whitespace. Use the owning Go documentation specs, TypeScript documentation specs and Rust development checks when affected. TypeScript coverage includes source, strict compilation and performance specs.
- Search neighboring HTML, fixture and performance assertions for exact source spelling. Update intentional quote/import/spacing expectations while retaining semantic assertions, traversal/allocation bounds and strict compiler checks.
- Follow development commands and the existing verification requirements. Select checks by affected language and behavior; an unrelated edit does not require every language toolchain.
Verification
make test-documentation checks renderers, contexts, profiles, rendered HTML
and
local assets. Prepare the component bundles and JavaScript development
dependencies with make release-deps first. The target builds the JavaScript
package before compiling TypeScript examples against its public declarations;
the Ruby server CI uses the same order.
make test-documentation-python additionally checks the Python renderer limits
against the real request encoder. It requires the locked Python environment
prepared by make release-deps and is included in make test. The ordinary
Ruby renderer checks run without the Python package environment.
Deterministic context and HTML regressions run in the ordinary server suite. They check description sharing, allocation growth and isolation between users without a wall-clock threshold. The Rust adapter suite separately checks that normalization avoids unrelated description descendants.
The additional required make test-performance-clients contract checks every
registered renderer and mode with real generated profiles, bounded per-render
work and allocations, and complete-page allocation scaling. It runs in the
locked, unbundled benchmark environment, separately from the ordinary server
bundle. See performance testing
for preparation, exact coverage and CI behavior.
Documentation benchmark
Prepare the server bundle in its declared Nix environment, then run the opt-in harness from the repository root:
nix develop .#server-ruby --command bash -c \
'make --no-print-directory benchmark-documentation > /tmp/documentation.json'
The target synchronizes shared documentation before running the harness.
Standard
output is JSON; shell/dependency preparation happens before the redirected
command.
No network server, database, API client compilation or formatter is involved.
The synthetic fixture has users admin and member. Direct-render and description
lanes use Basic authentication with the local password fixture-only. HTML
requests use real documentation OAuth cookies on supported sources, with token
validation on each request; historical sources without that feature use Basic.
Defaults measure two shapes with eight ordinary actions each: eight resources with one action, and one resource with eight actions. Each ordinary action also has a privileged sample; the first resource has an additional admin-only action. Context lanes derive a prepared parent with a changed profile or a constant small sample. The HTML lane performs one real Rack request per trial, with all available dynamic renderer panels. Use a minimal smoke run or a larger repeated campaign:
make benchmark-documentation \
BENCHMARK_DOCUMENTATION_ARGS='--shape resources --size 1 --warmup 0 --trials 1 --iterations 1'
make benchmark-documentation \
BENCHMARK_DOCUMENTATION_ARGS='--shape both --size 64 --samples 2 --parameters 8 --warmup 2 --trials 7 --iterations 500' \
> /tmp/documentation-large.json
Run these make commands inside the prepared shell. --shape custom --resources
N --actions N controls both dimensions independently; --lanes context
isolates
derivation and --lanes html isolates requests. --user member selects
restricted
help. --help lists all options. --rust enables the fixture's matching Rust
profile, includes both Rust variants in HTML, and adds separate async/blocking
action and authentication adapter lanes. It requires the optional generator's
Ruby dependencies to be available; ordinary benchmark runs need none of them.
Prepare both component environments using nix develop .#server-ruby and
nix develop .#client-rust. The server bundle alone does not activate the Rust
dependencies. In the top-level Nix shell, combine the prepared local gem stores:
env -u RUBYOPT -u RUBYLIB -u BUNDLE_BIN_PATH -u BUNDLE_GEMFILE \
-u BUNDLE_LOCKFILE -u BUNDLE_PATH -u BUNDLE_FROZEN -u BUNDLER_SETUP \
GEM_PATH="$PWD/.gems:$PWD/clients/rust/.gems:$(ruby -e 'puts Gem.default_dir')" \
make benchmark-documentation BENCHMARK_DOCUMENTATION_RUBY=ruby \
BENCHMARK_DOCUMENTATION_ARGS='--rust --shape both --size 8 --warmup 1 --trials 3 --iterations 100' \
> /tmp/documentation-rust.json
This command runs outside Bundler and records the gem versions actually activated. Retain that list alongside both component locks and use the same prepared inputs for both revisions. Missing optional dependencies fail the run; Rust lanes are never silently omitted. See the Rust adapter guide.
Every lane reports each trial and the median wall seconds, process CPU seconds and allocated objects, with the operation count per trial. Wall time uses a monotonic clock. A full GC runs before each trial, outside its timer; GC triggered by the measured work remains included. Mount/profile construction has separate setup metrics. Ownership construction, description selection, warmups, output validation, hashing and JSON serialization are outside lane measurements. Reports include fixture dimensions, renderer IDs, profile mode, Ruby runtime, host, Git HEAD/status/diff digest, loaded source digests, dependency versions and available lockfile/harness/fixture digests. HTML and Rust outputs include status, bytes and SHA-256; failed or missing output causes a nonzero exit.
For a before/after comparison, retain one exact harness and fixture and point
them at each implementation with --source-root /absolute/checkout. Set up
equivalent dependencies and runtime first. An explicit --source-revision LABEL
can supplement the detected HEAD and is required for an exported source tree.
Retain exact heads, commands, raw JSON, lock/input identities and host identity.
Compare dimensions, renderer modes, statuses, bytes and body digests before
comparing medians. Context description_shared deliberately distinguishes the
old copying implementation. Investigate any output difference; record exact
volatile-field normalization if needed rather than silently dropping mismatches.
These measurements describe the synthetic workload on the recorded host. They do not establish production latency. The opt-in harness has no timing threshold; the paired endpoint performance suite supplies required quick/weekly CI timing, allocation and query gates, including documentation OPTIONS and both HTML shapes. It retains baseline/self noise and blocks inconclusive results. Ownership, traversal and allocation regressions cover this failure class, while concentrated-resource costs and other future regressions still need representative measurements.
Each client also has acceptance commands that check executable snippets against installed packages or an API client. These run rendered source against disposable fixtures; syntax checks alone do not establish that client calls or authentication work. See each client's development documentation for its test environment. Highlighter rebuilding is described in utils/highlight.