Skip to content

API Genie CLI 0.10.0

Latest

Choose a tag to compare

@mykulyak mykulyak released this 25 Oct 21:16
· 3 commits to main since this release
e85d5e9

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.