Changelog

What changed in each release of the CLI. The newest release is on the downloads page.

0.10.0 (2026-10-25)

The mock server is now apigenie-server, with a document store for CRUD resources and several services in one process, and a fourth binary, apigenie-catalog, keeps a repository’s API catalog. apigenie-generator applies OpenAPI overlays before generating, and the three binaries share one table of exit statuses.

Highlights

  • apigenie-server, the mock server and validating proxy under its new name, with a dashboard at /__apigenie/ui (Traffic, Operations, Scenarios, Contract, Data and, for AsyncAPI, Channels), several services on one port with a prefix each, a document store (ctx.db, db.crud: auto resources, seeding, snapshots), session recording and replay (--record DIR, --replay DIR), client certificates, MQTT 5 properties, and validating proxies for Kafka (--proxy-to kafka://) and AMQP (--proxy-to amqp://) brokers.
  • apigenie-catalog (experimental), a fourth binary: catalog list finds the API specifications of a repository and catalog init records them in apigenie.yaml.
  • Code generation: --overlay FILE on every OpenAPI and AsyncAPI target; the Elixir family (client elixir and server phoenix for OpenAPI and AsyncAPI, server absinthe for GraphQL, server elixir-grpc and the Elixir gRPC client for Protobuf); server mcp (experimental), a Model Context Protocol server from an OpenAPI document; server orpc at the conformance bar.
  • apigenie test speaks Kafka and AMQP (KAFKA and AMQP regions), GraphQL subscriptions over graphql-ws, gRPC client and bidirectional streams, MQTT 5 and mutual TLS.
  • Towards 1.0: one table of exit statuses for the binaries, formatVersion in every JSON report with published JSON Schemas, a JSON Schema for apigenie.yaml behind apigenie config, config check and config schema, configuration discovery without --config, the Compatibility page, and experimental labels on what is outside the coming promise.
  • Linting and diffing: the OpenAPI format registry as the list of formats, with schema-format-deprecated, schema-format-unknown and a hint severity; diff knows sunset dates and no longer rates deprecating as breaking; apigenie evolve plan OLD NEW (experimental) plans an expand/contract migration.
  • Also: convert --from wsdl --to openapi31, i18n check and i18n sync, OpenAPI formatting at every depth with layout options, Windows console colors, and a docs package published as @mykulyak/apigenie-docs.

Upgrading

  • Upgrading from 0.9: apigenie-mock is now apigenie-server. The mock server and validating proxy is installed as apigenie-server by every archive and package, the npm package and Homebrew, and its Docker image is mykulyak/apigenie-server. The old names keep working until 1.1, each with a one-line deprecation notice on standard error: apigenie-mock is a symlink to apigenie-server (a copy in the Windows archive, a command of the npm package), and the APIGENIE_MOCK_* variables are read when the APIGENIE_SERVER_* variable of the same name is unset. The last mykulyak/apigenie-mock image (latest and this version) only prints the new name and exits with 2; older tags are unchanged. apigenie-server.yaml is read before apigenie-mock.yaml, which still works; commands.mock, the x-apigenie-mock extensions, the X-Apigenie-Mock-* headers and the /__apigenie admin API are unchanged. What the server sends says apigenie-server instead: the Server and Via headers, the HAR creator, the admin status name, the Basic realm, the MQTT client ids, and the contract report’s report member (apigenie-server-contract) and JUnit suite name (apigenie-server contract). The reference page moved to server; the old address redirects. The design is ADR-147.

  • One table of exit statuses for apigenie, apigenie-generator and apigenie-server. 0 success, 1 findings (lint errors, breaking changes, format --check differences, i18n apply --strict unknown keys, overlay --validate problems), 2 a usage, input or unexpected error, 3 and 4 apigenie-server’s document and address errors, 5 a failed contract report (apigenie-server, apigenie test). The 1.0 compatibility promise freezes it. Scripts that check for the old numbers need these changes:

    • errors that exited with 255 (-1) now exit with 2: unreadable or invalid input files in bundle, convert, changelog, diff, i18n, lint and overlay, bad configurations, and every error of apigenie-generator;
    • errors that exited with 1 now exit with 2: unknown options and commands, missing files, configuration files that can’t be parsed, format on a file with syntax errors or an unsupported document, bundle of a .proto file with errors, license --library with an unknown name, and unexpected errors in apigenie-server and apigenie test;
    • apigenie test exits with 2 instead of 3 when a request file, an environment file or the specification can’t be read or parsed;
    • apigenie and apigenie-generator without a command, or with an unknown one, exit with 2 (255 before) and print the help of the command above it on the standard error.

    apigenie-server keeps 3, 4, 5 and 130. The design is ADR-141 (exit status).

  • --version lists the binaries installed next to it. apigenie --version prints the version on its first line, then apigenie-generator VERSION and apigenie-server VERSION for those found in the same directory (the same for the other two binaries). Scripts comparing the version should read the first line: apigenie --version | head -n 1. The GitHub Action, the Homebrew formula and the release workflows do. Subcommands no longer have a --version of their own (apigenie lint --version printed 1.0).

  • lint --formatter json and diff --report detailed --formatter json write an object. Lint JSON is { "formatVersion": 1, "results": [...] } and the detailed diff report { "formatVersion": 1, "changes": [...] }; both were bare arrays. Read .results and .changes instead (compatibility).

  • diff no longer rates deprecating as breaking. In the recommended rules, setting or clearing deprecated on an operation, a parameter, a header or a schema is non_breaking: it announces a removal, and the removal is the breaking change (rated by sunset_passed, see Added). Gates that relied on it should set operation.deprecated: breaking (and parameter, header, schema) in commands.diff.rules. The design is ADR-172.

  • Seeded faker values change. Faker now draws its values with algorithms of its own, the same on every platform, and name, email and other values of a --seed differ from 0.9.0’s. Usernames and emails are ASCII (Cieślar becomes Cieslar), and date.between is a range ({date.between: {from: 2024-01-01, to: 2024-12-31}}) rather than an alias of date.recent.

  • Faker arguments are checked. x-faker, @mock(faker:, args:), graphql.fields and fake() reject arguments a provider doesn’t take, of the wrong type or out of bounds (a negative length, a probability above 1) with faker’s message, where they were clamped or ignored.

  • apigenie format keeps the format of OpenAPI, Swagger and AsyncAPI files without a configuration. Formatting api.json with no --config used to write api.yaml next to it; JSON files now stay JSON. Set format: yaml to convert them.

  • The OpenAPI format configuration is checked. Unknown keys (indnet), unknown objects in property_order, indents outside 2 to 9 and unknown output formats are reported with their line and column, and nothing is formatted; they used to be ignored or to abort the command.

  • The recommended OpenAPI preset changes more of a document. It now reaches nested schemas, inline callbacks and the objects of OpenAPI 3.2, sorts the path templates of paths (its paths: sorted had no effect), orders more schema keywords ($ref, $id, $defs, default, examples, readOnly, writeOnly, deprecated…) and the fields of 3.2 objects, and orders Reference Objects by reference. Expect a one-time diff when reformatting with extends: [recommended]. property_order.responses applies to the status codes of operations; the names of components.responses follow the new componentNames key.

  • The configuration file is validated against a JSON Schema when it is loaded, by apigenie, apigenie-generator and apigenie-server. Settings that were silently ignored are now errors (exit status 2) with the line, the column and the JSON pointer of the value: unknown keys under commands.* (the OpenAPI, AsyncAPI and Swagger linters, and the WebSocket generators, used to skip them), sections of commands that have no settings (commands.bundle, commands.changelog: changelog uses commands.diff), and values of the wrong type. apigenie config check lists the errors of a file; fix them, or remove the settings that did nothing. The design is ADR-145.

  • apigenie and apigenie-generator find the configuration file without --config: apigenie.yaml (.yml, .json, or .apigenie.*) in the current directory or a parent up to the root of the git repository. Pass --no-config to run with the defaults, as before, in a repository that has such a file. apigenie-server falls back to the same file after its own apigenie-server.yaml and apigenie-mock.yaml.

  • The docs package is @mykulyak/apigenie-docs. The npm project that apigenie-generator docs writes now depends on @mykulyak/apigenie-docs (^0.1.0), the new name of the web components package. 0.9.0 wrote @mykulyak/apigenie-web-components into package.json, and that package was never published under that name, so npm install failed. In a project generated by 0.9.0, replace that dependency with "@mykulyak/apigenie-docs": "^0.1.0", or run apigenie-generator docs into it again.

  • apigenie test speaks MQTT 5. MQTT regions connect with MQTT 5 (falling back to 3.1.1 when the broker refuses it), and the messages they publish are checked with their MQTT 5 properties against the AsyncAPI MQTT bindings. A region publishing to a channel whose binding states properties (a payloadFormatIndicator, contentType, correlationData, responseTopic, or an operation’s messageExpiryInterval) fails with message.properties.invalid until it gives them with # @property lines, or keeps MQTT 3.1.1 with the version: 3.1.1 setting. The design is ADR-110 (amended).

Added

  • The OpenAPI format registry (https://spec.openapis.org/registry/format/) is API Genie’s list of formats. apigenie-server (mock and --proxy-to, OpenAPI and AsyncAPI), apigenie test’s contract checks and every other JSON Schema validation check the registry’s formats beyond JSON Schema’s:

    • int8, int16, uint8, uint16, uint32, uint64 and double-int ranges, with int64 and uint64 also as integer text;
    • decimal and decimal128, as numbers or text (decimal128: 34 significant digits, NaN, INF);
    • date-time-local and time-local (RFC 3339 without an offset);
    • http-date (IMF-fixdate and RFC 9110’s obsolete forms) and unixtime;
    • ipv4-cidr and ipv6-cidr;
    • language (RFC 5646 tags);
    • media-range and char;
    • base64url and the RFC 8941 structured field items (sf-binary, sf-boolean, sf-decimal, sf-integer, sf-string, sf-token).

    commonmark, html, password and binary stay unchecked, as the registry allows. openapi.custom_formats in apigenie.yaml (or the server’s configuration file) now reaches apigenie-server. Its formats are accepted as declared, and an entry naming a registry format replaces that format’s type and turns its check off. The registry is vendored by scripts/update-format-registry (--check verifies the copy is current). The design is ADR-166.

  • Lint rules for formats: schema-format-deprecated and schema-format-unknown, in the OpenAPI, AsyncAPI and Swagger linters.

    • schema-format-deprecated (a warning in every ruleset) reports byte, binary and base64url, which the format registry deprecates from OpenAPI 3.1, with what replaces them (contentEncoding: base64, contentMediaType, contentEncoding: base64url). In OpenAPI 3.0 and Swagger 2.0 documents, which have no replacement, the issues are hints.
    • schema-format-unknown (off in recommended, a warning in strict and all) reports a format that is neither in the registry nor in openapi.custom_formats, such as a misspelled date_time.
    • apigenie lint passes openapi.custom_formats to the format rules.
  • A hint severity for lint issues. Hints are shown by every formatter (SARIF note, checkstyle and Visual Studio info, hint elsewhere, and N hints in the stylish summary), but never count as errors or warnings and never change the exit status. Rules can be configured with hint (or info) in apigenie.yaml.

  • The generators map the format registry’s formats.

    • Java, Spring and the AsyncAPI Java targets: uint32, double-int, unixtime and sf-integer become Long, and uint64 becomes BigInteger in the OpenAPI targets. date-time-local and time-local become LocalDateTime and LocalTime in the AsyncAPI targets too.
    • C# and ASP.NET: int8 to uint64 become sbyte, short, byte, ushort, uint and ulong; double-int and unixtime become long, decimal128 becomes decimal, and date-time-local becomes DateTime.
    • Go: double-int, unixtime and sf-integer become int64.
    • Python and FastAPI: numbers of format decimal128 become Decimal like decimal ones, date-time-local becomes pydantic’s NaiveDatetime, and time-local becomes datetime.time. Decimals of type: string stay strings in every generator but C#, keeping their pattern.
    • Elixir: date-time-local becomes NaiveDateTime.t(), and time-local becomes Time.t().
    • TypeScript validators and Zod schemas check the ranges of int8 to uint64 and double-int. Zod validates date-time-local with z.iso.datetime({ local: true }) and time-local with z.iso.time().
    • apigenie-generator data and the server’s generated values pass the checks of every registry format: integer widths keep to their range, unixtime is a time in 2020..2029, and http-date, the CIDRs, language tags, media ranges, the sf-* items, JSON pointers and regular expressions get values of their syntax.
  • The documentation components link formats to the format registry. A schema’s format, in apg-schema’s type label or its facts, links to the format’s page of the registry (part format-link), and the generated examples have values for the registry’s formats. The website’s new Formats page lists every registry format with its JSON types and API Genie’s support, and the lint rules, the configuration page and the server page link to it.

  • apigenie-server --proxy-to kafka://host:port contract-tests an AsyncAPI document’s Kafka channels against a real cluster. There is no broker in the server: it is a validating client of the cluster (ADR-170).

    • Reading. It reads the channels’ topics (the Kafka binding’s topic, else the address; parameters match the topic’s segments) from the offsets they had at start, outside any consumer group, so the application’s consumers and offsets are left alone.
    • Checking. Every record is checked as the application’s (a channel with only send operations), a client’s (only receive operations) or whichever matches. The value is checked against the messages, the headers against the message’s headers, the key against the Kafka binding’s key (new check message.key.invalid) and the partition against the channel binding’s partitions (new check message.partition.invalid).
    • Results. Records are logged as kafka-in and kafka-out events and count in the coverage and the contract report like any other messages. Enforce mode reports, as records can’t be stopped.
    • Admin publishes. POST /__apigenie/asyncapi/publish produces a Kafka channel’s record to the cluster after the same checks, with the new key, headers and partition fields, and answers where it went.
    • Connection. kafkas:// connects over TLS. SASL PLAIN credentials come from APIGENIE_KAFKA_USERNAME and APIGENIE_KAFKA_PASSWORD. The cluster needs Kafka 2.3 or later; compressed record batches are reported and skipped.
  • apigenie-server --proxy-to amqp://host:port/vhost contract-tests an AsyncAPI document’s AMQP channels against a real AMQP 0-9-1 broker (ADR-171). It is a client of the broker, not a broker.

    • Reading. It binds an exclusive queue of its own to the exchange of every channel whose address is a routing key (the address’s parameters as * words of a topic exchange, any key of a fanout or headers exchange) and checks the messages it gets like the Kafka tap’s records, plus their properties against the AMQP bindings (deliveryMode, priority, expiration, userId, replyTo, timestamp, contentEncoding, messageType; message.properties.invalid). Queue channels are left alone, as reading them would take the application’s messages, and the tap says so.
    • Results. Messages are logged as amqp-in and amqp-out events and count in the coverage and the contract report.
    • Admin publishes. POST /__apigenie/asyncapi/publish publishes an AMQP channel’s message to the broker, with the new routingKey, properties and headers, mandatory and confirmed (409 when no queue takes it).
    • Connection. amqps:// connects over TLS. Credentials come from APIGENIE_AMQP_USERNAME and APIGENIE_AMQP_PASSWORD.
  • AMQP requests in apigenie test. An AMQP amqp://host:port/vhost region publishes its body’s messages to exchange and routing-key with # @property basic properties and headers, and reads subscribe queues and private queues bound with bind: EXCHANGE KEY; a message no queue takes is an error. The messages are checked like the tap’s, and replies are paired by a correlation id in a header or a property (RabbitMQ’s direct reply-to works). --generate --amqp-url makes one region per operation of an AMQP channel.

  • KAFKA requests in apigenie test. A KAFKA kafka://host:port region produces its body’s records to topic with a key, a partition and # @property NAME VALUE headers, and reads subscribe (or topic) until # @expect records came; the region’s own records are left out.

    • A topic that a cluster creating topics on first use (auto.create.topics.enable) doesn’t have yet is created by the region’s first record and read from its start, also on Kafka 4, which reports such a topic unknown while it creates it.
    • With an AsyncAPI --spec, the records are checked like MQTT messages, plus their key and partition, and replies are paired with requests by a correlation id in a header or the payload.
    • Credentials come from username and password, else APIGENIE_KAFKA_USERNAME and APIGENIE_KAFKA_PASSWORD, and are redacted.
    • kafkas:// uses --ca-cert, --insecure and --client-cert.
    • --generate --kafka-url kafka://host:port makes one region per operation of a Kafka channel.
  • apigenie convert --from wsdl --to openapi31 imports SOAP services. A WSDL 1.1 document with SOAP 1.1 or 1.2 document/literal bindings over HTTP becomes an OpenAPI 3.1 document. The command also reads the WSDL documents and XML Schemas it imports and includes, as local files; remote ones aren’t fetched. In the result:

    • Each SOAP operation is POST {address path}/{operation}, with x-soap-address and x-soap-action. The operations of a SOAP 1.1 and a SOAP 1.2 binding at one address merge, with a text/xml and an application/soap+xml body.
    • Requests and responses are SOAP envelopes whose schemas carry xml objects, so apigenie-server validates SOAP requests and answers SOAP envelopes. Faults are 500 responses, and one-way operations answer 202.
    • The XML Schema types (sequences, choices, attributes, simple content, restrictions and enumerations, extensions, occurrence bounds, nillable elements) are components, and the WSDL documentation is in the descriptions.
    • What has no counterpart is told as warnings with file, line and column: xs:any, substitution groups, lists, and decimals. WSDL 2.0, rpc/encoded and rpc/literal services are refused with exit status 2.

    The result lints without errors with recommended, and diff and the generators read it. --from swagger2|wsdl is optional: an XML input is WSDL. The library is src/wsdl_convert, tested on Apache CXF, NWS, ONVIF and WSDL 1.1 Note documents vendored in data/tests/wsdl with their licences. The design is ADR-164.

  • apigenie evolve plan OLD NEW (experimental) plans safe API changes. It splits the breaking changes between two OpenAPI 3.x documents into an expand wave (ship now: the old elements stay next to the new ones, deprecated: true with an x-sunset date) and a contract wave (ship after the window: the target document), writes 1-expand.yaml, 2-contract.yaml and plan.yaml to --out (default evolve-out/), and proves the plan with diff before writing anything: the expand wave is non-breaking against the old document, the contract wave has only scheduled removals (and what its units and the residue own) against the expand wave, and it equals the target. Ten recipes: removed operations and paths, moved paths (--hints FILE with renames as METHOD /path or diff’s location syntax, a window and a reason), removed and renamed response properties, renamed request properties and parameters, removed request parameters, newly required request properties and parameters, removed request enum values (x-deprecated-enum); renames are detected when one member goes and one with the same schema comes. What no recipe makes safe is residue, listed with a reason (exit status 1). Each unit carries a server note (emit both, accept both, apply a default) and the report lists them as a checklist. --window 90d|12w, --today DATE, --config (the commands.diff block), --format text|json (formatVersion: 1, apigenie-evolve-report.json). The reference page is evolve; the design is ADR-172.

  • diff knows sunset dates. The rule condition sunset_passed holds when the removed element is deprecated and its x-sunset (an RFC 3339 date or date-time) is on or before the date of the comparison, --today (default: the current date; OpenAPI documents only). The recommended rules use it for remove_item of operations, path items (every operation sunset), parameters, component schemas and parameters, response properties and their required items, and request enum values listed in the schema’s x-deprecated-enum: a removal after the sunset is non-breaking, before it breaking, and documents without x-sunset are rated as before. A property deprecated next to its $ref counts as deprecated.

  • apigenie-generator <target> --overlay FILE applies OpenAPI Overlays (1.0 to 1.2) to the input before generating, in order, so the vendor extensions of a target can live in an overlay of their own instead of in the API description. Every target of OpenAPI and AsyncAPI documents takes it (AsyncAPI ones bundled first), and commands.generate.<target>.overlay lists overlays in the configuration file, relative to it, applied before the --overlay ones. An action whose target selects nothing is a warning with its position in the overlay, and an error with --strict (nothing is generated, exit status 2). Warnings and read errors about a value an overlay set point at the overlay’s line (spring.overlay.yaml:9:9 Warning ...), and the document’s own lines are kept, since apigenie overlay and every document built with jv::JVBuilder now keep the positions of what they copy. Each generator’s reference page lists the x- extensions it reads. The codegen conformance suite generates its Spring target with --overlay --strict instead of a separate apigenie overlay step. The design is ADR-156.

  • Client certificates (mutual TLS) for apigenie-server and apigenie test. --client-cert FILE --client-key FILE presents a certificate to the servers that ask for one: the https://, wss://, mqtts:// and gRPC TLS upstreams of apigenie-server --proxy-to (also tls.client_cert and tls.client_key in the configuration, APIGENIE_SERVER_CLIENT_CERT and APIGENIE_SERVER_CLIENT_KEY), and every https://, grpcs://, wss:// and mqtts:// request of apigenie test, with --insecure too. apigenie-server --tls-client-ca FILE (tls.client_ca, APIGENIE_SERVER_TLS_CLIENT_CA) requires a client certificate issued by those authorities on every HTTPS, WSS, gRPC and MQTTS connection, the admin API included. A refused client is a warning in the log that says why. The subject of an accepted certificate is clientSubject in the JSON log and the admin API’s log, and the contract report lists the clients (clients in JSON, a client property in JUnit, a Clients: summary line). A client certificate or key that can’t be loaded, a lone --client-cert or --client-key, and --tls-client-ca without --tls-cert exit with status 2. A server that refuses the client’s certificate now says so in the error (the server requires a client certificate) instead of “the connection closed before a response”. Session resumption and revocation checks (CRL, OCSP) are not supported. The design is the amendment of ADR-104 (server, test).

  • apigenie-server --tls-cert serves MQTT over TLS. The MQTT broker of an AsyncAPI document uses the server’s certificate (and --tls-client-ca), on port 8883 unless --mqtt-port or asyncapi.mqtt_port says otherwise; the startup line names it mqtts://. Before, it stayed cleartext on 1883.

  • apigenie-server has a dashboard at http://127.0.0.1:4010/__apigenie/ui (the startup banner names it): Traffic as it happens, with an exchange’s request, answer and validation problems, Copy as curl, Replay, Save as stub and Add to .http; Operations by tag with their hits and what answers them, and a fixed answer per operation (a status, an example, a generated body, a latency) saved as an override, with the Prefer header that does the same for one request; Scenarios with their rules and hits, a rule builder and the network simulation with the last response times; Contract with the gate against --fail-on and --min-coverage, the coverage, the violations by check, the operations never called and the report files; Data with the document store’s collections, seeds and snapshots and ctx.state. Every panel copies its settings as configuration YAML or x-apigenie-mock. The page is built from the web components (src/app_web, the apg-server-* elements, @mykulyak/apigenie-docs/server) and embedded in the binary; it follows the admin API’s rules (--admin off, admin.allow_remote, admin.port). The admin API says more for it: /routes items carry tags, deprecated, the declared responses with their examples and lastSource, GET /overrides answers whole rules and the scenarios list theirs as ruleList, the status gains url, startedAt and logSize. The design is ADR-167 (Dashboard).

  • The dashboard has a Channels screen for AsyncAPI documents, listed when a served document has channels: the channels by WebSocket, MQTT and HTTP with their operations, the clients connected and the messages counted; for a channel, its messages in and out as they happen, its clients, and Publish, a message sent as the application would (POST /__apigenie/asyncapi/publish) to every client of the channel or to one, with a payload started from the document’s examples or left to the server; a payload the message’s schema refuses shows the violations and goes nowhere, until Send anyway; Copy as curl. The admin API says more for it: GET /__apigenie/asyncapi/channels items carry messageList, each message with its contentType, summary and examples (payload and headers), and POST /__apigenie/asyncapi/publish takes client, an id of /asyncapi/clients, to send to that one client (404 asyncapi-unknown-client when it isn’t connected). The design is the amendment of ADR-167 (Dashboard); gRPC and GraphQL variants of the dashboard come after 1.0.

  • apigenie-server’s admin API serves what a dashboard needs. GET /__apigenie/events streams the log as it happens, as Server-Sent Events: the entry’s id as id:, its kind as event: (request, ws-out, reload, and the new scenario and contract-updated), a violation event after an entry with violations, ?type=, ?level=, ?service=, and ?after= or Last-Event-ID to replay what the log still holds. While a stream is open every exchange’s request and response headers and bodies are captured; entries gain responseHeaders, Base64 bodies for bytes that aren’t UTF-8, and *BodyTruncated, and log.body_limit now bounds what the log keeps and what /log returns. /routes and /scenarios count hits with lastStatus and lastAt (DELETE /__apigenie/hits starts over). POST /__apigenie/stubs {"id": 42} makes a stub file of a logged exchange, written to dir or proxy.record_dir, or answered. GET /__apigenie/contract.json and /contract.xml download the report as --report and --report-junit would write it. For AsyncAPI documents, GET /__apigenie/asyncapi/channels and /asyncapi/clients list the channels and the WebSocket and MQTT clients, and POST /__apigenie/asyncapi/publish sends a message (given, or made as the schedule would) to a channel’s clients, answering 422 when it doesn’t match. A server of several services names the service of every item and event. The design is ADR-161 (Admin API).

  • apigenie-server serves several services at once, on one port with a prefix per service: several SPECs (apigenie-server pets.yaml orders.yaml events.yaml mounts /pets, /orders and /events) or the configuration’s services list, each with name, spec, mount (a path prefix, default /<name>; {host: ...}; {port: ...}, which a .proto file’s service needs) and its own proxy, stubs, scenarios, validation, security, operations and other document settings over the server’s. AsyncAPI WebSocket channels share the port; the MQTT broker is one per server, and services whose MQTT channels collide have their topics served under <name>/. Every event, admin API item (/routes, /scenarios, /overrides, /log, the status’s documents), HAR entry and report section names its service; GET /__apigenie/services lists them and ?service=NAME narrows any admin endpoint. The contract report has a section per service and a combined summary, to which --fail-on and --min-coverage apply. The startup line lists the services and their URLs. One SPEC is served as before. The design is ADR-159.

  • apigenie-generator server mcp (experimental) generates a Model Context Protocol server in TypeScript from an OpenAPI 3.x document, on the official MCP SDK (@modelcontextprotocol/server 2), so that AI agents call the API through typed tools: one tool per operation, named after its operationId, described by its summary and description, with a Zod input schema of its parameters and body. The tools call the API through a generated fetch client (src/client) and return the status and body of the response as structured content, with an output schema; failed requests and responses the document doesn’t describe are error results that keep the body. Binary bodies and multipart files are base64 strings. include_tags, exclude_tags and x-mcp: false select the operations; the base URL and the credentials of the security schemes come from environment variables (<PREFIX>_BASE_URL, <PREFIX>_API_KEY…). --mock adds npm run mock, serving the document with apigenie-server, and makes it the default base URL for demos. Configured under commands.generate.server_mcp; the design is ADR-158. The codegen conformance suite checks it at Levels 1 to 3, Level 3 driving the tools with an MCP client against apigenie-server --proxy-to. The fetch client generator exposes how its functions call the operations (client_fetch::Plugin::bindings()), and the operation selection of the Elixir and Go generators moved to openapi_generator.

  • apigenie-generator client elixir also takes AsyncAPI 3.x documents and generates an Elixir library for the counterpart of the described application (or, with perspective: application, the application’s Kafka library). Its WebSocket channels connect on Mint.WebSocket, with the Phoenix Channels protocol of server phoenix (topics joined on /socket/websocket, replies to refs, heartbeats) or with the plain frames of the document at the channels’ addresses: connect takes the address parameters and the ws binding’s query, send_* sends, a request function waits for its reply (matched by correlation id on plain connections), and what the server sends is decoded and given to the callbacks of a behaviour. Its Kafka channels have send_* and request_* functions on brod and a consumer group (:brod_group_subscriber_v2) per received operation, sharing the dispatch, decoding and replies of server phoenix’s pipelines; the library depends on snappyer, so that brod reads Snappy-compressed records. Configured under commands.generate.client_elixir.asyncapi; the design is ADR-154. The codegen conformance suite checks both perspectives at Levels 1 and 2 (with mix dialyzer) and, at Level 3, runs it against server phoenix: the WebSocket channels in both framings, the plain one through apigenie-server --proxy-to ws://…, and the Kafka channels both ways on the broker of the workflow’s service container.

  • apigenie-generator server phoenix also takes AsyncAPI 3.x documents and generates a Phoenix application of their WebSocket and Kafka channels. Each WebSocket channel is a Phoenix channel (its address as the topic, /tracking/{shipmentId} as tracking:<shipmentId>, joined on /socket/websocket) and also a plain WebSocket at its address, whose frames are the messages of the document; both decode the address parameters, the ws binding’s query and the received messages into model structs before a behaviour’s callbacks get them, and the channel module’s push_* and broadcast_* functions send. A request’s callback returns its reply, which gets the request’s correlation id. Each received Kafka operation is a Broadway pipeline (broadway_kafka) dispatching records by ce_type, payload property or decoding to the callbacks of its behaviour, with on_unknown and on_invalid hooks (a replier’s :ok sends no reply) and batching from x-apigenie-broadway of the Kafka binding or the configuration; each sent operation has send_* functions on brod, and requests wait for their replies by correlation id on the reply topic; the project depends on snappyer, so that brod reads Snappy-compressed records. MQTT, AMQP and HTTP channels are skipped with an info line, Avro payloads with a warning. Configured under commands.generate.server_phoenix.asyncapi (perspective: counterpart generates the Kafka counterpart); the design is ADR-154. The codegen conformance suite checks both perspectives at Levels 1 and 2 (mix compile --warnings-as-errors, mix format --check-formatted, mix credo --strict) and, at Level 3, the plain WebSockets through apigenie-server --proxy-to ws://… and the application against its counterpart on the Kafka broker of the workflow’s service container.

  • apigenie-generator server absinthe generates an Absinthe server of a GraphQL schema: a Mix project on Absinthe 1.12, Absinthe.Plug, Plug and Bandit. Its schema module imports the SDL shipped with the project (import_sdl, descriptions, deprecations, defaults and @oneOf included), and hydrate/2 attaches:

    • resolvers: a behaviour per object type with a callback per root field and per field with arguments, calling the implementation configured for it (config :app, App.Resolvers.Query, impl: MyApp.Query), an error without one;
    • the resolution of interfaces and unions (__typename or an implementation);
    • parse and serialize stubs of the custom scalars.

    Custom directives are declared in a prototype schema. An optional Dataloader source is added with dataloader, and the router serves the endpoint and, in dev, GraphiQL. Subscriptions get resolvers but no transport. Configured under commands.generate.server_absinthe; the design is ADR-152. The codegen conformance suite checks it at Levels 1 to 3 (mix compile --warnings-as-errors, mix format --check-formatted, mix credo --strict, and the application answering apigenie test and the fixture’s dataset with the data of apigenie-server).

  • The foundation of GraphQL code generation (ADR-151): graphql_generator reads a GraphQL schema into a target-neutral model (types with their extensions merged, fields, arguments, enum values, input types, scalars, directives, descriptions, deprecations and default values) for the GraphQL targets of apigenie-generator, and the codegen conformance suite gains a GraphQL fixture: the Carpet Express GraphQL API (data/fixtures/graphql/carpet-express.graphql), the GraphQL face of the OpenAPI and AsyncAPI fixtures, with its feature catalog, field coordinates (Query.shipment) as probe identifiers, and a dataset of operations whose answers from apigenie-server back the Level 3 of GraphQL servers.

  • apigenie-generator server elixir-grpc generates an Elixir gRPC server of a .proto file set on elixir-grpc (grpc_server 1.0) and protobuf-elixir 0.17: the modules of the messages, enums and services as protoc-gen-elixir writes them (no protoc needed), a behaviour per service with a callback per method (client and server streams as Elixir enumerables), a GRPC.Server calling the implementation configured with config :app, <Service>, impl: ... (UNIMPLEMENTED without one), the endpoint and the application (mix run --no-halt, port 50051 or PORT). apigenie-generator client elixir given a .proto file generates a gRPC client on grpc 1.0 and Mint: the same modules with the stubs, and a module per service with a typed function per method returning {:ok, response} or {:error, %GRPC.RPCError{}}, deadlines and metadata as options. Configured under commands.generate.server_elixir_grpc and commands.generate.client_elixir.protobuf; the design is ADR-153. The codegen conformance suite checks both at Levels 1 to 3 (mix compile --warnings-as-errors, mix format --check-formatted, mix credo --strict, mix dialyzer; the server under apigenie test, the client calling all 24 methods through apigenie-server).

  • Protobuf code generation starts: the protobuf_generator library gives the Protobuf targets of apigenie-generator a model of a .proto file set (the file and its imports, found with -I/--proto-path and among the embedded google/protobuf, google/api and google/rpc files) on the resolved descriptors of the Protobuf runtime: messages with their nesting, fields with their cardinality, oneofs, maps, enums, services and the streaming kind of their methods, comments, deprecations and (google.api.field_behavior). Proto3 only. The codegen conformance suite has a Protobuf fixture, the gRPC services of Carpet Express (data/fixtures/protobuf: 7 services, 24 methods of all four streaming kinds, a catalog of 42 features), and runs Protobuf servers under the gRPC contract tester and clients through apigenie-server as a validating gRPC proxy. The design is ADR-150; Connect-ES and connect-go are the next targets.

  • The Compatibility page states the 1.0 promise: what semantic versioning covers from 1.0.0 (commands, options, configuration keys, environment variables, exit statuses, JSON report versions, the docs bundle, the apigenie-server admin API, the x-apigenie-* extensions), what it doesn’t, and how a removal is deprecated first. The --help of apigenie, apigenie-generator and apigenie-server links to it. The design is ADR-155.

  • apigenie-catalog, a fourth binary (experimental), keeps the API catalog of a repository. Every archive, the .deb and .rpm, the npm package and Homebrew install it next to apigenie, apigenie-generator and apigenie-server; --version and --help of each binary name it, and apigenie catalog ... prints the apigenie-catalog command to run instead (catalog). The design is ADR-160.

  • apigenie-catalog list [DIR] lists the API specifications of a repository with no configuration: every OpenAPI, AsyncAPI and Swagger document, Protobuf file and GraphQL schema that git tracks or doesn’t ignore (every file outside git), less catalog.exclude, with its name (from the catalog section, info.title, the Protobuf package or the file name), format and version, owner, path and the commit that last changed it. --json writes a report with formatVersion and a published schema, apigenie-catalog-report.json.

  • apigenie-catalog init [DIR] writes a catalog section to apigenie.yaml (created at the root of the repository when there is none) with one entry per specification: name, path and an owner: null placeholder. Running it again adds new specifications, keeps edited entries and their comments, and reports the entries whose files are gone (--prune removes them); --dry-run prints the file. Only the section’s lines change in an existing YAML file.

  • The catalog section of the configuration file (specs with path, name and owner; exclude) is part of the configuration schema, so config check, config --for catalog and editors know it (configuration).

  • apigenie-server keeps documents in a store that scripts share (see Document store). Scripts get ctx.db.<collection> with insert, get, find(where, {limit, offset, sort}), count, all, update (a JSON merge patch), replace, delete and clear; where takes equality and $eq, $ne, $in, $gt, $gte, $lt, $lte and $contains on fields and paths (address.city), and documents get the next id unless they bring one. ctx.state is unchanged. With db.crud: auto (or --crud), an OpenAPI document’s /shipments and /shipments/{id} are served from the store without a script: POST stores the body (its readOnly properties generated, the document checked against its schema), GET lists with the operation’s own paging, filter and sort parameters (envelopes get total and friends), PUT, PATCH (a merge patch) and DELETE change it, and an unknown id is 404; x-apigenie-mock: {resource: ...} names a resource (always served), binds it to a schema or switches it off. Writes that don’t match the schema are reported, or refused with --strict. Collections are seeded from the files of db.seed, else the list operation’s example, else db.generate documents generated with the run’s seed, and ctx.db.reset() or POST /__apigenie/db/reset puts them back. A request’s X-Apigenie-Test header gives each test run a namespace of its own that starts from the seeds. The admin API shows the store (GET /__apigenie/db, /db/{name}), replaces or clears a collection, and takes and restores named snapshots (PUT /__apigenie/db/snapshots/{name}, POST .../restore). --db-file (db.file, env APIGENIE_SERVER_DB_FILE) keeps the store in a JSON file across runs, written when the server stops or on POST /__apigenie/db/save; writes are db events in the log. With several services, each has a store of its own (and --db-file is refused). Configured under commands.mock.db; the design is ADR-157.

  • apigenie-generator server phoenix generates a Phoenix 1.7 application of an OpenAPI 3.x document, on Bandit and Jason, without Ecto, HTML or OpenApiSpex: a router with a route per operation (static segments first, QUERY and COPY through match, segments with several parameters split by the runtime), a behaviour per tag whose callbacks receive the decoded parameters and body, and a controller per tag calling the configured implementation (501 without one). Requests are checked before they reach it: missing credentials give 401, parameters and bodies that don’t match their schemas 400, other media types 415, all as problem documents; credentials go to an optional authenticator. mix phx.server serves it. Configured under commands.generate.server_phoenix; the design is ADR-131. The codegen conformance suite checks it at Levels 1 to 3 (mix compile --warnings-as-errors, mix format --check-formatted, mix credo --strict, and apigenie test against mix phx.server).

  • apigenie-generator client elixir generates an Elixir client of an OpenAPI 3.x document on Req and Jason: a Mix project with a module per tag and a function per operation, op(client, [body,] params \\ [], opts \\ []), returning {:ok, body} (the response decoded into its model, a list of them, text, bytes or nil) or {:error, %ApiError{}}, and a ! variant raising the error. Parameters are a keyword list encoded in their styles (deepObject, delimited arrays, content parameters as JSON), bodies are model structs or maps sent as JSON, forms or multipart parts (files, lists of files), and new/1 takes the base URL, Req options for retries and timeouts, and credentials for every security scheme: API keys in headers, the query or cookies, HTTP bearer and basic, OAuth2 and OpenID tokens, or a supplier called before each request. Configured under commands.generate.client_elixir; the design is ADR-131. The codegen conformance suite checks it at Levels 1 to 3 (mix compile --warnings-as-errors, mix format --check-formatted, mix credo --strict, mix dialyzer, and every operation called through apigenie-server --proxy-to).

  • Elixir models for the coming Elixir targets (client elixir, server phoenix): one module per schema with a struct, @type t and new/1, new!/1 and to_map/1 decoding and encoding JSON maps; string enums as atoms, oneOf/anyOf with a discriminator dispatching on the tag, allOf flattened, dates, times and base64 decoded, additionalProperties as a map field. The code passes mix format --check-formatted and mix compile --warnings-as-errors, which WITH_ELIXIR_TESTS checks (ctest -L elixir); the design is ADR-131.

  • apigenie i18n check compares a translation file with the texts of a spec, Protobuf or GraphQL file (or an i18n extract output) and fails unless every text is translated and every key is a text; it reports missing, empty, unknown and invalid keys and the coverage, as text or JSON (--format json, --allow-empty) (i18n).

  • apigenie i18n sync updates a translation file after the document changed: it keeps the translations of unchanged texts and reports the new texts, the changed ones whose translations may be stale (--drop-changed drops them) and the removed ones (--prune drops them).

  • i18n extract extracts more OpenAPI texts: the summary and description of Reference Objects (3.1+), keyed at the $ref, the summary of tags and name of servers (3.2), the descriptions of OAuth scopes, and the titles and descriptions of schemas under $defs, dependentSchemas and additionalItems.

  • The openapi_i18n WebAssembly module applies translations (apply), compares and updates translation catalogs (compare, sync), and its Translator lists, replaces and removes locales and falls back on other locales (translateWithFallback).

  • apigenie generate ... exits with 2 and prints the command to run instead: code generation moved to apigenie-generator; run: apigenie-generator <the same arguments>. apigenie mock ... points to apigenie-server the same way.

  • The --help of each binary ends with the exit statuses and a line naming the other two binaries and what they do.

  • Experimental commands, outside the 1.0 compatibility promise, say so in their --help and on the first line they print (on the standard error, so that their output stays usable): apigenie convert (one target) and apigenie-generator schema zod (no conformance Level 3 yet). Their reference pages and the CLI overview carry the same label; docs/experimental.md lists what is experimental and why.

  • Every JSON report has a top-level formatVersion (1): lint, diff (status, changelog, detailed), changelog, the apigenie test report and the summary line of apigenie test --formatter json, the contract report of apigenie-server, and the docs bundle manifest (next to apigenieDocs). Adding fields keeps the version; removing or changing one bumps it (ADR-140).

  • JSON Schemas of these formats, published at https://apigenie.pl/schemas/ (apigenie-lint-report.json, apigenie-diff-report.json, apigenie-changelog-report.json, apigenie-test-report.json, apigenie-contract-report.json, apigenie-docs-manifest.json) and linked from each command’s reference page, and a Compatibility page listing the versioned outputs.

  • XML request and response bodies (application/xml, text/xml, +xml) are checked by apigenie-server, its validating proxy and apigenie test: a body is parsed, mapped to the JSON value its schema’s xml objects describe (name, namespace, prefix, attribute, wrapped, OpenAPI 3.2 nodeType) and validated like a JSON body. A body that isn’t well-formed is request.body.malformed (400) or response.body.malformed with its line and column; a root element other than the schema’s is reported too. The mock answers XML media types with XML, and apigenie test --generate sends XML bodies, written the same way (XML bodies). The design is ADR-130.

  • apigenie test streams. GraphQL subscriptions go over WebSocket with the graphql-ws protocol (graphql-transport-ws) instead of a single POST. The request’s headers go on the handshake and in connection_init. The events are the response (?? body $[0].data..., response.messages), and each is checked against the operation’s selection set at next[i]. # @expect and # @timeout end the subscription, and --generate adds a request per Subscription field.

    gRPC client- and bidi-streaming methods are no longer skipped. Their messages are written as for WS regions, with === between them and === wait-for-server to wait for an answer. Each message is checked against the .proto file at message[i], and a bidi call’s body is the array of its answers. --generate sends two messages to each stream.

    Both count in the coverage. The design is ADR-109 and ADR-111 (amended 2026-10-12) (GraphQL subscriptions, gRPC requests).

  • The OpenAPI model reads 3.2’s xml.nodeType (x-nodeType in 3.0 and 3.1 documents) and writes it back.

  • Internal: the xml library, a strict XML 1.0 + Namespaces 1.0 parser (a pull reader and an immutable tape DOM like jv’s, positions on every node) and a streaming writer with escapeText/escapeAttribute. DTDs and entities other than the five predefined ones are refused, so external entities and entity expansion (“billion laughs”) are errors; depth, attribute count and input size are limited. The design is ADR-129.

  • Colored output in Windows consoles: the CLI, apigenie-generator and apigenie-server style their output when it goes to a console, as they do in terminals elsewhere, and still not when it is redirected or NO_COLOR is set.

  • lint --watch prints the files whose change triggered the run (on standard error, so that the report stays parseable), and no longer misses edits made while a run is in progress.

  • Faker providers take typed and named arguments and describe themselves. x-faker accepts {"string.alpha": {length: 6, casing: upper}} and {provider, args, options}, the GraphQL mock’s args and graphql.fields take a list or an object, and mock scripts call fake('internet.email', {provider: 'example.org'}). 295 providers (from 215) cover a person’s sex and country, an email’s names and host, phone formats, string casing, address countries, date formats and more (faker reference).

  • Faker records: person.profile, location.address, finance.card, finance.currency and commerce.product make related values together (one sex for a person’s names, a username and email of those names, one country for an address with its time zone, one issuer for a card), written as objects in mocked responses and generated data.

  • helpers.fromPattern (alias helpers.fromRegExp) and helpers.replaceSymbols, and date.between, date.birthdateByYear and date/time formats for dates.

  • OpenAPI formatting at every depth. The formatter recognizes the objects of OpenAPI 3.0, 3.1 and 3.2 by walking the document from its root: schemas under properties, items, allOf, $defs, if/then/else and every other applicator, path items and operations of callbacks at any depth, 3.2 query operations, additionalOperations, itemSchema, prefixEncoding and itemEncoding, the content and examples of headers, and individual OAuth flows (oauthFlow). Example payloads and extension values are never taken for OpenAPI objects. The design is ADR-149.

  • Independent orders for named maps and raw values: componentNames, webhooks, callbacks, callback, content, headers, properties, scopes, extension and exampleValue, so that, for example, component schemas can be sorted by name while the properties of each schema keep their order.

  • Layout options for OpenAPI: line_width, newline (lf, crlf), final_newline and document_marker, backed by new jv write options.

  • style_overrides give chosen fields their own YAML styles, by field name and object kind, such as literal descriptions or flow required lists.

  • apigenie format --openapi-fragment KIND formats files holding a single OpenAPI object (a schema, a path item, a response…) with the rules of that object.

  • A typed options API (openapi_format::FormatOptions, Formatter::setOptions) for C++ consumers of the formatter library, validated like the configuration file.

  • apigenie config prints the effective configuration (YAML, or JSON with --json): the values of the file, those set by APIGENIE_SERVER_* environment variables (or their APIGENIE_MOCK_* fallbacks), and the defaults of the settings the file leaves out. --origin annotates each value with the file, the variable or default that set it, and --for <section> limits it to one section (lint, format, diff, generate, generate.<target>, mock) (config).

  • apigenie config check [FILE] validates a configuration file and exits with 2 on errors, reporting every unknown setting at once with a suggestion for a misspelled name, and the allowed values of a setting with a fixed set.

  • apigenie config schema [--for <section>] [--standalone-server] [--out FILE] prints the JSON Schema (draft-7) of the configuration file, of one section, or of a standalone apigenie-server.yaml. Each release publishes them at https://apigenie.pl/schemas/apigenie-config/<version>.json and .../apigenie-server-config/<version>.json, with latest.json, for # yaml-language-server: $schema=... comments in editors.

  • --no-config for lint, format, diff, changelog and every apigenie-generator target.

  • MQTT 5 message properties end to end. apigenie-server --proxy-to mqtt:// relays user properties, payload format indicator, content type, response topic, correlation data and message expiry both ways and checks them against the AsyncAPI MQTT bindings (0.2.0): the new contract check message.properties.invalid (error) for a payload format, content type, correlation data or response topic other than the message binding says, and a missing or longer message expiry than the operation binding’s; the user properties are checked as the message’s headers (message.headers.invalid); a reply’s header correlation id and Correlation Data are compared with the request’s. The mock’s own broker sends the properties its bindings and headers state, checks its MQTT 5 clients’ and answers requests on their reply address or Response Topic. apigenie test connects with MQTT 5 (version: 5, 3.1.1 or auto), sets properties with # @property content-type ..., response-topic, correlation-data, payload-format-indicator, message-expiry-interval and user NAME VALUE, subscribes to its response topic, pairs replies by reply-address user property or Response Topic, and --generate writes the properties of the bindings. Reports count the messages whose properties were checked (summary.properties). The design is ADR-112 and ADR-110 (amended); see test and server.

  • apigenie-server --record DIR records the session: every exchange the server answered, of every service and every protocol (HTTP, GraphQL operations, gRPC calls with their streams, WebSocket and MQTT messages of both sides), in the order it happened, each as a file under DIR/<service>/ numbered across the session, with session.yaml naming the services (with the SHA-256 of their documents) and listing the exchanges. Each exchange carries sequence, service and test, the test being the request’s X-Apigenie-Test header, a gRPC call’s x-apigenie-test metadata, or the mark set with POST /__apigenie/record/mark {"test": ...}. HTTP and gRPC exchanges keep the stub forms of --record-dir, so --stubs DIR/<service> still loads them; GraphQL operations the example form; messages a form of their own. POST /__apigenie/record/start and /stop record a part of a run, GET /__apigenie/record says whether one is on, and the dashboard’s Traffic screen has Record session and Save session. --record-redact (and record.redact) hides more headers and, by JSON pointer, values of bodies and messages. apigenie-server session export DIR --har FILE writes a session’s HTTP exchanges, GraphQL operations and gRPC calls as HAR 1.2, in order, with the test of each entry. --record-dir and --record-har are unchanged. The design is ADR-168 (Session recording).

  • apigenie-server --replay DIR replays a recorded session for a test suite, without the real servers: each request gets the next unused recorded exchange of its test that matches it, so the same request twice in a test gets the first recorded answer, then the second. The test is the X-Apigenie-Test header (x-apigenie-test metadata for gRPC); untagged requests share one test drawing from the whole session; POST /__apigenie/replay/reset {"test": ...} restarts a test’s counters. Matching leaves out dates, the readOnly fields of OpenAPI request bodies, volatile headers and what --replay-ignore HEADER|/POINTER names. --replay-missing says what a request none matches gets: fail (a 502 problem naming the closest recording and the differences, the default), generate (the mock’s answer) or passthrough (--proxy-to’s, and with --replay-update the run replaces the session). Every protocol replays: HTTP, GraphQL operations and subscriptions, AsyncAPI WebSocket, MQTT and HTTP channels (the recorded messages as the server’s side, in order) and gRPC calls, bidirectional streams in their recorded interleaving. When the server stops it reports the exchanges never replayed and the requests that matched none, and exits with status 5 if there are any; GET /__apigenie/replay gives the report at any time, and replay: configures it all. The reference page has a Playwright example. The design is ADR-169 (Session replay).

Changed

  • apigenie and apigenie-server are about 360 KB smaller: Mbed TLS is built with only what their TLS uses (TLS 1.2 and 1.3 with certificates, AES-GCM and ChaCha20-Poly1305, x25519 and the NIST P-256, P-384 and P-521 curves). HTTPS, WSS, gRPC and MQTTS servers that offer only static RSA or DHE key exchange, or only other ciphers, can no longer be reached by --proxy-to or apigenie test; the handshake fails with a fatal alert. ADR-104.
  • schema-format-type-mismatch and schema-format-defined follow the OpenAPI format registry, in the OpenAPI, AsyncAPI and Swagger linters, and accept the formats of openapi.custom_formats. schema-format-type-mismatch used to check a fixed list of formats; it now checks every registry format (int8 on a string, date-time-local on an integer, …), so existing specifications can get new errors. Its messages say which values the format is for (Schema type and format do not match: "int8" is a format of number values). Some pairs it used to report are accepted now, as the registry allows them: int64 and uint64 on strings, decimal on strings and numbers, an integer format on type: number. schema-format-defined accepts the registry’s integer formats (and unixtime) for integers and its number formats (decimal, …) for numbers. The Swagger linter keeps to the formats Swagger 2.0 defines.
  • Declarative lint rules with severity: info, hint, 2 or 3 report hints instead of warnings.
  • apigenie-generator server orpc meets the codegen conformance bar of the other servers: Level 1 runs on OpenAPI 3.0, 3.1 and 3.2, and Levels 2 (tsc, ESLint) and 3 (apigenie test against the generated server) are wired, with only QUERY and COPY, which oRPC doesn’t route, expected to fail; it is no longer listed as not settled in docs/experimental.md. The generated server now reads what oRPC doesn’t decode by itself: cookie parameters, content: application/json parameters, a path segment of several parameters (/labels/{labelId}.{extension}), JSON media types other than application/json and JSON Lines bodies (parsed by the server), form fields converted to their types, and file uploads; bodies and responses take every media type of the operation, with binary responses as Blobs and text/event-stream as an eventIterator() of the itemSchema. A discriminator’s defaultMapping selects the default option, operations with servers of their own are also served at their paths, 4XX/5XX ranges and default responses become errors, writeOnly properties are left out of responses, and the handler stubs and server.ts pass typescript-eslint’s recommended rules (regenerate server.ts and the handlers, which are written once, to get them).
  • The Zod schemas of generate schema zod, server mcp and server orpc check unevaluatedProperties: false against the properties the schema and its $ref, allOf and conditional schemas declare, instead of ignoring it with a warning: closed allOf subtypes such as Person or ParcelShipment reject unknown properties.
  • apigenie-server‘s status (GET /__apigenie) counts the clients’ requests only: the admin API’s and the dashboard’s own exchanges and CORS preflights are no longer in requests (ADR-167).
  • XML bodies of apigenie-server, its validating proxy and apigenie test (ADR-130) changed in three ways, found serving SOAP envelopes (ADR-164):
    • Child elements are written in the order the schema declares its properties, as XML Schema sequences require. Before, generated values put required properties first.
    • An element with the local name of a property but in another namespace than the property’s xml.namespace is no longer taken for it. It is kept as {namespace}name, so additionalProperties: false reports it and the property counts as missing.
    • A request without Accept (or with */*) gets its response in the media type of its body when the response has it: a SOAP 1.2 request (application/soap+xml) gets a SOAP 1.2 envelope rather than the first media type listed.
  • diff rates a component schema by where it is used. An OpenAPI component schema referenced only from parameters and request bodies (following the references between schemas, in either document) gets the request rules, one referenced only from responses the response rules; before, every component schema got the stricter of the two, so removing a property from a request-only schema, or adding a required one to a response-only schema, was breaking. Schemas used both ways, or not at all, are rated as before. Callbacks and webhooks swap the directions, as they always did.
  • The GraphQL contract checks of apigenie test and of apigenie-server’s GraphQL proxy warn about field errors (response.field-error): every error of an operation the server ran, next to data that is null or partial. Before, such answers passed silently. A server answering every field with an error passed apigenie test --generate with no violations. --fail-on warnings fails on them; --severity or contract.severity changes the level. Errors without data stay response.request-error.
  • apigenie-mock is renamed apigenie-server, with apigenie-mock and the APIGENIE_MOCK_* variables kept as deprecated aliases until 1.1, and the mykulyak/apigenie-server Docker image (see Upgrading).
  • i18n apply translates only the texts i18n extract extracts from OpenAPI, Swagger and AsyncAPI specs: a key pointing to another string, such as an operationId or a URL, is now reported as addressing nothing (a warning, an error with --strict) instead of being applied, and a key pointing to an object is no longer an error. The spec must be valid.
  • The OpenAPI reader reads the schemas under $defs, dependentSchemas and additionalItems as schemas, so lint rules, generate and the other commands see them; $defs and dependentSchemas are written after patternProperties.
  • Loading translations into the Translator (apigenie-generator docs --i18n) is atomic: an invalid file changes nothing, and the error names the locale and the key. Empty translations are skipped, so the source text is shown.
  • JUnit reports (--report-junit of apigenie-server and apigenie test) escape element content and attribute values separately: quotes in <failure> and <system-out> text are written as they are, line breaks in attribute values as &#xA;, and characters XML can’t carry as U+FFFD instead of ?.
  • response.body.unvalidatable no longer covers XML bodies, which are now checked.
  • File errors name their cause: “Failed to open file: openapi.yaml (not found).” instead of “Failed to open file: openapi.yaml.”.
  • The notes “not checkable over MQTT 3.1.1” of apigenie test appear only for regions that speak MQTT 3.1.1; over MQTT 5 header correlation ids and reply addresses are checked. A broker’s MQTT 5 refusal names its reason code (reason code 0x86 (bad user name or password)).

Deprecated

  • version in the contract report of apigenie-server and in the apigenie test report: it duplicates formatVersion and will be removed in the next release.

Removed

  • apigenie credits no longer lists TinyXML2, which API Genie stopped using long ago.

Fixed

  • Bidirectional gRPC session replay preserves recorded response order when several requests arrive before the initial replies are sent, including when mock latency delays those replies.

  • Format checks of numbers: format: int32, int64, float and double were only applied to strings, so they rejected every string and never checked a number. A format now applies to its JSON types: int32 checks that a number is an integer in range (1.0 counts), and a string under it is left to type. format: password no longer rejects an empty string.

  • The gRPC answers apigenie-server generates set the fields marked (google.api.field_behavior) = REQUIRED, messages, lists and maps included, and not to their default values, so a validating proxy in front of the mock no longer reports response.message.incompatible for its own answers; fields marked INPUT_ONLY are left out. The requests of apigenie test --generate for .proto files set REQUIRED fields too and leave out OUTPUT_ONLY ones. A REQUIRED field whose message would contain itself stays optional.

  • The gRPC checks of apigenie-server (mock and --proxy-to) and apigenie test report a field whose wire type isn’t its declared type’s, such as a string where the .proto file declares an int64. Decoding drops such a field, as protobuf parsers do, so the message used to pass as if the field were absent. It is now request.message.incompatible or response.message.incompatible (code wire-type), at the field’s location, and the mock in enforce mode refuses such a request with INVALID_ARGUMENT.

  • @mykulyak/apigenie-docs is published on npm (2026-10-11, docs-0.1.0). The 0.9.0 release notes said that the web components were “on npm” before they had been published, and under their old name. The release gate now installs and builds a generated docs project against the npm registry, so a CLI can’t be released while the package it depends on is missing or out of range.

  • format --output with more than one input, or with an output file of an unknown extension, failed with an empty error message; it now says what’s wrong.

  • license --library with an unknown name printed its error on the standard output.

  • apigenie-server answered Accept: application/xml with a JSON body under an XML Content-Type.

  • Paths longer than 260 characters work on Windows, without the system-wide long-path setting.

  • test import --output no longer fails when the output directory’s name contains a dot (--output api.v2): the directory used to be mistaken for a file and left uncreated.

  • lint --fix and the other writers that replace a file in place create their temporary file exclusively, next to the target, and never leave it behind.

  • Faker’s IBANs have valid check digits, routing numbers a valid ABA checksum, book.isbn a valid ISBN-13 check digit, and Polish, US, Spanish and British national ids follow their rules.

  • Faker’s full names use one sex for first and last names, female middle names are generated, git commit dates name their own weekday, and usernames get their ., _ and - separators back.

  • JSON output writes empty objects and arrays as {} and [] instead of spreading them over three lines with a blank one in between (format, diff and changelog JSON reports, lint --formatter json and sarif, docs bundles).

  • property_order.paths was ignored: /paths was ordered by the default order.

  • property_order.reference was never applied: Reference Objects are recognized by their $ref, except in schemas, where $ref is a keyword, and in path items that have their own operations.

  • The error for an openapi.custom_formats that is not an array said an object was expected; errors in the configuration file now name the file and the position.

  • apigenie-server named no file in its error for an invalid configuration file that it found itself.

0.9.0 (2026-10-10)

API Genie 0.9.0 translates OpenAPI, Swagger 2.0, AsyncAPI, Protobuf and GraphQL files, and its mock server (apigenie-mock, now apigenie-server) mocks and contract-checks HTTP, WebSocket, MQTT, gRPC and GraphQL APIs. The new GitHub Action mykulyak/api-genie-action installs the CLI in workflows on Linux, macOS and Windows.

Highlights

  • Translations for all five standards. apigenie i18n extract and i18n apply work on OpenAPI, Swagger 2.0, AsyncAPI, Protobuf and GraphQL files: the texts are extracted keyed by JSON pointers, declaration names or schema coordinates, and translations are put back without touching the rest of the file (i18n).
  • Contract testing across OpenAPI, Swagger, AsyncAPI, gRPC and GraphQL, with TLS. apigenie test runs .http files (HTTP, gRPC, WebSocket, MQTT and GraphQL requests) and generated requests against a live API, and apigenie-mock --proxy-to sits between clients and a real server or broker (HTTP, WebSocket, MQTT, gRPC, GraphQL), checking every request and response against the contract. Both report coverage, JSON and JUnit reports, and fail CI with --fail-on and --min-coverage. TLS is supported throughout: the mock serves HTTPS, WSS and gRPC over TLS (--tls-cert, --tls-key), and requests and upstreams can be https://, wss:// and mqtts://, verified against the system’s CA certificates (--ca-cert, --insecure) (test, mock server).
  • Code generation for five stacks. Clients and servers for TypeScript (fetch, Express, oRPC, WebSocket, Kafka on KafkaJS, Zod), Python (httpx, FastAPI, FastStream), Go (net/http, Kafka), Java (Spring clients and servers, Kafka) and C# (HttpClient, ASP.NET Core, Kafka), from OpenAPI 3.0–3.2 and, for event-driven APIs, AsyncAPI 3.x. Every target is checked by a conformance suite: snapshots, the target’s own compiler and linter, and a run against apigenie-mock --proxy-to. The generators are a separate executable, apigenie-generator, installed with the CLI (apigenie-generator).
  • Web components for every spec type, and generate docs. The apg-* custom elements (@mykulyak/apigenie-web-components on npm) document OpenAPI, Swagger 2.0, AsyncAPI, Protobuf/gRPC and GraphQL APIs, with Try it out for HTTP and GraphQL, built on the same engine as the CLI compiled to WebAssembly. apigenie-generator docs compiles any of these files, several at once, into a docs bundle that the components render into a static site (npm install && npm run build), with translations, the git changelog and example files; it replaces generate doc html (see Upgrading).

Upgrading

  • apigenie generate is now apigenie-generator. The code generators moved out of apigenie into a separate executable, apigenie-generator, installed next to it by every archive and package, the npm package and Homebrew, and included in the mykulyak/apigenie Docker image (docker run --entrypoint apigenie-generator ...). Drop generate from the command line: apigenie generate client fetch spec.yaml -o out becomes apigenie-generator client fetch spec.yaml -o out. The options and the commands.generate section of the configuration file are unchanged; apigenie generate fails as an unknown command. The banners of generated files name the new command. The entries below still call the generators generate <target>. The design is ADR-123.
  • generate doc html is replaced by generate docs, with no alias. apigenie-generator docs openapi.yaml -o docs replaces apigenie generate doc html openapi.yaml --output docs; it takes several inputs of mixed families (OpenAPI, Swagger 2.0, AsyncAPI, GraphQL, Protobuf) and makes one site, and -c/--config, --i18n LOCALE=FILE, --package-name and --package-version keep their meaning. The output is no longer an Astro project but a docs bundle (apigenie-docs.json and documents/<id>/document.<locale>.json, ADR-122) with a package.json that depends on @mykulyak/apigenie-web-components instead of @mykulyak/api-genie-doc-html-template; npm install && npm run build still builds the static site, now into dist/, prerendered from the web components. --bundle-only writes the bundle alone, for a site built by other means. The CLI does not run Node. The configuration section commands.generate.doc_html is commands.generate.docs: html_title_prefix is title, i18n.default_locale and i18n.prefix_default_locale move to the top of the section (the locales come from --i18n), changelog keeps its name (also --changelog), and documents lists the inputs (file, id, title, proto_paths, examples). pages, operations, resources, search, sidebar, html_meta, template, openapi_schema, request_code_examples, use_request_body_examples, generate_response_schema, use_response_examples and fail_on_generator_error described the Astro layout and have no replacement: the site has the layout of <apg-api-reference> (navigation with search, one page per operation and schema), themed with the components’ CSS custom properties and ::part(). Generated Astro projects keep building with the template they depend on, but nothing regenerates them: run generate docs into a new directory.
  • New dependency: Mbed TLS. apigenie and apigenie-mock link Mbed TLS 3.6 (Apache-2.0) for https://, wss:// and mqtts:// connections; release binaries grow by about 0.9 MB. Builds from source get it from vcpkg (mbedtls >= 3.6.2). Verifying servers needs the system’s CA certificates: the Docker images now include them; in your own minimal images install ca-certificates. apigenie credits and apigenie license --library mbedtls list it.
  • Telemetry is opt-in. The CLI no longer writes .apigenie.telemetry in the current directory, and sends nothing. Set APIGENIE_TELEMETRY_FILE to a path to keep a local telemetry log; old .apigenie.telemetry files can be deleted.
  • i18n has subcommands. apigenie i18n FILE --output DEST becomes apigenie i18n extract FILE --output DEST.
  • lint fails on errors. lint exits with status 1 when issues of severity error remain (after --fix), so CI jobs that ran it only for its report fail now; lower the severities in the configuration to keep them passing.
  • apigenie-mock --stubs with a GraphQL schema or an AsyncAPI document exits with status 2 instead of ignoring the option.

Added

  • generate docs documents OpenAPI, Swagger 2.0, AsyncAPI, GraphQL and Protobuf files, several at once, as one static site: it writes a docs bundle (apigenie-docs.json; per document and locale the resolved document, or the documentation model of AsyncAPI, GraphQL and Protobuf files; with --changelog the git changelog, as apigenie changelog computes it; the example files next to a GraphQL schema and those listed in the configuration) and the npm project that renders it with @mykulyak/apigenie-web-components (npm install && npm run build): prerendered pages with declarative shadow DOM that hydrate in the browser, Try it out, a sitemap, a locale switcher for the --i18n locales, a changelog page and the examples. --bundle-only writes the bundle alone. Internal: the docs_bundle library (compileDocsBundle), changelog_helpers (the changelog of any family, shared with the changelog command), and graphql_describe and protobuf_describe, which write the documentation models that the graphql and protobuf WebAssembly modules used to write themselves, as asyncapi_describe does for AsyncAPI. The design is ADR-122.

  • generate server spring (OpenAPI) routes QUERY operations and additionalOperations such as COPY: their handlers carry @ExtensionMethod("QUERY"), which the generated ExtensionMethodConfiguration matches through a custom request condition of the request mapping (WebMvcRegistrations, or WebFluxRegistrations with reactive=true). pipeDelimited and spaceDelimited array query parameters are split on their separator (@DelimitedParameter), and query parameters with JSON content are read as JSON into their model (@JsonParameter), which is null and left unvalidated when the parameter is absent; both converters are registered by ParameterConversionConfiguration. Request media types whose bodies have different types get a handler each, named as the methods of generate client java (updateShipment for application/merge-patch+json, updateShipmentWithJsonPatch for the application/json-patch+json array). The new ApiExceptionHandler answers invalid requests with a 400 Problem (type, title, status, detail and, for constraint violations of the parameters, errors with the name, path and message of each), where a ConstraintViolationException used to answer 500. Conformance Level 3 of the target only expects the x-internal operation, which servers leave out, to fail.

  • generate server aspnet (OpenAPI) falls back to the default mapping of a discriminator (defaultMapping, or x-defaultMapping before 3.2): unions are read and written by the converters of generate client csharp, written next to the abstract record and attached with [JsonConverter] instead of [JsonPolymorphic], so a missing or unmapped discriminator reads as the default type, and the discriminator may be anywhere in the object on .NET 8 too. Request media types whose bodies have different types get an endpoint and API method each, named as the methods of the C# client (UpdateShipment for application/merge-patch+json, UpdateShipmentJsonPatch for the application/json-patch+json array) and restricted to their media types with Accepts<T>(), so routing picks the endpoint by Content-Type; the JSON Patch array used to be bound as a ShipmentPatch object (400). Conformance Level 3 of the target passes with only the x-internal operation, which the server leaves out, expected to fail.

  • The codegen conformance runner decides that the x-internal operations of the fixture are expected to fail for servers that leave them out: omitsInternalOperations on a Level 3 entry of tests/conformance/targets.json (set for the ASP.NET and Spring servers) expects every request of those operations, the request set’s and the generated one, to fail, instead of hand-written expected entries; a server that starts serving one still fails the level.

  • generate client kafkajs and generate server kafkajs generate TypeScript (Node.js) packages for the Kafka channels of AsyncAPI 3.x documents, on KafkaJS 2.2.4: the server for the described application (KafkaApp, taking a handler per received operation, with start() and stop()), the client for its counterpart (KafkaClient, with connect(), listen<Operation>(handler) and close()); perspective swaps either. Every sent operation gets a sender with a send<Message>(message) method per message, which validates the message, writes the constant headers (e.g. ce_type), the key and the topic, and, for an operation with a reply, sends the request with a new correlation id unless it has one and resolves with the typed reply, matched by correlation id on one reply consumer per operation (ReplyTimeoutError when none arrives in time). Received operations get handler interfaces with on<Message>(message, record) and onUnknown, dispatched by ce_type header, payload property or deduction; repliers return the reply, sent with the request’s correlation id. Payloads are validated by dependency-free functions generated from the schemas (MessageValidationError), headers by their constraints. Avro payloads, topics with parameters and replies at runtime-expression addresses are skipped with a warning. Configured under commands.generate.client_kafkajs and commands.generate.server_kafkajs (layout, import_extension, perspective, validation); the design is ADR-120. The codegen conformance suite checks both commands in both perspectives at Levels 1 and 2 (tsc and ESLint) and, at Level 3, runs the client against the server on the Kafka service container both ways, as for Go and C#.

  • generate client ws and generate server ws generate the replies of operations whose reply is on the request’s channel: the client gets typed on<Message> handlers for the reply messages and, when the messages have a correlation id in their payloads, request<Operation>(payload, { timeoutMs }) resolving with the reply carrying the request’s id; the server session gets send<Message> methods for them and replyTo<Operation>(request, reply), which copies the request’s correlation id into the reply. On the AsyncAPI conformance fixture, requestPickup() and replyToRequestPickup(). Replies on another channel or at a runtime-expression address are still reported as unsupported.

  • apigenie test sends WS regions to wss:// and MQTT regions to mqtts:// (or ssl://) URLs, with ports 443 and 8883 by default. Their certificates are checked as for https://, with SNI and the URL’s host name, so --ca-cert and --insecure apply. MQTT regions send their username and password settings in CONNECT (MQTT 3.1.1). A setting the region doesn’t give comes from APIGENIE_MQTT_USERNAME and APIGENIE_MQTT_PASSWORD. A refusal names the reason (bad user name or password, not authorized). Credentials show as [redacted] in the console and the JSON and JUnit reports, as OAuth 2 secrets do, and a URL’s user:password@ is shown as ***@. These URLs used to fail with “speaks plain WebSocket/MQTT only”, and the credentials were noted as not sent. MQTT 5 is still not supported. The design is the amendment of ADR-110.

  • apigenie test with an AsyncAPI --spec checks HTTP requests against the document’s HTTP channels, with the code and checks of the validating proxy: the channel and operation by path and method (a receive operation’s HTTP binding’s method, GET for a send one) or by # @operation, a receive operation’s body, query and header fields against its messages and a 2XX response against its reply, a send operation’s response against its messages. The requests count for their operations in the coverage, which used to show them as not tested, and --generate sends one request per HTTP channel operation (to --base-url, a ws:// URL taken as http://). A path of no HTTP channel is a note. Both apigenie test and apigenie-mock --proxy-to now check the query of an operation’s HTTP binding (operation.query.invalid) and the header fields of a message’s headers and HTTP binding headers, its traits’ included (message.headers.invalid); responses other than 2XX are still not checked. The design is ADR-118, which amends ADR-110 and ADR-112.

  • apigenie test runs httpYac’s OAuth 2 authorization instead of skipping it: Authorization: oauth2 [GRANT] [PREFIX] (and openid) with the client_credentials, password and refresh_token grants requests a token from <prefix>_tokenEndpoint (or the OpenID Connect discovery document of <prefix>_issuer) with the <prefix>_clientId, _clientSecret, _scope, _audience, _username, _password and _useAuthorizationHeader variables, keeps it for the run, requests it again (with its refresh token) when it expires, and sends Authorization: Bearer <token>. Token endpoints are reached like other requests, https:// included (--ca-cert, --insecure); a refused token request fails its request, which isn’t sent. The authorization_code, implicit and device_code grants are still skipped, with a reason naming the grant. With --spec, requests sent with a token are checked by the new request.authorization.oauth2-scheme and request.authorization.oauth2-scope warnings (the operation has no OAuth 2 scheme, the requested scopes aren’t its). Tokens, client secrets, passwords and Authorization credentials show as [redacted] in the output, the reports and warnings. apigenie test import maps Postman’s client credentials and password grants to these variables instead of disabling the requests. The design is ADR-117.

  • The asyncapi WebAssembly module (asyncapi.mjs, and asyncapi.web.mjs for browsers) exports describe(text, {fileName, files}). It returns the documentation model of an AsyncAPI 3.0 or 3.1 document as plain JSON ({"apigenieAsyncAPI": 1, …}):

    • servers, channels, operations and messages with their JSON pointers and source positions;
    • references followed and external files given in files bundled as apigenie asyncapi bundle does;
    • traits applied with JSON Merge Patch, and the defaults of the specification;
    • headers and payloads as schemas (other formats, such as Avro, kept with their schemaFormat), and a position table;
    • the problems found as issues.

    AsyncAPI 2.x is an error. It is built on the new asyncapi_describe library; the JSON Merge Patch of traits moved into the asyncapi library (asyncapi/traits.hpp), where the models generators use it too. ADR-119 has the design.

  • apigenie-mock, its validating proxy (--proxy-to) and apigenie test --spec take Swagger 2.0 documents: they convert them to OpenAPI 3.2 in memory, as convert --to openapi32 does, each time the document is read, and write no file. Locations stay those of the Swagger file: the lines of warnings, the lines and JSON pointers of x-apigenie-mock problems, and the new pointer of each operation in the contract reports. What the conversion can only approximate is told once at startup as info: lines with the Swagger file’s line and JSON pointer ({"event":"info"} with apigenie test --formatter json). Swagger 2.0 documents used to be refused with exit status 3.

  • apigenie-mock --proxy-to mqtt://host:port (or mqtts://, verified like https:// upstreams) with an AsyncAPI document contract-tests a real MQTT broker: the mock’s broker relays, each client getting a connection of its own to the broker with its identifier, version, clean start, keep-alive, will and credentials, and the broker’s CONNACK and SUBACK. Every PUBLISH is checked against the topic’s channel: what clients publish against its receive operations, with the QoS of their MQTT binding (message.qos.unexpected), what the broker delivers against its send operations (a channel without them: its receive operations and the replies sent there). In enforce mode invalid messages aren’t forwarded (MQTT 5 clients get reason code 0x99). Wills, refusals and an unreachable broker (server unavailable) are passed on, clients stay connected across reloads, the log has mqtt-connect and mqtt-disconnect events, and coverage, --report, --report-junit, --fail-on and --min-coverage work as for WebSocket. --proxy-to can be repeated to proxy a WebSocket server and an MQTT broker at once (proxy.target takes a list). The design is ADR-112, which supersedes parts of ADR-097.

  • An AsyncAPI document’s HTTP channels are proxied to the http(s):// or ws(s):// server of --proxy-to through the OpenAPI proxy’s transport and checked against the AsyncAPI document: a receive operation’s request body against its messages and a 2XX response’s body against its reply, a send operation’s response (GET) against its messages. In enforce mode invalid requests get 422 and invalid responses are replaced by 502; paths of no channel get 404 or 405.

  • The AsyncAPI checks have channel.parameter.invalid: a value of an address parameter (a topic level, a path segment) outside the parameter’s enum, for WebSocket, MQTT and HTTP channels.

  • mqtt::ClientConnection, an MQTT 3.1.1 and 5 client on the event loop (over TLS too), and broker hooks that decide CONNECT and SUBSCRIBE later and let the owner relay instead of delivering.

  • apigenie test runs GraphQL requests instead of skipping them: GRAPHQL URL requests of httpYac and the JetBrains HTTP client (the query, then its variables as JSON after a blank line; # @operation is the operationName) and POST requests whose body is a query are sent as GraphQL over HTTP, and JSON {"query": ...} bodies as they are. With a GraphQL schema as --spec (.graphql, .graphqls, .gql), each operation is validated against the schema before it is sent (an invalid one fails its request, not the run, and isn’t sent), and each response is checked as apigenie-mock --proxy-to checks it: the shape of data and errors, value types, null in non-null fields that no error explains, __typename of abstract types and schema drift. Coverage is per field and per root field (--min-coverage is the share of fields), and --report and --report-junit are the GraphQL contract report of apigenie-mock with the run’s tests and requests. --generate sends one request per field of Query and Mutation, with required arguments made up from their types as the mock makes up values; --base-url is the endpoint. --severity takes the GraphQL checks. The design is ADR-111; the GraphQL contract report moved from the apigenie-mock executable into the graphql_mock library, with the same output.

  • apigenie test runs httpYac’s WS and MQTT requests and checks their messages against an AsyncAPI 3.x --spec. A region connects to a ws:// server or an mqtt:// broker (MQTT 3.1.1; topic, publish, subscribe, qos, retain headers), sends the messages of its body (separated by ===, === wait-for-server) and waits for the server’s until # @expect N came or # @timeout (--message-timeout, 5000 ms) passed; the messages received are the response, a JSON array (?? body $[0].type == pickup.confirmed, ?? body $.length == 2, response.messages in scripts). The channel comes from the URL’s path or the topic (# @channel names it); messages sent are checked against the channel’s receive operations and messages received against its send operations, with the AsyncAPI proxy’s checks. A message of a request/reply operation is paired with its reply, checked by the new contract checks message.reply.missing (warn) and message.reply.uncorrelated (error, a payload correlationId that differs); correlation ids and reply addresses in message headers are noted as not checkable over MQTT 3.1.1. Coverage per operation and message and the reports (--report, --report-junit, --formatter json, --min-coverage) are the AsyncAPI proxy’s. --generate sends a region per WebSocket and MQTT channel with the receive operations’ example messages, at --base-url ws://... and the new --mqtt-url. The design is ADR-110, extending ADR-136 and ADR-097.

  • apigenie-mock --proxy-to contract-tests a real gRPC server against its .proto file: the proxy checks the upstream’s messages against their output types (also fields marked [(google.api.field_behavior) = REQUIRED], under proto3’s presence rules, and deprecated fields and enum values), its status (HTTP status, content type, grpc-status, the percent-encoding of grpc-message, grpc-status-details-bin as a google.rpc.Status whose code is grpc-status), the number of messages of unary calls and the -bin metadata, with check ids such as response.message.incompatible and response.status.details-incompatible that contract.severity changes. Locations name the field (message[0]/items/2/sku). It counts the calls of each service and method, and writes the contract reports the OpenAPI, GraphQL and AsyncAPI proxies write (--report with "specification": "grpc", --report-junit with a test case per method, --fail-on, --min-coverage as the share of methods, exit status 5, GET /__apigenie/contract). --strict (or grpc.validation.response: enforce) checks each upstream message before forwarding it and fails the call with INTERNAL. The upstream is HOST:PORT (cleartext HTTP/2) or https://host[:port] (HTTP/2 over TLS). The design is ADR-109.

  • apigenie test sends GRPC requests of .http files (httpYac’s GRPC host:port/package.Service/Method with the request message as a proto3 JSON body, metadata as headers) with a .proto file as --spec, its imports looked up in the new --proto-path directories. Unary and server-streaming calls are made over HTTP/2, cleartext or over TLS for grpcs:// URLs (with --ca-cert and --insecure), and checked like the proxy’s; a body that isn’t a valid message fails the request with the field’s path before it is sent. Assertions and scripts see the status code as status (?? status == 0), metadata and trailers as headers and the message as a JSON body (an array for server streaming). Client- and bidi-streaming requests are reported as skipped with a warning. --generate --base-url HOST:PORT (or grpcs://HOST:PORT) sends a request per method with a generated message. The reports are the gRPC contract report plus the requests (grpc, grpcStatus).

  • apigenie-mock --tls-cert FILE --tls-key FILE serves HTTPS, WSS and gRPC over TLS. Clients that ask for h2 by ALPN, such as browsers and gRPC clients, get HTTP/2. The settings are also tls.cert and tls.key in the configuration, and APIGENIE_MOCK_TLS_CERT and APIGENIE_MOCK_TLS_KEY. A separate admin port uses the same certificate, and the MQTT broker stays cleartext. The startup line shows https:// and wss:// URLs and "tls": true. TLS 1.2 and 1.3 come from Mbed TLS; the design is ADR-104.

  • apigenie-mock --proxy-to reaches TLS upstreams: https:// for the OpenAPI and GraphQL validating proxies, wss:// for the AsyncAPI WebSocket proxy, and https://host[:port] for gRPC (HTTP/2 over TLS with ALPN h2, shown as grpcs://). Certificates are verified against the system’s roots: the macOS keychains, the Windows ROOT store, the Linux CA bundle, or SSL_CERT_FILE and SSL_CERT_DIR. --ca-cert FILE (repeatable; tls.ca_certs, APIGENIE_MOCK_CA_CERT) adds authorities, and --insecure (tls.insecure, APIGENIE_MOCK_INSECURE=1) skips verification. A rejected certificate gets the usual 502 with the reason.

  • apigenie test sends https:// requests, with the same --ca-cert FILE and --insecure options. Imported collections no longer need their HTTPS URLs pointed at a cleartext server.

  • generate server go also takes AsyncAPI 3.x documents and generates a Go module (Go 1.22) for their Kafka channels on franz-go (github.com/twmb/franz-go/pkg/kgo, pinned at v1.18.1, with a complete go.sum): structs, enums and sealed-interface unions with json tags and Validate methods for the payloads, a struct per message (key, typed headers, payload) validated before it is sent and after it is received, and an App with a consumer group and a handler interface per receive operation (several messages told apart by a constant payload property or header, e.g. the ce_type of binary-mode CloudEvents; others go to OnUnknown) and a typed sender per send operation. Request/reply operations with a reply channel return the reply from the sender, matched by its correlation id, and repliers send the handler’s reply with the correlation id copied. Consumer groups and client ids come from the Kafka operation bindings. cmd/<package>/main.go takes the brokers from -brokers or KAFKA_BROKERS, wires stub handlers and stops cleanly on SIGINT and SIGTERM. Channels of other protocols are skipped with an info line, Avro payloads with a warning. Configured under commands.generate.server_go.asyncapi; the design is ADR-105. The codegen conformance suite checks both perspectives at Levels 1 and 2 and, at Level 3, has the application and the counterpart exchange the fixture’s Kafka messages on the conformance workflow’s new Kafka service container, including the requestLabelRender reply, an unknown ce_type and an invalid payload.

  • generate client go also takes AsyncAPI 3.x documents and generates a Go Kafka client package (a Go 1.22 module on franz-go v1.18.1, without main.go), by default for the counterpart of the described application and with perspective: application for the application itself. NewClient(options) creates the producer, Close() closes every client, and Options takes the brokers, a client id and franz-go options passed to every client. A send operation gets Send<Operation>(ctx, message), which writes the constant headers (e.g. ce_type), the key and the topic from the bindings; with several messages it takes an interface they implement. A request/reply operation gets a blocking Request<Operation>(ctx, message) that sets a fresh correlation id, waits for the reply until the context’s deadline and returns it typed (an interface of the replies when there are several), with one reply consumer per operation shared by all requests. A receive operation gets Listen<Operation>(handler), consuming in the background with the server’s dispatch, validation and repliers. Configured under commands.generate.client_go.asyncapi; the design is ADR-106. The codegen conformance suite checks both perspectives at Levels 1 and 2 and, at Level 3, runs the client against generate server go on the Kafka service container both ways: the server’s events and label requests reach the client’s listeners and replier, and the client’s sends and requests (concurrent ones and one ending at its context’s deadline included) reach the server’s handlers.

  • Options.ClientID of the Go Kafka code of generate server go and generate client go replaces the producer’s client id.

  • generate server aspnet also takes AsyncAPI 3.x documents and generates an ASP.NET Core project (.NET 8) for their Kafka channels on Confluent.Kafka 2.15.1, so a REST API and its events can live in one application: the payload models of generate client csharp (records, enums, unions read by converters of the System.Text.Json source-generated context), a record per message (key, typed headers with DataAnnotations, payload) validated before it is sent and after it is received, and per receive operation a handler interface, registered with dependency injection and resolved per record, and a consumer hosted as a BackgroundService in the operation’s consumer group (several messages told apart by a constant payload property or header, e.g. the ce_type of binary-mode CloudEvents; others go to OnUnknownAsync). Records are committed once their handler returns and handled again when it throws; invalid records are logged and committed. Each send operation gets a typed sender; request/reply operations with a reply channel return the reply from Request<Message>Async, matched by correlation id, and repliers send the handler’s reply with the correlation id copied. The AsyncApi:Kafka section of appsettings.json holds the brokers, topics, group and client ids (from the Kafka operation bindings) and further librdkafka properties, overridable by environment variables; stopping the host closes the consumers. Configured under commands.generate.server_aspnet.asyncapi; the design is ADR-107. The codegen conformance suite checks both perspectives at Levels 1 and 2 and, at Level 3, runs the application and the counterpart on the conformance workflow’s Kafka service container, as for Go.

  • generate client csharp also takes AsyncAPI 3.x documents and generates a C# Kafka client library (.NET 8 class library on Confluent.Kafka 2.15.1), by default for the counterpart of the described application and with perspective: application for the application itself. <Name>KafkaClient has a producer per channel it sends to, whose Send<Message>Async writes the constant headers (e.g. ce_type), the key and the topic from the bindings; a request/reply operation gets Request<Message>Async, which sets a fresh correlation id, waits for the reply with a timeout (ReplyTimeout or an argument) and a CancellationToken, and returns it typed (an interface of the replies when there are several), with one reply consumer per client and a pending request per correlation id. Receive operations get the handler interfaces and hosted consumers of generate server aspnet, with its dispatch, validation and repliers. Add<Name>KafkaClient(configuration, configure) binds the options to the AsyncApi:Kafka section and registers the client and its producers, and its builder’s Add<Operation>Handler<THandler>() registers a handler with its consumer. --package-name is the root namespace (default: the title followed by .Client), as for OpenAPI. Configured under commands.generate.client_csharp.asyncapi; the design is ADR-108. The codegen conformance suite checks both perspectives at Levels 1 and 2 and, at Level 3, runs the client against generate server aspnet on the Kafka service container both ways, including concurrent requests, a request timing out and a cancelled one.

  • generate client java also takes AsyncAPI 3.x documents and generates a Java Kafka client library (a Maven project for Java 21 on the Apache Kafka client 4.1.0, Jackson and Hibernate Validator, without Spring), by default for the counterpart of the described application and with perspective: application for the application itself. It shares the message records, models, dispatch, validation and repliers of generate server spring. <Name>KafkaClient (with <Name>KafkaClientOptions: servers, topics, client and group ids, reply timeout, producer and consumer properties) gets send<Message>(message) per send operation, writing the constant headers (e.g. ce_type), the key and the topic from the bindings; <operation>(message[, timeout]) per request/reply operation, which sets a fresh correlation id (header, payload pointer or kafka_correlationId) and completes with the typed reply, with one group-less reply consumer per client; and listen<Operation>(handler) per receive operation, consuming on a virtual thread with onUnknown and onError defaults. --library and -p are errors with AsyncAPI input. Configured under commands.generate.client_java.asyncapi; the design is ADR-121. The codegen conformance suite checks both perspectives at Levels 1 and 2 (mvn package, javac -Xlint:all -Werror) and, at Level 3, runs the client against generate server spring on the Kafka service container both ways, including concurrent requests and a timeout.

  • generate client python also takes AsyncAPI 3.x documents and generates an asyncio Kafka client package on aiokafka with the Pydantic models of generate server faststream, by default for the counterpart of the described application and with perspective: application for the application itself. messages.py has a dataclass per message (payload, headers, key) with headers models and their constant headers; <Name>KafkaClient, an async context manager, gets send_<message>(), request_<operation>(message, *, timeout=None) (fresh correlation id, typed reply, one group-less reply consumer per client, TimeoutError) and listen_<operation>(handler) with abstract handler classes (on_unknown and on_error defaults), by the Kafka rules of ADR-102. The package passes mypy --strict and ruff, and depends on aiokafka[lz4,snappy,zstd], so it reads topics other producers compress. Configured under commands.generate.client_python.asyncapi; the design is ADR-121. The codegen conformance suite checks both perspectives at Levels 1 and 2 and, at Level 3, runs the client’s counterpart against the Kafka broker of generate server faststream’s application and the client’s application against its counterpart on the Kafka service container, including concurrent requests and a timeout.

  • generate server spring also takes AsyncAPI 3.x documents and generates a Spring Boot application (Maven project) for their Kafka channels: a record per payload and per message (key, payload, headers) with Jackson and Jakarta Bean Validation annotations, a @KafkaListener and a handler interface per receive operation (several messages told apart by a constant payload property or header, e.g. the ce_type of binary-mode CloudEvents), and a sender over KafkaTemplate per send operation. Request/reply operations with a reply channel use ReplyingKafkaTemplate, and replies carry the correlation id from the correlationId location. Topics, group and client ids (from the Kafka bindings) and servers go to application.yaml; message headers are merged with those of their traits as AsyncAPI does. Constraints Bean Validation can’t express (draft-07 dependencies, if/then/else, tuples) become comments. Channels of other protocols are skipped with an info line, Avro payloads with a warning. Configured under commands.generate.server_spring.asyncapi; the design is ADR-102. The codegen conformance suite builds both perspectives at Level 2 and, at Level 3, has the application and the counterpart exchange the fixture’s Kafka messages on an embedded broker (@EmbeddedKafka), including the requestLabelRender reply.

  • generate client go generates a Go client for the net/http package of Go 1.22 from an OpenAPI 3.x document, using the standard library only. One Client over a configurable *http.Client takes functional options (base URL, request editors, user agent, an option per security scheme such as WithAPIKey), and has a method per operation and request media type taking a context.Context first. Each method returns a response struct with one typed field per documented status and media type (JSON200, ApplicationProblemJSON404 *ProblemDetails) and the raw *http.Response; statuses of 400 and above also return an *APIError with the RFC 9457 problem details. text/event-stream and application/jsonl responses are iterators typed from itemSchema (x-itemSchema in 3.0 and 3.1), binary responses stay open as io.ReadCloser, multipart bodies are structs whose binary parts (from contentMediaType or format: binary) are File values honouring encoding, content parameters are sent as JSON, operation and path servers are honoured (WithOperationServers(false) ignores them), credentials go to the operations that accept them, and x-internal operations are left out. Types and validation are shared with generate server go. Configured under commands.generate.client_go.

  • generate client csharp generates a C# client of an OpenAPI 3.0, 3.1 or 3.2 document for .NET 8 (or 9 and 10), built on HttpClient and System.Text.Json: a class library with one client per tag taking the HttpClient in its constructor (client.Shipments.ListShipmentsAsync()), a client of the whole API, and Add{Api}Client() registering them with IHttpClientFactory. Methods are async with a CancellationToken; they return the body of the success response, or a *Result by status when statuses have different bodies, and throw ApiException with the RFC 9457 problem details of error responses. Every parameter style is supported, objects and content parameters (as JSON) included; bodies are JSON, text, binary, forms and multipart with FileParts for format: binary and contentMediaType fields; other media types of bodies and responses are methods of their own (UpdateShipmentJsonPatchAsync); server-sent events and JSON Lines are IAsyncEnumerable<T> typed by itemSchema (or x-itemSchema). Operation and path servers are honoured, x-internal operations left out, and bearer, basic, OAuth 2.0, OpenID Connect and API key credentials sent by DelegatingHandlers. The models are those of generate server aspnet, with a source-generated JsonSerializerContext and converters that read every oneOf/ anyOf with a discriminator wherever the discriminator is, falling back to defaultMapping (or x-defaultMapping). The code builds with -warnaserror and the .NET analyzers. Configured under commands.generate.client_csharp.

  • The OpenAPI reader reads itemSchema of media types and defaultMapping of discriminators (OpenAPI 3.2), and the writer keeps them.

  • apigenie test import COLLECTION -o DIR imports Collection Format v2.0/v2.1 JSON exports into folder-based .http tests with environments, bearer/basic/API-key references, upload assets, inherited scripts and a marker-gated QuickJS pm compatibility subset. Status-only tests become ?? assertions; unsupported script blocks are preserved as comments with source-line warnings, and OAuth 2 requests are disabled. --spec adds operation bindings and saved-response example overlays without editing the spec. --environment can be repeated; --force opts into replacing generated files. Authentication secrets are stored only in the generated environment file.

  • apigenie test reads QUERY requests (the safe method with a body of OpenAPI 3.2) in .http files, and --spec takes OpenAPI 3.2 documents.

  • apigenie test runs the requests of .http files (the httpYac flavour, which the JetBrains HTTP client and VS Code’s REST Client read too) against a live server and validates every request and response against an OpenAPI document (--spec) with the checks of apigenie-mock’s validating proxy. Files have ### regions, {{var}} variables, @var = value definitions, # @name/# @ref/# @forceRef chaining with {{name.response.body.$.id}} references, {{ }} scripts and JetBrains > {% %} handlers (QuickJS: request, response, client, exports, test, assert, console), ?? assertions, and .env and http-client.env.json environments (--env, --env-file, --var). Requests are bound to operations by method and path; # @operation <operationId> picks one, # @operation off skips the checks. --generate sends one request per operation, from the document’s examples or generated values. The run fails with exit status 5 when a request fails or when the contract gate (--fail-on, errors by default, and --min-coverage) does; --report and --report-junit write apigenie-mock’s contract report with the requests added. Unknown directives are warnings. GraphQL, gRPC, HTTPS and OAuth 2 are not supported yet.

  • --strip-comments for format, overlay and lint --fix drops the comments of the files they write, which they keep otherwise (ADR-093): YAML comments of OpenAPI, Swagger and AsyncAPI documents, comments of Protobuf files and # comments of GraphQL documents, whose descriptions are kept.

  • i18n apply applies translations to Protobuf (.proto) and GraphQL (.graphql, .graphqls, .gql) files, keyed as i18n extract writes them: the leading comments of Protobuf declarations and the descriptions of GraphQL definitions are rewritten in place, in their style (// lines, /* */ blocks, block or quoted strings), and every other byte of the file is kept, comments included, so applying untranslated texts returns the file unchanged. Keys addressing no comment or description are warnings, or errors with --strict (ADR-103).

  • i18n extract extracts the texts of Swagger 2.0 documents, recognized by their swagger property, as it does for OpenAPI: titles, summaries and descriptions keyed by JSON pointers, which i18n apply puts back (ADR-103).

  • i18n apply --strip-comments drops the YAML comments of a spec or the # comments of a GraphQL file (descriptions are kept), as format, overlay and lint --fix do (ADR-093); it is refused for Protobuf files, whose translated texts are comments.

  • i18n apply applies translations to OpenAPI and AsyncAPI documents (JSON or YAML): each key of a translation file, a JSON pointer as i18n extract writes it, has the string it points to replaced by its value, and everything else is kept, YAML comments included. --translations can be repeated (later files override earlier ones), the result goes to --output or the standard output in the input format or that of --format, and keys pointing to nothing are warnings, or errors with --strict.

  • apigenie-mock --proxy-to ws://host:port with an AsyncAPI document is a validating proxy for contract testing of a WebSocket API: clients of a WebSocket channel are relayed to the real server (handshake headers and subprotocol passed on, 502 or 504 when it can’t be reached), and every message of both sides is checked against the channel’s receive (client messages) and send (server messages) operations. Violations carry checks such as message.incompatible and channel.send.undeclared, go to the log, and --strict keeps invalid messages from going on (the client gets an invalid-message or invalid-upstream-message problem instead). Coverage counts each message of each operation; --report, --report-junit, --fail-on, --min-coverage and GET /__apigenie/contract work as for OpenAPI. HTTP channels and the MQTT broker keep being mocked. contract.severity accepts the new checks.

  • apigenie-mock --proxy-to ... --record-dir DIR with an OpenAPI document writes each exchange the server answered as a stub file (getPet-1.yaml, or GET_health-1.yaml for a route of no operation: the request’s method, path, query, headers and body, and the response’s status, headers and body), which --stubs DIR (or stubs: in the configuration) replays without the server: before the document’s examples, after Prefer and scenario rules, with X-Apigenie-Mock-Source: stub:getPet-1. --record-har FILE writes the whole session through the proxy, the mock’s own answers included, as a HAR 1.2 file when the server stops, with comment (proxy or mock) and _apigenie (source, operation, upstream status, violations) on each entry. Both hide the values of Authorization, Cookie, Set-Cookie, Proxy-Authorization and X-Api-Key; proxy.redact sets the list. Also proxy.record_har and APIGENIE_MOCK_RECORD_HAR.

  • apigenie-mock --proxy-mode fallback with an OpenAPI document and --proxy-to: every request still goes to the real server, but when the server can’t be reached, doesn’t answer in time, or answers 404 or 501 for an operation of the document, the mock answers as it would without --proxy-to (examples, generated data, scenarios, Prefer). Such answers carry X-Apigenie-Mock-Upstream-Status, are logged with fallback: true and aren’t response.status.unknown violations. The contract report tells the server’s answers from the mock’s: summary.sources, each operation’s sources and mockedResponses, two JUnit properties and a Sources: line in the summary. Configured under proxy.mode.

  • apigenie-mock serves OpenAPI 3.2 documents: the query method and the methods of additionalOperations (such as COPY) are routed, validated and recorded in contract reports like the others. Streams described by itemSchema are answered with an empty body.

  • A codegen conformance suite runs every OpenAPI generator on one shared, realistic OpenAPI 3.2 fixture, the Carpet Express Shipping API (data/fixtures/openapi, with its 3.1 and 3.0 variants, Spring and mock overlays and a feature catalog). Level 1, part of every ctest run, compares each generator’s output byte for byte with a checked-in snapshot and checks a per-target manifest of the features it doesn’t support yet. Levels 2 and 3 type-check and lint the output with the target’s own toolchain and run the generated code against the fixture’s contract (clients behind apigenie-mock --proxy-to, servers with apigenie test --generate and a hand-written request set), for the fetch, C#, Go, Java and Python clients and the FastAPI, Spring, Go, ASP.NET and Express servers, and the zod schemas at Level 2, in a Linux workflow that runs on generator changes and on demand. The results are in docs/codegen_conformance.md; the design is ADR-098.

  • The codegen conformance suite covers the AsyncAPI generators: generate client ws and generate server ws run on the Carpet Express Event Platform (data/fixtures/asyncapi), the AsyncAPI 3.0 event side of the same service (Kafka, WebSocket, MQTT 5, HTTP and AMQP channels, three request/reply styles, traits with a merge trap, bindings for all five protocols, draft-07 schemas), with a mock overlay and a feature catalog. Level 1 compares their output with snapshots and checks their manifests (the twelve non-WebSocket channels don’t concern them; the reply of a request/reply operation is not generated yet). Level 2 type-checks and lints the generated packages; Level 3 drives the generated client and server through apigenie-mock as a validating WebSocket proxy. The matrix in docs/codegen_conformance.md has an AsyncAPI section; the design is ADR-100.

  • apigenie generate server faststream generates a Python FastStream application from the Kafka, AMQP (RabbitMQ) and MQTT channels of an AsyncAPI 3.x document: a broker per server (URL from an environment variable), a subscriber per receive operation (topics and patterns, consumer groups, queues, exchanges and routing keys, QoS from the bindings; several messages dispatched by a constant property) calling a handler you write in handlers.py, which regenerating keeps, and a publisher with a typed publish function per send operation. Payloads and headers become Pydantic v2 models, the same as generate client python’s, with trait headers merged; headers are typed Header() parameters. Requests wait for their reply on the document’s reply channel by correlation id; reply addresses in headers become reply_to arguments. WebSocket and HTTP channels and Avro payloads are skipped and reported. Configuration is commands.generate.server_faststream; it is in the codegen conformance suite at Levels 1 to 3. The design is ADR-101.

  • The codegen conformance suite runs generate server faststream at Level 3: the conformance workflow has RabbitMQ (with the fixture’s virtual host) and Mosquitto service containers, pinned by digest, next to its Kafka one, and the generated application runs on the three brokers next to a counterpart. The counterpart publishes the fixture’s examples (MQTT courier positions and sorter scans, AMQP customs decisions) to the application’s handlers, receives what the application publishes (shipment events and label requests on Kafka, job offers on MQTT, customs filings on the customs exchange) and answers the label requests on their reply topic. The MQTT traffic goes through apigenie-mock’s MQTT bridge, whose contract report checks it; the Kafka and AMQP messages and the MQTT 5 user properties are checked against the fixture in the harness (checkMessages(), ajv). Every item passes.

  • apigenie-mock --proxy-to http://host:port/graphql with a GraphQL schema is a validating proxy for contract testing: GraphQL requests are checked against the schema (graphql-js’s rules, variables, deprecated usage) and forwarded to the real server, whose answers are checked field by field against the schema and the request’s selection set (missing and unexpected fields, nulls no error explains, scalar, enum and __typename values, lists, the shape of errors, statuses and media types). The proxy also introspects the server and reports where its schema differs (schema.drift, schema.addition). Violations carry checks such as response.data.incompatible, go to the log and X-Apigenie-Mock-Violations-Detail, and --strict answers bad requests itself and replaces bad answers (502). Field and root field coverage, --report, --report-junit, --fail-on and --min-coverage work as for OpenAPI. --proxy-mode fallback lets examples, scenarios and scripts answer first, and --record-dir writes the server’s answers as examples the mock replays.

  • generate server fastapi generates a Python FastAPI server from an OpenAPI 3.x document, with the project layout and options of openapi-generator’s python-fastapi generator: pydantic 2 models (enums, unions with discriminators, recursive models, aliases and constraints), a router and a base class to implement per tag, security dependencies for API keys, HTTP, OAuth2 and OpenID Connect schemes with alternatives and scopes, pyproject.toml, a Dockerfile and pytest tests. Routes declare their status codes and response classes, parameters and bodies of every kind (forms, uploads, text, binary) are typed, and the implementation package, the tests and .openapi-generator-ignore are never overwritten. Configured under commands.generate.server_fastapi.

  • generate client python generates a Python client of an OpenAPI document, built on httpx (or httpx2) and Pydantic 2: Client and AsyncClient classes with one resource per tag (client.pets.list_pets()), Pydantic models with Python field names, tagged unions for discriminators and Input variants for readOnly/writeOnly properties, all parameter styles, JSON, form, multipart, text and binary bodies, typed errors parsed with the documented schemas, retries with backoff honoring Retry-After, per-request options, raw and streaming responses, server-sent events, server variables, and authentication by security scheme, including the OAuth 2.0 client credentials flow. The code passes mypy --strict. Configured under commands.generate.client_python.

  • generate client java generates a Java client of an OpenAPI document as a Maven project (Java 17, Spring Framework and Jackson versions from the Spring Boot BOM, without Spring Boot): an ApiClient on Spring’s RestClient (--library restclient, the default) or RestTemplate (resttemplate) with one class per tag, whose operations have op(...), opWithHttpInfo(...), opStream(...) (a lazy Stream of text/event-stream and JSON Lines items) and opWith<MediaType>(...) methods for further request media types (updateShipmentWithJsonPatch); every parameter style, JSON-encoded content parameters, JSON, JSON Lines, form, multipart (Resource parts) and binary bodies, QUERY and other methods, operation and path servers, credentials per security scheme (bearer, basic, API keys in headers, queries and cookies) applied to the operations requiring them, and ApiException/ProblemDetailsException (RFC 9457) for errors; x-internal operations are left out. --library feign and --library http-interface generate Spring Cloud OpenFeign and Spring HTTP interface clients. Configured under commands.generate.client_java.

  • generate server express generates an Express 5 server stub in TypeScript from OpenAPI 3.x documents: typed handler interfaces per tag, implemented in files of your own, and createRouter(handlers, options), a router which checks security requirements with security handlers per scheme, decodes parameters in all OpenAPI styles and converts them to the types of their schemas, parses bodies, validates requests (and optionally responses) with Ajv or with generated validators without dependencies (validation), answers errors with RFC 9457 problem details and operations without handlers with 501. It can include the document and an example server. Configured under commands.generate.server_express.

  • generate server orpc generates a TypeScript server based on oRPC (https://orpc.dev) from an OpenAPI document: Zod schemas, a procedure contract per operation with its route, input, output and type-safe errors (one per 4xx/5xx response, sent with the response body of the document), handler stubs, and a Node.js server serving the procedures with oRPC’s OpenAPIHandler and their OpenAPI reference. Inputs use oRPC’s compact structure unless the request needs the detailed one, parameters are converted from strings, and operations with several success responses or response headers return status, headers and body. Handlers and the server files are written once, so regenerating keeps them. Targets oRPC 1, or oRPC 2 (beta) with orpc_version: 2. Configured under commands.generate.server_orpc.

  • generate server aspnet generates an ASP.NET Core server in C# built on Minimal APIs, for .NET 8 to 10 and without NuGet packages: records and enums of the schemas, one interface per group of operations whose methods return Results<...> unions of typed results (generated result types cover statuses TypedResults lacks), endpoints binding path, query, header and cookie parameters (array styles included) and JSON, text, binary, form and multipart bodies, and a project with Program.cs and stub implementations written only when missing. oneOf with a discriminator becomes [JsonPolymorphic] records, enums keep the values of the document, an endpoint filter validates requests against the constraints of their schemas (400 validation problems), and operations with security requirements call RequireAuthorization(). layout: sources writes only the sources, for existing projects. Configured under commands.generate.server_aspnet.

  • apigenie-mock --proxy-to http://host:port with an OpenAPI document is a validating proxy for contract testing: it forwards every request to the real server and checks the request and the server’s response against the document (parameters, bodies, security, statuses, media types, headers). Violations carry stable checks such as response.body.incompatible and show up in the log, in X-Apigenie-Mock-Violations-Detail and at GET /__apigenie/contract; --strict rejects bad requests (422) and replaces bad responses (502). The mock counts the operations, request bodies and responses the traffic covered, and writes --report (JSON) and --report-junit when it stops; --fail-on and --min-coverage make it exit with 5. Configured under proxy and contract.

  • overlay command applies OpenAPI Overlays (versions 1.0, 1.1 and 1.2 of the specification) to an OpenAPI document, or to any JSON or YAML document: apigenie overlay INPUT --overlay OVERLAY, with --overlay repeated to apply several overlays in order. Updates, copies and removals follow the merge rules of the specification, reusable actions are supported, YAML comments are kept, and actions that select nothing are reported as warnings. --validate only checks the overlays, with the line and column of each issue. Targets are RFC 9535 JSONPath queries, functions (length(), count(), match(), search(), value()) included. The openapi_overlay WebAssembly module exports the same apply and validate.

  • generate server go generates a Go server for the net/http.ServeMux of Go 1.22 from an OpenAPI 3.x document, using the standard library only. Names and layering follow oapi-codegen: ServerInterface receives bound parameters, StrictServerInterface request and response objects (typed headers, statuses, JSON, form, multipart, text and binary bodies). Types carry Validate() methods, and parameters and bodies are validated before the handlers run, with RFC 9457 problem details for failures; oneOf/anyOf become structs with one field per variant, optional nullable properties Nullable[T]. Patterns end with {$} where needed, and the patterns http.ServeMux can’t register together share one that dispatches to the most specific operation. A SecurityHandler and middlewares run before parameters are bound. Configured under commands.generate.server_go.

  • generate schema zod generates Zod (v4) schemas, with their inferred TypeScript types, from the root schema and the $defs of a JSON Schema document (drafts 4 to 2020-12), or from the component schemas of an OpenAPI 3.x, Swagger 2.0 or AsyncAPI document. Objects follow JSON Schema and keep unknown properties unless additionalProperties says otherwise; formats, numeric and string constraints, tuples, patternProperties, not, if/then/else and dependencies are validated, discriminated unions are detected, and recursive schemas become getters. Configured under commands.generate.schema_zod.

  • generate client ws and generate server ws generate TypeScript code from the WebSocket channels of AsyncAPI 3.x documents: a client with one class per channel (browsers and Node.js, no dependencies) and a Node.js server built on the ws package, with session classes and handler interfaces. Payloads, address parameters and the query of the WebSocket binding are typed; received messages get handlers of their own when a property with a constant value tells them apart. The server implements the described application and the client its counterpart, unless the perspective option says otherwise. Configured under commands.generate.client_ws and commands.generate.server_ws.

  • format --check reports the files that formatting would change, one path per line, and exits with 1 when there are any, without writing anything.

  • GitHub Action (src/github_action, not published yet) that installs the CLI on Linux, macOS and Windows runners and runs lint, format --check or diff (against the pull request’s base), with annotations on the files and a job summary. With sarif: true, lint issues are also uploaded to GitHub code scanning; on pull requests, the diff report is posted as a comment that later runs update in place.

  • sarif formatter of lint: SARIF 2.1.0 for GitHub code scanning and other dashboards, with paths relative to the current directory and fingerprints based on JSON pointers, so that issues are tracked across runs.

  • apigenie-mock, a mock server for OpenAPI 3.0 and 3.1 documents, installed with apigenie by every package and published as the mykulyak/apigenie-mock Docker image. It answers with the document’s examples or with data generated from its schemas, validates requests (422 with problem details) and its own responses, checks security requirements, follows Prefer headers (code, example, dynamic, delay, drop, scenario), simulates latency, jitter, dropped connections and bandwidth limits, and logs every exchange as text or JSON lines. Scenarios force error states from a configuration file (apigenie-mock.yaml or commands.mock), x-apigenie-mock document extensions or an admin API under /__apigenie, which also reloads, reseeds and stops the server. --watch reloads the document and the configuration when they change. Schemas can pick faker functions with x-faker.

  • apigenie-mock serves AsyncAPI 3.x documents: WebSocket clients of a channel get its send operations’ messages on a schedule and their messages are checked against its receive operations and answered with their replies; HTTP channels take and answer messages; an in-house MQTT 3.1.1 and 5 broker serves the channels of mqtt servers.

  • apigenie-mock serves the gRPC services of .proto files over cleartext HTTP/2, on the same port as HTTP: unary and streaming methods answered from YAML or JSON stub files (--stubs, grpc.stubs) with request and metadata matchers, templates, delays and error statuses with google.rpc details, or with messages generated from the response types. It honours deadlines and message limits, follows x-apigenie-mock-* metadata, applies scenarios (grpc_status, grpc_message) and scripts, serves server reflection (v1 and v1alpha) and health checks, proxies unanswered calls to an upstream server (--proxy-to) and records them as stub files (--record-dir). The admin API manages stubs and has the call history and a verify endpoint under /__apigenie/grpc. Imports are looked up in --proto-path directories and among the embedded well-known, google/api and google/rpc files.

  • apigenie-mock also answers OpenAPI documents over cleartext HTTP/2 (prior knowledge).

  • apigenie-mock serves GraphQL schemas (.graphql, .graphqls, .gql) at /graphql: GET, POST and batched requests per GraphQL over HTTP, validated against the schema with the rules and messages of graphql-js, executed with generated data (by type, field name and custom scalar name, or as graphql-faker’s @fake, @examples and @listLength and the mock’s @mock directives say), full introspection, GraphiQL and the SDL. Entities (types with an id) keep their values across requests, and user(id:), users, usersConnection (Relay) and createUser, updateUser, deleteUser-like fields read and change them. Examples (graphql.examples, example files, <schema>_examples/) answer operations or fields, cut to the selection set and completed with generated values; scenario rules match operations and root fields and inject errors at field paths. Subscriptions are served over WebSocket (graphql-transport-ws and the legacy graphql-ws) and server-sent events. Configured under graphql.

  • apigenie-mock runs JavaScript hooks (--script, scripts): onRequest answers requests, onResponse changes responses and their simulated delays or drops, onMessage changes or drops AsyncAPI messages, onGraphQL answers GraphQL operations, with state kept between requests, logging, seeded random numbers, faker functions and scenarios.

  • Linter validates documents against the OpenAPI schema, and more rules support autofixing, including the new schema-no-optional-nullable-properties rule.

  • diff command (openapi-diff) and the HTML documentation generator are available again.

  • diff severities are configurable per context: request and response rule overlays (callbacks and webhooks swap them), overrides for single locations, conditions such as if: required, and add_first_item / remove_last_item rules. Schema properties are compared, respecting readOnly and writeOnly.

  • diff detailed reports describe each change in all formats.

  • diff command compares AsyncAPI 3.x documents: servers, channels, operations, messages and their payloads and headers, security schemes, bindings (property by property for HTTP, WebSockets, Kafka, AMQP and MQTT) and components, with the same reports, formatters, exit status and --update-version as OpenAPI documents. The severity of a message change depends on whether the application sends or receives the message (send and receive rule overlays). Configured under commands.diff.asyncapi.

  • diff command compares Protobuf (proto3 .proto) files: package, imports, options, messages, fields (matched by their numbers), oneofs, enums, services, methods, extend blocks, reserved numbers and names, and comments, with the same reports, formatters and exit status as OpenAPI documents. Type names are resolved before they are compared, and removing a field whose number the new file reserves is not breaking. Severities can depend on reserved, deprecated and wire_compatible conditions, and options are configured by their names. Configured under commands.diff.protobuf.

  • diff command compares Swagger 2.0 documents: host, base path, schemes, media types, paths, operations, parameters (matched by location and name, the body parameter by location), responses, headers, schemas (following references to definitions), security definitions and requirements, and tags, with the same reports, formatters, exit status and --update-version as OpenAPI documents. Body parameters and consumed media types get the request rules, responses and produced media types the response ones, and definitions the stricter of the two. Configured under commands.diff.swagger.

  • diff command compares GraphQL schemas (.graphql, .graphqls and .gql files): root operation types, types of every kind, fields, arguments, input fields, default values, interfaces, union members, enum values, directive definitions, applied directives and descriptions, with the same reports, formatters and exit status as OpenAPI documents. Type extensions are merged into their types, and operations are ignored. Severities follow graphql-js’s breaking changes and can depend on required, compatible, deprecated and boolean conditions; directives are configured by their names. Configured under commands.diff.graphql.

  • changelog command builds the changelog of an OpenAPI file from its git history: one entry per commit or per release tag, or the history of each operation (--by operation, filtered with --operation), as stylish text, Markdown, HTML or JSON. Files the specification references are followed in each version.

  • changelog command builds the changelog of AsyncAPI 3.x files too: added, removed and changed operations, channels and servers, with the changes of a channel’s messages shown under the operations using it, rated with the commands.diff.asyncapi rules. --operation selects AsyncAPI operations by id, by send/receive and channel address, or by channel address.

  • changelog command builds the changelog of Protobuf (.proto) files too: added, removed and changed methods (named Service.Method), messages, enums, services and extensions, package changes shown like version changes, rated with the commands.diff.protobuf rules. The file is compared on its own, without the files it imports. --by operation groups the changes by method, and --operation selects methods by full name, method name or service name.

  • Swagger 2.0 input support.

  • changelog command builds the changelog of Swagger 2.0 documents too: added, removed, deprecated and changed operations, with the changes of a path item shown under its operations, and the host, base path, schemes, media types and security of the API as metadata, rated with the commands.diff.swagger rules. Files the specification references are followed in each version.

  • changelog command builds the changelog of GraphQL (.graphql, .graphqls, .gql) schema files too: added, removed and changed operations (the fields of the root operation types, named like Query.books), scalars, object types, interfaces, unions, enums, input objects and directive definitions, with changes of the root operation types, the schema description and the schema directives as metadata, rated with the commands.diff.graphql rules. The file is compared on its own. --by operation groups the changes by operation, and --operation selects operations by full name, field name, root type name or operation type (query, mutation, subscription).

  • bundle command bundles Swagger 2.0 documents and the local files they reference into a single file: referenced schemas, parameters and responses become entries of definitions, parameters and responses, and referenced path items take the content of their files.

  • bundle command bundles AsyncAPI 3.x documents and the local files they reference into a single file. Root servers, channels and operations kept in other files take the content of those files, so that operations still refer to their channels and channel messages; other referenced objects become components. Schemas in other formats, such as Avro, are copied as they are, and references by URL are kept.

  • bundle command bundles Protobuf (.proto) files: the input file and the messages, enums and extensions it uses from the files it imports (searched in the -I/--proto-path directories) are combined into one proto3 file. Declarations from other packages are renamed with a prefix made from their package, and well-known type imports are kept.

  • format command formats AsyncAPI 3.x documents, configured under commands.format.asyncapi.

  • i18n command extracts the texts of AsyncAPI 3.x documents for translation: the titles, summaries and descriptions of servers, channels, operations, messages, schemas and the other objects, keyed by their JSON pointers.

  • i18n command extracts the leading comments of Protobuf (.proto) files for translation, keyed by the fully qualified name of the message, field, oneof, enum, enum value, service or method they document; enum values are keyed under their enum, and the comment of the file (that of its syntax statement) under the package name.

  • i18n command extracts the descriptions of GraphQL (.graphql, .graphqls, .gql) files for translation, keyed by full name: the schema (schema), types, fields, arguments, input fields, enum values and directives by their schema coordinates (e.g. Query.search(text:), Genre.POETRY, @key), operations and their variables (e.g. query FindBooks, query FindBooks($text:)) and fragments (e.g. fragment BookSummary).

  • format command formats Swagger 2.0 documents, configured under commands.format.swagger.

  • format command formats GraphQL schemas and operations (.graphql, .graphqls, .gql) in the layout of Prettier, keeping all comments, and optionally sorts definitions and the members of types. Configured under commands.format.graphql.

  • lint command lints AsyncAPI 3.x documents with 31 rules (ported from the OpenAPI ones where they apply, plus channel-parameters-defined, channel-addresses-unique and server-hosts), rulesets, custom rules and autofixes, configured under commands.lint.asyncapi.

  • lint command lints Swagger 2.0 documents with 46 rules (ported from the OpenAPI ones where they apply, plus host-defined, schemes-secure, media-types-defined and swagger-validation, which reports the specification violations found by the Swagger validator), rulesets, custom rules and autofixes, configured under commands.lint.swagger.

  • lint command lints Protobuf (proto3 .proto) files with 39 rules modelled on buf’s: naming conventions, packages (versions, directories, consistent language options, import cycles between packages), imports (unresolved, unused, public, weak, cycles, order), enums, services and RPCs, comments and spell checking. Imports are looked up in --proto-path directories, the import_paths of the configuration and the directories above the file, with the well-known google/protobuf files built in. --fix removes unused imports and sorts imports; an // apigenie-lint-disable <rules> comment disables rules for a declaration. Configured under commands.lint.protobuf with the recommended (default), strict and all rulesets.

  • lint command lints GraphQL schemas and operations (.graphql, .graphqls, .gql) with 87 rules modelled on graphql-eslint, graphql-schema-linter and Apollo’s schema linter: the type system and operation rules of the specification, naming conventions, descriptions, deprecations (reasons, deletion dates), schema design (root types, mutations and payloads, unreachable types, identifiers, @oneOf, circular input types), the Relay connection conventions, ordering, spelling, and operations checked against the schema (unknown fields, types and arguments, required arguments, deprecated fields, selection depth, identifier selections). Operations are checked against the files named by --graphql-schema or by the schema option, or against the type system of their own document. A # apigenie-lint-disable <rules> comment disables rules for a definition. Configured under commands.lint.graphql with the recommended (default), strict, all and relay rulesets; --fix doesn’t apply to GraphQL files.

  • Minimal telemetry.

  • Linter rules reasoning about schemas: schema-no-contradictions (constraints rejecting every value, and properties forbidden by an allOf member with additionalProperties: false), schema-oneof-no-overlap (oneOf branches a single value matches, shown in the report), schema-required-properties-defined and schema-conditional-effective (if/then/else without effect), all in the recommended ruleset, and patch-request-body-partial (required properties in PATCH bodies) in the strict ruleset.

  • Official release binaries for macOS (arm64), Linux (x64, static, runs on any distribution) and Windows (x64), plus .deb and .rpm packages, a Homebrew formula, per-platform npm packages and a versioned Docker image.

  • generate server express and generate client fetch follow discriminator.defaultMapping (OpenAPI 3.2; x-defaultMapping in 3.0 and 3.1). Express validates such a oneOf by the discriminating property: with Ajv as an if/then/else chain that ends with the default schema, with validation: generated by a dispatch reporting the same issues. A value whose property matches no mapping, or lacks it, is validated against the default schema instead of failing oneOf. The types of both targets get a type guard per member (isParcelShipment(value: Shipment)), the default member’s taking the values no mapping matches. The fetch client’s conformance snapshots cover OpenAPI 3.1 and 3.0 too.

Changed

  • The documentation smoke run of the codegen conformance suite is generate docs (test_docs_bundle) instead of generate doc html, and the e2e/generate_doc_html benchmark cases are e2e/generate_docs.

  • apigenie test import writes the credentials it finds (bearer tokens, basic credentials, API keys, OAuth 2 client secrets and passwords) to a generated http-client.private.env.json instead of http-client.env.json, which keeps the other variables and no secret; apigenie test reads both. The sensitive-environment warning names the private file.

  • The web components read AsyncAPI documents with the asyncapi WebAssembly module, as they read GraphQL and Protobuf: set asyncapi-wasm-url on <apg-spec> or <apg-api-reference>, or call configureAsyncApiEngine(url). npm run prerender takes --asyncapi-wasm. There is no JavaScript reader for AsyncAPI any more; without the module, AsyncAPI documents fail to load with a message saying what to set. The views are unchanged. @mykulyak/apigenie-asyncapi-view reads the module’s documentation model; its parse and traits modules are gone. Server-rendered pages embed the model, so hydration needs no module.

  • The JSON contract reports of apigenie-mock --report, GET /__apigenie/contract and apigenie test --report give each OpenAPI operation the JSON pointer of the operation in the document (e.g. /paths/~1pets/get).

  • Internal: swagger_convert returns a source map with each conversion, from the JSON pointers of the result to those of the Swagger 2.0 document.

  • apigenie-mock --proxy-to with a .proto file logs request violations instead of failing the call with INVALID_ARGUMENT, as the OpenAPI, GraphQL and AsyncAPI proxies do; --strict or grpc.validation.request: enforce restores it, and a request is then checked before it reaches the upstream (an always-mode proxy forwarded it first). A mock without --proxy-to keeps enforcing requests, and now also rejects a request that misses a field marked [(google.api.field_behavior) = REQUIRED]. A non-200 HTTP answer of the upstream ends the call with the status gRPC clients make of it (UNAVAILABLE for 503) instead of always UNAVAILABLE, and an upstream answer without grpc-status ends as UNKNOWN instead of OK.

  • --report and --report-junit of apigenie-mock are accepted for .proto files, and /__apigenie/contract answers for them.

  • The requester of generate server aspnet’s Kafka code (AsyncAPI) waits for replies on the runtime’s new KafkaReplies, which generate client csharp shares; its behaviour is unchanged. The OpenAPI configurations of generate client csharp and generate server aspnet ignore their asyncapi section instead of rejecting it as an unknown option.

  • generate server aspnet (OpenAPI) writes the recursive DataAnnotations validation of ValidationFilter as Infrastructure/ModelValidator.cs, which the AsyncAPI Kafka application uses for its messages too; the behavior is unchanged.

  • apigenie and apigenie-mock link Mbed TLS 3.6 (Apache-2.0), the first runtime library from vcpkg; credits and license --library mbedtls list it. The release binaries grow by about 0.9 MB (macOS arm64: apigenie +3.5%, apigenie-mock +8.2%). The Docker images now include Alpine’s CA certificates.

  • apigenie-mock --proxy-to no longer refuses https:// and wss:// targets with exit status 2, and the 426 answer to gRPC over HTTP/1.1 on a TLS port says to ask for h2 by ALPN instead of offering Upgrade: h2c.

  • generate server spring is built on openapi_generator_java, the Java layer it shares with generate client java, whose Feign and HTTP interface writers now generate its spring-cloud and spring-http-interface libraries; their output is unchanged. Models get defaultImpl from discriminator.defaultMapping (x-defaultMapping before 3.2), name implicit discriminator mappings after the const value of the members (person instead of Person), no longer require the properties of anyOf/oneOf alternatives (Person and Company required both email and phone of ContactChannels), and treat an allOf with only a reference to a oneOf interface as that interface (3.0 documents produced a class extending an interface).

  • generate server go: an anyOf or oneOf whose members only list required properties no longer makes a union of untyped values, so allOf schemas holding one are merged into structs (instead of any) and Validate() checks the alternatives; a discriminator’s defaultMapping (x-defaultMapping in 3.0 and 3.1) decodes unknown values as its fallback variant, and an implicit mapping also accepts the const the variant gives the property. The runtime’s request binders moved from runtime_params.gen.go to a new runtime_bind.gen.go.

  • The OpenAPI library reads a media type’s itemSchema and a discriminator’s defaultMapping (3.2), and their x-itemSchema and x-defaultMapping forms in 3.0 and 3.1 documents.

  • generate server aspnet and generate client csharp share their C# models (src/openapi_generator_csharp). The server’s output changes in two ways: the implicit discriminator value of a oneOf variant is the const of its discriminator property when it has one (e.g. "person" instead of the schema name "Person"), and a oneOf or anyOf whose members map to one C# type has that type (e.g. List<string> for strings of the ipv4 and ipv6 formats, instead of List<object>).

  • The contract report of apigenie-mock (gate, JSON and JUnit) moved into the mock and openapi_mock libraries, shared with apigenie test; its output is unchanged.

  • format keeps the YAML comments of OpenAPI, Swagger and AsyncAPI documents (at the top and the end of the file, above keys, at the end of lines, inside sequences and between entries); comments move with their keys when properties are sorted. Before, every comment was dropped, and format --check reported commented files that were formatted.

  • lint --fix keeps the YAML comments of the OpenAPI, Swagger and AsyncAPI documents it rewrites, including those of the properties a fix replaces, instead of dropping them all.

  • format writes a Protobuf comment that follows a declaration on the next line (and that protoc attaches to it) on its own line again, instead of moving it to the end of the declaration’s line.

  • i18n has two subcommands: i18n extract extracts texts, as i18n did, and i18n apply applies translations. apigenie i18n FILE --output DEST becomes apigenie i18n extract FILE --output DEST, which also accepts -o.

  • apigenie-mock --stubs with a GraphQL schema or an AsyncAPI document exits with status 2 instead of being ignored; stubs answer the calls of a .proto file and, now, the operations of an OpenAPI document.

  • lint exits with status 1 when it reports issues of severity error (those remaining after --fix), so CI jobs fail on them; warnings keep the exit status at 0.

  • The CLI binary is about 2.8 MB (14%) smaller: the test-data generator’s string tables (names, addresses, words and the like) are stored compressed and decoded when first used, instead of as pointer tables.

  • The built-in English spelling dictionary is stored compressed (about 290 KB less in the binary) and decoded only when the first spell-check rule runs; the decoded bytes are released once the dictionary is parsed.

  • The third-party license texts shown by license --library are stored compressed and decoded only when one is printed.

  • format reports files that are neither OpenAPI nor AsyncAPI documents and leaves them untouched, instead of writing empty output.

  • Parse errors in JSON and YAML input now report why and where the input cannot be parsed.

  • lint with several input files writes a single report for the json, checkstyle, junit, html, sarif and tap formatters, instead of one document per file.

  • diff exit status and --update-version consider all changes, whatever the report shows, and a fixed severity of a node caps the severities of changes inside it (e.g. adding an optional parameter is non-breaking).

  • diff --update-version changes only the version in the file, keeping its formatting.

  • diff configuration is validated: unknown node types, properties and severities are reported.

  • generate server spring builds Java sources with a syntax tree instead of text templates. The generated code is the same, but imports are sorted and blank lines and indentation are consistent.

  • unused-shared-component is enabled again in the recommended (warning), strict and all rulesets, and skips documents without operations, which are libraries of components for other documents.

  • schema-no-optional-nullable-properties recognizes OpenAPI 3.0 nullable: true, referenced nullable schemas and anyOf/oneOf null branches, finds required in allOf members, and skips PATCH and merge-patch bodies, where null clears a value (option ignorePatchBodies).

  • spell-check (OpenAPI and AsyncAPI) now checks spelling: it reports each misspelled word of titles, summaries and descriptions with suggested corrections, using a built-in English dictionary, and skips code, URLs, identifiers and acronyms. Options words (extra words), suggestions and dictionary/affix (any Hunspell dictionary) are optional. The rule moved from the recommended ruleset to strict and all.

  • Internal: the Swagger 2.0 to OpenAPI 3.2 conversion behind convert --to openapi32 moved from the CLI into its own library, swagger_convert, which also reports what each conversion approximates or drops (not shown by the CLI yet). The output of convert is unchanged.

  • Internal: a release gate (release_gate.yaml) runs every test on Linux, Windows and macOS, the tests that build generated code with Java, Go, .NET and Python, the RealWorld demo, codegen conformance Levels 2 and 3 and the WebAssembly build; a cli-* tag only gets its draft release once the gate passed on the tagged commit.

Removed

  • generate doc html, the Astro documentation project it generated and the @mykulyak/api-genie-doc-html-template package it depended on, with the commands.generate.doc_html configuration section (see Upgrading). Internal: openapi_generator_doc_html and code_morph_astro.

Fixed

  • apigenie-mock --license without a library name prints API Genie’s license instead of the usage: an option whose value is optional counted as given only with a value.
  • generate server spring reads request bodies typed by a oneOf without discriminator (JSON Patch operations, Carpet Express’s recursive SearchFilter): such interfaces had no type information, so Jackson couldn’t instantiate them and the request answered 500. The interface now names a Deserializer built on the generated OneOfDeserializer, which reads the one member the value matches: a member that declares all its properties (or takes additional properties), reads it and, with Bean Validation, is valid. A value matching no member, or several, is a 400. useDeductionForOneOfInterfaces=true keeps Jackson’s deduction. Strings with format: uri (java.net.URI) no longer get @Pattern or @Size in generate server spring and generate client java: Bean Validation can’t check them on a URI and threw UnexpectedTypeException (the server’s createWebhookSubscription answered 500); their pattern isn’t checked.
  • The Pydantic models of generate client python, generate server faststream and the Python Kafka client check pattern: Annotated[str, matching(r"...")], a validator of _models.py compiling the pattern with Python’s re when a value is first checked (Pydantic’s default engine rejects look-arounds, back-references and large \w repetitions, which would break the model). Named string schemas keep their constraints in their aliases, e.g. ShipmentId: TypeAlias = Annotated[str, matching(r"^shp_[0-9A-Za-z]{24}$")]; a pattern re can’t compile, such as one using \p{L}, isn’t checked.
  • generate server faststream: published messages and replies no longer carry null for optional fields that weren’t set, which their schemas don’t allow. The publish functions and the subscribers’ replies send payloads through the runtime’s new encode_message() (the fields that were set), instead of letting FastStream serialize the models with every field. Subscribers of operations with a static reply channel now return a KafkaResponse, RabbitResponse or MQTTResponse, and their reply publisher states the reply’s schema (schema=).
  • The code of generate client ws and generate server ws passes typescript-eslint’s recommended type-checked rules: options and handler interfaces that would be empty are type aliases, the client runtime’s WebSocketLike.addEventListener has one overload per event instead of an any listener, and the shared AsyncAPI type mapper no longer writes unknown into unions ([key: string]: unknown) or intersections (an anyOf of required lists).
  • The validators of generate server express (validation: functions) import pointerToken only when a generated function uses it, so object schemas with additionalProperties: true no longer leave an unused import.
  • Generated values (apigenie-mock responses and messages, examples) satisfy an object schema’s oneOf/anyOf next to its properties, such as anyOf: [{required: [phone]}, {required: [email]}]: one alternative is picked and what it requires is generated. The alternatives used to be ignored, so such objects could lack every property they ask for.
  • apigenie-mock: the problems of an x-apigenie-mock extension name it by a well-formed JSON pointer (/paths/~1pets/get/x-apigenie-mock, not /paths//pets/get/...), and by its line when the value has no position of its own.
  • apigenie-mock generates AsyncAPI messages whose schemas use AsyncAPI’s string discriminator or an externalDocs given by $ref: such documents made every message without an example fail with “the payload has no schema values can be generated from”. A reply whose message has its correlationId in the payload now carries the request’s value (Carpet Express’s requestPickup gets a pickupConfirmed with its requestId).
  • apigenie-mock --proxy-to ws:// checks the server’s reply to a client message of a request/reply operation against the reply’s messages and its correlationId (message.reply.uncorrelated), instead of reporting it as channel.send.undeclared on a channel without send operations (ADR-110).
  • generate server faststream no longer fails with “The message schemas can’t be mapped to Python types” when a payload schema’s externalDocs is a $ref, as the AsyncAPI fixture’s CloudEvent has: the reference is followed.
  • The AsyncAPI contract report counts the requests of HTTP channels, mocked or proxied, as messages of their operations.
  • generate client csharp: the generated project compiles again: the client of the API is registered with AddHttpClient<TClient, TClient>(factory), as AddHttpClient<TClient> has no factory overload and bound the lambda to its configuration callback (CS1503). Unions read by the generated discriminator converters no longer carry [JsonPolymorphic]/[JsonDerivedType], which System.Text.Json rejects next to a custom converter (“does not support metadata writes or reads”) when sending or reading a oneOf with a discriminator.
  • generate client csharp and generate server aspnet: properties with a default value are settable instead of init-only, because the System.Text.Json source generator of .NET 8 assigns every init-only property and replaced the default of an absent one with 0/false/null; decimals described as strings (type: string, format: decimal) are written as JSON strings.
  • generate client python: form body fields whose encoding sets style or explode are written like query parameters of that style (deepObject: hints[building]=rear) instead of as JSON text. The package passes ruff’s default rules: __all__ lists are sorted as RUF022 expects, and the runtime uses X | Y unions and Self.
  • generate server express with validation: generated: objects without declared properties (maps), or with only patternProperties, no longer get validators with constant conditions (if (!(false)), false || ...), which failed ESLint’s no-constant-condition. The codegen conformance suite’s Level 2 now also type-checks and lints a validation: generated build.
  • generate client fetch: properties of application/x-www-form-urlencoded bodies whose encoding sets style or explode are written like query parameters of that style (deepObject: hints%5Bbuilding%5D=rear, also pipeDelimited, spaceDelimited and unexploded form) instead of as JSON text; the other object properties stay JSON. The runtime no longer calls String() on values of unknown type, so it passes typescript-eslint’s no-base-to-string.
  • generate server express routes QUERY and COPY operations (OpenAPI 3.2): their routes were typed with a method the runtime’s HttpMethod type lacked, so the generated package didn’t compile. An empty form field of a nullable property (location= for oneOf: [GeoPoint, {type: null}]) is read as null instead of failing validation, and the runtime passes typescript-eslint’s recommended type-checked rules.
  • generate client fetch, generate server express and generate server orpc: anyOf/oneOf alternatives that only add constraints (anyOf: [{required: [email]}, {required: [phone]}]) no longer add & unknown to the type.
  • generate server aspnet: an empty form field for an object (location=, how form style writes an empty or null value) is read as null instead of failing to parse as JSON with 400.
  • generate server spring: a @lombok.* annotation in a model’s x-class-extra-annotation drops the members Lombok generates for that model and adds Lombok to the pom, as additionalModelTypeAnnotations does; before, the model kept its own equals/hashCode next to @lombok.EqualsAndHashCode and the project didn’t build without Lombok. Deprecated parameters no longer get @Deprecated, which has no effect on parameters (javac -Xlint warns since JDK 21); the deprecation stays in @Parameter(deprecated = true).
  • generate client python and generate server faststream: allOf parts with oneOf/anyOf alternatives that only add constraints, such as Carpet Express’s ContactChannels (an email or a phone), are merged like other object parts, so Person and Company are models instead of Any. Discriminated unions dispatch on the mapping, on the schema names of the members it leaves out (the implicit mapping) and on the value each of those members fixes the property to with const or a single enum value (partyType: person of a schema named Person). Values whose tag is missing or unknown are read as the discriminator.defaultMapping schema (x-defaultMapping in OpenAPI 3.0 and 3.1), which joins the union if it isn’t a member; before, they matched no model. The unions keep their callable pydantic.Discriminator, as Field(discriminator=...) can neither fall back nor take several tags per model. The codegen conformance suite checks generate client python against the OpenAPI 3.1 and 3.0 fixtures too.
  • generate server fastapi passes the codegen conformance suite’s mypy, ruff and runtime checks without known failures, on the OpenAPI 3.2, 3.1 and 3.0 fixtures:
    • Discriminator tags follow the Python models: a member that fixes the property with const or a single enum value gets that value (party_type: Literal["person"] for a schema named Person), where the implicit mapping made it Literal["Person"] and rejected valid parties. A union with a discriminator.defaultMapping (x-defaultMapping in OpenAPI 3.0 and 3.1) dispatches with a callable Discriminator() that reads values with a missing or unknown tag as the default schema, which joins the union if it isn’t a member.
    • RootModel subclasses have their type argument (RootModel[int | str]; a forward reference in a cycle), which mypy --strict asked for.
    • A request body with several JSON media types, such as application/merge-patch+json and application/json-patch+json, is validated against the schema of the request’s Content-Type (415 for another one); the route took only the first media type’s schema. The body parameter is the union of the schemas and the served OpenAPI document lists every media type.
    • Integer enums in parameters and form fields convert the request’s text (dpi=300); Literal[203, 300, 600] rejected every value.
    • Multipart file parts described with contentMediaType or contentEncoding: binary (OpenAPI 3.1 and 3.2), not only format: binary, are UploadFiles, and arrays of them list[UploadFile].
    • Imports are sorted the way Ruff’s isort rules expect (case-insensitive, numbers by value, long single-name imports wrapped), and the imports at the end of modules of a cycle no longer carry a # noqa: E402 that Ruff reports as unused.
  • apigenie test --generate sends multipart file parts (format: binary, contentMediaType or contentEncoding: binary) with a file name and the Content-Type of their encoding, and an array as a part per item; servers that only take parts with a file name as files, such as FastAPI’s and ASP.NET Core’s, rejected the generated requests.
  • generate client python and generate server faststream: an object schema with properties of its own and oneOf or anyOf alternatives that are objects (each narrowing a property or requiring others) becomes a union of one model per alternative with the object’s properties merged in. The properties were lost (a union of classes holding only the alternatives’ properties), and anyOf of required lists, such as “an email or a phone”, was Any.
  • The CLI no longer writes .apigenie.telemetry in the current directory; set APIGENIE_TELEMETRY_FILE to keep a local telemetry log.
  • apigenie-mock and apigenie test type the fields of form and multipart bodies (and deepObject and delimited parameters) whose properties come from allOf members, references included: residential=false of a body allOf: [$ref Address, {...}] is the boolean false, not the string. They were validated as strings, so valid requests were reported or rejected with 422.
  • YAML output quotes strings that YAML 1.1 parsers (PyYAML, SnakeYAML, go-yaml v2) read as booleans, numbers or merge keys: yes, No, ON, off, y, N, 0b101, 1_000, 1:30, << and the like. Before, bundle, overlay, lint --fix, convert and format with the best-fit style wrote them plain, so other tools read enum: [yes, no] as booleans and 1:30 as 90.
  • apigenie-mock validates requests and responses whose schemas refer to themselves (trees, nested filters, recursive oneOfs). They were accepted without any check, in OpenAPI and AsyncAPI documents alike. The lint rules schema-no-contradictions and schema-oneof-no-overlap now analyze such schemas too.
  • OpenAPI 3.2 documents: examples written with dataValue and media types that refer to components/mediaTypes are read. Generators no longer drop them (Spring rendered such examples as null, Go typed such problem responses as any), and apigenie-mock answers with those examples.
  • generate server fastapi routes the OpenAPI 3.2 query method and the methods of additionalOperations (such as COPY) with @router.api_route(..., methods=[...]). They were emitted as @router.query/@router.copy, which APIRouter doesn’t have, so a server generated from a document with such an operation failed to import.
  • convert --to openapi32 converts the structure of Swagger 2.0 documents, not only their references: body and formData parameters become request bodies with the media types of consumes (files as multipart/form-data), collectionFormat becomes style and explode, response schemas, headers and examples move into content per media type of produces, global body parameters become components.requestBodies, and references to parameters and responses point into components. Schemas get type: file as binary strings, object discriminators, numeric exclusive bounds and x-nullable as a null type. Before, most converted documents could not be read (2163 of the 2168 Swagger documents of the openapi-directory corpus convert into readable documents now, the others are invalid).
  • Many memory-safety and robustness issues in the parsers, bundler, reader and linter.
  • diff changelog reports rate added, removed and modified operations by their own changes rather than their path’s, report request body changes as such instead of as response codes, and no longer list a removed deprecated operation as deprecated.
  • generate server spring numbers enum constants whose names collide (e.g. the values +1 and -1, both named _1), like openapi-generator, so the generated models compile.
  • test reports a rejected certificate as such for wss://, mqtts:// and grpcs:// URLs whose host name has several addresses: it stops at the first address that answers instead of trying the others and reporting the last one’s “Connection refused” (on macOS, localhost is 127.0.0.1 and ::1, and a server often listens on one).
  • The YAML and JSON writers report an alias of a collection that contains it as soon as they meet it again, instead of after expanding it to the depth limit, which overflowed the JavaScript stack of the WebAssembly build.
  • generate server spring with useSpringBoot4 no longer declares spring-boot-starter-restclient (or -webclient) twice, which Maven rejects as a duplicate dependency.

0.8.2 (2025-11-13)

Pre-release, published on GitHub.

0.8.0 (2025-07-12)

Pre-release, published on GitHub.