Generate a REST interface
apizr generate rest turns eligible Capability IR declarations into a standalone
FastAPI application, static OpenAPI document and artifact manifest. Generation
reads one Python file or notebook without running it.
Start with pricing.py:
def total(prices: list[float], *, tax: float = 0.2) -> float:
return sum(prices) * (1 + tax)
Inspect and generate:
uv run apizr inspect pricing.py --module-name project.pricing
uv run apizr generate rest pricing.py --module-name project.pricing --output-dir .output/rest
The directory must be new or empty. Existing files and symlink paths are refused;
there is no destructive --force option. Use a physical directory path if a
filesystem alias such as /tmp is a symlink on your system. Safe output writing
requires the directory-descriptor/no-follow facilities tested on Linux and macOS;
unsupported hosts fail before writing.
Choose direct or governed execution
Direct mode remains the default. To opt into a bounded fresh worker for every request/Tool call, generate a separate bundle with an explicit policy:
apizr generate rest pricing.py --execution-policy examples/policies/local-default.json \
--output-dir .output/rest-governed
Use that example policy from the Apizr checkout, or create policy.json containing
{} and pass its path. Both Python and notebook inputs support the option.
| Behavior | Direct (default) | Governed (opt-in) |
|---|---|---|
| Source import | In server at startup | Only in a fresh worker on each call |
| Globals / mutable defaults | Persist between calls | Reset each call |
| Wall timeout | No process timeout boundary | Worker termination on policy deadline |
| Worker protocol | Existing in-process invocation | Bounded input/output |
| Environment | Server environment | Clean or explicitly allowlisted |
| Filesystem/network sandbox | None | None |
Both modes require trusted code. Governed mode is not a filesystem/network
sandbox. It requires a supported POSIX runtime host. Unsupported requested
controls, such as network denial, are refused during generation; unavailable
runtime facilities or invalid artifacts fail startup. No Apizr installation is
needed to run either bundle. Install its own requirements.txt and use the same
startup commands below.
Governed startup checks policy, plan, source and artifact digests without importing
source. Changed source/plans/policy or missing worker files after startup cause
sanitized call errors; the server remains usable. Generation stays static in both
modes. OpenAPI/Tool definitions stay identical. The CLI reports execution.mode;
only governed bundles add an execution/ bridge and apizr_governed/ runtime.
Governed errors map invalid arguments to HTTP 422, timeout to 504, and every other execution failure to generic 500. Results must already be finite JSON: unlike the direct FastAPI encoder, governed execution rejects tuples, sets, bytes and arbitrary objects. Return annotations still do not enforce results. Source exceptions, including intentional HTTPException responses, are sanitized at the worker boundary.
See governed transport architecture and the execution policy guide for defaults, limits, environment allowlists and the precise trust boundary.
Start the application
Direct-mode startup imports and executes the bundled source; governed mode imports it only inside the worker on a call. Run only trusted code. A
ready result concerns the static interface contract, not runtime safety.
From the Apizr checkout:
uv run uvicorn app:app --app-dir .output/rest --host 127.0.0.1 --port 8001
From a standalone bundle, install its requirements.txt in a virtual environment
and run uvicorn app:app from the bundle directory. Apizr itself is not needed.
curl -f http://127.0.0.1:8001/health
curl -f http://127.0.0.1:8001/capabilities/total \
-H 'Content-Type: application/json' \
-d '{"prices": [10, 20]}'
# 36.0
Open /docs for interactive API documentation or /openapi.json for the same
OpenAPI document generated on disk. Functions always use POST under
/capabilities/; infrastructure routes stay separate.
Select functions and inspect refusals
uv run apizr generate rest pricing.py --select total --output-dir .output/selected
uv run apizr generate rest examples/pricing.ipynb --module-name project.pricing --output-dir .output/notebook-rest
--select accepts comma-separated names or full IDs such as
python:project.pricing:total. Without selection, every callable assessment must
be eligible. Conditional, unsupported or ambiguous declarations stop generation
with their readiness reason codes. Selecting a ready function can exclude an
unrelated unsupported generator, but cannot override uncertainty recorded on the
selected function itself.
Only can_generate_interface=true passes. There is no unsafe override. Use
apizr inspect to understand decorators, rebinding, initialization,
dependencies and unresolved input types before changing the source.
Logical identity defaults to the input filename stem. A dotted --module-name
keeps identity explicit and portable; the bundle contains the corresponding
package structure. Avoid names already used by the runtime, including app
(the generated bootstrap), json or fastapi; conflicts fail startup clearly.
Requests, defaults and errors
Every request body is an object; send {} to functions without parameters.
Unexpected fields, missing required fields and invalid types produce HTTP 422.
Strings and booleans are not silently accepted as numbers.
Omitting a defaulted field lets Python apply its own default. Explicit null is
validated: x: int = None permits omission but not {"x": null}; a nullable type
such as int | None permits both. Default expressions are never evaluated during
generation or copied into the request model.
Positional-only and keyword-only declarations keep their call semantics. For
f(a=1, b=2, /), supply a when supplying b; a gap in positional arguments is
rejected with HTTP 422 and documented by OpenAPI. Trailing defaults may be omitted.
Declared return types are documentation only. Unexpected function/serialization
errors in direct mode produce a generic HTTP 500; intentional FastAPI HTTPException responses
are preserved. In direct mode, source digest or callable-shape mismatches stop application startup. Governed mappings are described above.
CLI exit codes:
0: bundle written successfully;1: selected declarations are ineligible, or an approved contract cannot be lowered;2: input or output error, including unknown selection, syntax, unsupported format, non-empty directory or file access failure.
Files and limits
The bundle contains app.py, the executable module under source/, canonical
capability-ir.json and readiness.json, openapi.json, apizr-rest.json and
requirements.txt. Notebook bundles also retain the original notebook bytes.
The manifest hashes every other generated artifact and links source, IR and
readiness digests. It is content metadata, not a signature or attestation.
This v1 command does not infer project dependencies or create Docker files.
The historical generation workflow remains available through
apizr --script ... and apizr --notebook ..., with its existing behavior.
See REST generator v1 for the exact type mapping, integrity checks, manifest, deterministic serialization and limitations.
Governed OCI-container mode
There are three explicit modes: direct (default), governed local-process (v1 policy), and governed OCI-container (v2 policy). Local-process bundles and their bytes remain unchanged and do not require Docker.
Prepare a trusted local worker image as described in the execution guide, then use its full immutable ID and platform:
apizr generate rest sample.py --output-dir ./generated-oci \
--execution-policy examples/policies/container-default.json \
--runtime-image sha256:<full-64-character-local-image-ID> \
--runtime-platform linux/amd64
Generation works without Docker and never pulls images. Both image and platform
are mandatory for OCI; image options with a local v1 policy are errors. Install
generated-oci/requirements.txt in the server environment; Apizr itself is not
required there. Docker CLI/Engine and the selected prepared worker image must be
available when starting the generated server. Startup checks fail before serving
if the provider or image is unavailable.
The interface definition is identical across modes; execution changes. Every OCI call uses a fresh container, so globals and mutable defaults reset. The transport never imports user source. The capability receives the existing OCI filesystem, network and resource limits. Subprocesses remain permitted but contained. See the v2 bridge contract for the control matrix, sanitized error mappings, integrity checks and trust boundary.