Zally lints OpenAPI specs against Zalando's RESTful guidelines
A minimalistic, simple-to-use API linter
At a glance
- What is it?
- Zally is an OpenAPI 2 and 3 linter from Zalando that ships as a REST API, a CLI and a web UI, with rules drawn from Zalando's RESTful API Guidelines and a Kotlin API for writing your own checks. The caveat that matters: the last tagged release was v2.1.1 in December 2022, even though the repository is still being pushed to.
- Who is it for?
- Zally fits a team with an API-first workflow that wants a linter to run in CI against a published specification, is happy with Zalando's RESTful guidelines as the default rule set, and can invest an afternoon in writing Kotlin checks for the rules it cares about. It does not fit a team that needs a linter with a 2026 release cadence, since the last tagged version is v2.1.1 from 2022-12-09, and it does not fit a team unwilling to run Postgres and a Spring Boot server.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One rule set, three ways to run it
Zally is a quality assurance tool, and the README describes it as a linter for OpenAPI specifications that increases the quality of APIs, checks compliance, delivers early feedback for API designers, and ensures the same look and feel across APIs.
The architecture is the interesting part. There are three interfaces over one set of rules: a RESTful API, a CLI and a Web interface. The CLI is described as using the server in the background so that you can check your API on the spot, which is a design choice with consequences. The CLI is not a standalone binary that parses a file on its own; it drives a running server, so your local linting loop depends on a server being up.
The Web UI complements that with two things the API and CLI do not give you. It shows which rules are implemented, which matters when you are trying to work out what a linter will and will not catch, and it includes an online editor for API definitions, so you can paste a specification and lint it without touching a file.
The README also names the alternative approach explicitly. It says Zally is not the only linter for OpenAPI v2 and v3 and links to an article comparing different OpenAPI linters, then argues for its own choice on three grounds: support for Zalando's RESTful Guidelines, the three ways of using it, and being highly customizable with Kotlin.
Zalando's RESTful guidelines are the default, not the only option
The standard configuration checks your APIs against the rules defined in Zalando's RESTful Guidelines, and the README is explicit that anyone can use it out of the box. The project also frames support for the API-first approach as a feature.
That default matters more than it first appears, because a linter's value is in the rule set rather than in the parsing. Zalando's guidelines are a public, opinionated document about REST API design, and Zally turns it into executable checks. The web UI exists partly to make that set legible, showing the implemented rules so a designer can see what compliance means in practice before a CI job rejects a pull request.
The customisation story is the Kotlin API. The features list names a Java and Kotlin API for new checks plus helper functions, and the README says Zally is highly customizable with Kotlin. The repository backs that up with a documentation folder that includes a rule development guide, and the server side is a Gradle project with a `src/` tree of its own.
So the intended usage pattern is layered. Start with the standard configuration, see which rules your existing specification violates, and then decide whether to accept those findings as your house style or write Kotlin checks for the ones that matter to you and configure the rest away.
The ignore extension is how you stop fighting your own linter
The features list has one entry that is quietly the most important for adoption: ignore functionality, provided by an `x-zally-ignore` extension. This is an OpenAPI vendor extension, a field added to the specification object, and Zally reads it to suppress a rule for a particular operation or element.
Every linter has this problem. A rule that is right in general is wrong for a particular endpoint, and without an escape hatch the only options are to disable the rule project-wide or to live with a false positive forever. A vendor extension scoped to the operation is the cleaner answer, because the exception travels with the specification and is visible in code review where the endpoint is defined.
The README does not document the exact syntax beyond naming the extension, so the practical detail to check is which level of the specification the extension is honoured at, whether on a path item, on an operation, or on an individual response. That is a documentation question rather than a design question, and the usage documentation in the repository's `documentation/` folder is where to look.
This is also the feature that decides how noisy the tool feels in day one. A linter with no ignore mechanism gets switched off after the first week of unrelated findings. One with a per-operation escape hatch gets adopted.
Docker Compose brings up the UI, the server and Postgres
The quick start is a single command, because the whole stack, web UI, server and database, is defined in a Compose file in the repository.
docker compose up -dAfter that the README says the Web UI is accessible on `http://localhost:8080` and the Zally server on `http://localhost:8000`. The Compose file confirms the wiring: the web-ui service builds from `./web-ui` and publishes port 8080 to 3000 in the container, the server service builds from `./server` and publishes 8000 to 8000, and it depends on a `postgres` service using the `postgres:13` image with a `postgres-data` volume for the database.
The environment variables are worth reading, because they are how you point Zally at other things. The web UI takes `ZALLY_API_URL`, defaulting to `http://localhost:8000`, and a comment at the top of the Compose file shows the pattern for running the container on a public URL by passing that variable. The server runs with `SPRING_PROFILES_ACTIVE=dev`, a `SPRING_DATASOURCE_URL` pointing at `jdbc:postgresql://postgres.local:5432/zally`, and `MANAGEMENT_PORT=7979`.
Both services have health checks, the web UI with `curl -f` against its own port and the server against `http://localhost:7979/health`. The `MANAGEMENT_PORT` is not the API port, it is the management port, and the health check deliberately targets the second one. That separation is a small detail, but it is why the server exposes 7979 in addition to 8000.
Adding your own rules means writing Kotlin against the server
The repository layout shows a monorepo with three code surfaces: `server/`, `web-ui/` and `cli/`, plus `documentation/` and `examples/`. The server is a Gradle project, which the release section confirms by pointing at `server/gradle.properties` for the current version and at the `./gradlew` wrapper for builds.
The publishing workflow is documented in unusual detail, which is a good sign for a project you might depend on. Releasing means creating a `release-<release-version>` branch, updating the version in `server/gradle.properties` from `-SNAPSHOT` to a final version, regenerating the mime types configuration with a Gradle task, and then building and publishing with the Gradle wrapper.
Publishing to Maven Central requires a signing plugin and `OSSRH_JIRA_USERNAME` and `OSSRH_JIRA_PASSWORD` environment variables. The steps continue with committing the release version, tagging, bumping to the next `-SNAPSHOT`, pushing the branch and the tag, and opening a pull request with the version update.
There is also a `jitpack.yml` at the top level, which is the configuration for building from JitPack. And the `examples/` directory holds a sample Swagger specification, `examples/sample_swagger_api.yaml`, plus a Travis configuration example, which is the quickest way to see what Zally expects as input.
The licence carries an exception, and the last release is old
The licence is not plain MIT. The README says MIT license with an exception and points to the licence file. When a project attaches an exception to MIT, the exception text is what governs the specific case it covers, and the plain MIT text does not tell you the whole story. You need to read the LICENSE file before you rely on the identifier, and the practical effect of the exception is a question for whoever handles licensing at your company rather than for this article.
The other fact that deserves weight is the release history. The listed releases are v2.0.0 on 2020-07-03, v2.1.0 on 2021-08-20 and v2.1.1 on 2022-12-09. The last push to the repository was on 2026-10-19, so the code is being worked on, but the most recent published version is more than three years old.
That combination, recent commits and an old tag, tells you something specific. Work is landing on the default branch without a new release, which means a consumer pinning to a tagged version is running code from 2022, while someone building from the branch is running something much newer with an unknown set of changes. For a linter that people usually run as a service in CI, the practical questions are which version the Docker images you deploy were built from, and whether the default branch is stable enough to build yourself.
The repository also carries a SECURITY file, a MAINTAINERS file and a CONTRIBUTING file, and the README points contributors at the project board and the issues tracker as the places to find something to work on.
Editorial conclusion
Zally fits a team with an API-first workflow that wants a linter to run in CI against a published specification, is happy with Zalando's RESTful guidelines as the default rule set, and can invest an afternoon in writing Kotlin checks for the rules it cares about. It does not fit a team that needs a linter with a 2026 release cadence, since the last tagged version is v2.1.1 from 2022-12-09, and it does not fit a team unwilling to run Postgres and a Spring Boot server. Before adopting it, start the stack with `docker compose up -d`, open the web UI on `http://localhost:8080`, and lint `examples/sample_swagger_api.yaml` to see how many of the default Zalando rules your own specification would trip.
Frequently asked questions
How do I run Zally locally?
Run `docker compose up -d`, which starts the web UI, the Zally server and a Postgres database. The Web UI is then accessible on `http://localhost:8080` and the Zally server on `http://localhost:8000`.
Which OpenAPI versions does Zally support?
The features list says Zally supports OpenAPI 3 and Swagger OpenAPI 2 specifications, and it lints external files as well as API definitions typed into the web UI's online editor.
Can I ignore a Zally rule for a single endpoint?
Yes. The features list names ignore functionality provided by an `x-zally-ignore` extension, which lets a rule be suppressed for a specific part of the specification rather than turning the rule off everywhere.
How do I write my own rules for Zally?
The features list names a Java and Kotlin API for new checks plus helper functions, and the README says Zally is highly customizable with Kotlin. The repository's documentation folder includes a rule development guide, and the server is the Gradle project the custom checks live in.
What licence is Zally released under?
The README says MIT license with an exception and points to the licence file, so the plain MIT identifier does not tell the whole story. The LICENSE file in the repository is what governs, and this is not legal advice.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zalando-zally)Was this useful?