Martian and the case for describing an HTTP API instead of calling it
The HTTP abstraction library for Clojure/script, supporting OpenAPI, Swagger, Schema, re-frame and more
At a glance
- What is it?
- Martian is a Clojure and ClojureScript library that turns an OpenAPI or Swagger description, or a plain Clojure map, into a client you call by operation name with keyword parameters, hiding URL construction, encoding and coercion behind an interceptor chain. The interesting engineering is the interceptor design, and the interesting limitation is that the description has to exist before your code can call anything.
- Who is it for?
- Martian is a good fit for a Clojure team that consumes an API described by OpenAPI or Swagger and wants the request construction, parameter coercion and validation to live in one place rather than spread across call sites, because the client is generated from the description and every HTTP library plugs in as an interceptor.
- 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 8 days ago.
- What is it written in?
- Mainly Clojure, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem is that HTTP details leak into your code
The README puts it plainly: calling HTTP endpoints can be complicated. You have to construct the right URL with the right route parameters, remember what the query parameters are, what method to use, how to encode a request body, coerce a response, and many other things that leak into your codebase.
Martian takes a description of these details, either from your OpenAPI or Swagger definition, or just as lovely Clojure data, and provides a client interface to the API that abstracts you away from HTTP and lets you simply call operations with parameters.
The call site looks like this:
(martian/response-for m :create-pet {:name "Doggy McDogFace" :type "Dog" :age 3})
;; => {:status 201 :body {:id 123}}Two things are happening there. The operation is named, :create-pet, rather than the method and path, and the parameters are a Clojure map with keyword keys. The URL construction, the body encoding and the response coercion are all handled by the library because the description told it how.
The library covers Clojure, ClojureScript and Babashka, and it is published on Clojars under the com.github.oliyh group. The audience is a Clojure developer who is tired of writing the same request-building code for every endpoint of a described API, and the reference implementation the README bootstraps against is the Pedestal petstore API, which is a reasonable place to see the design working end to end.
The interceptor chain is what makes it extensible
Martian ships implementations for a list of HTTP client libraries as separate modules, and the README is clear that this is not a closed list. Any other HTTP library can be used because of the extensibility of the interceptor chain, and the chain also allows custom behaviour to be injected in a uniform and powerful way.
That is the central architectural idea, and it is more than a plugin mechanism. A Martian client is not an HTTP client with a nicer API bolted on. It is a chain of interceptors that each get a chance to see and modify the request before it goes out, and the response on the way back. Coercion, encoding, validation, logging, authentication, anything that wants to inspect a call, slots into that chain rather than into your call sites.
The supported clients are listed with a platform matrix, which is worth reading because it is uneven. hato and clj-http are JVM only. clj-http-lite, http-kit and bb/http-client are JVM and Babashka. cljs-http and cljs-http-promise are ClojureScript only, which is the sensible split since the JS clients do not exist on the JVM.
So the choice of HTTP library is still yours, and Martian is the layer above it. The trade is that you are now on a different HTTP library than the one your other code uses, unless you pick the module matching your existing choice, in which case the abstraction is nearly free.
martian-test and the case for never calling the server
The martian-test module is the feature that changes how you write tests, and it is described in the README as allowing you to assert that your code constructs valid requests to remote servers without ever actually calling them, using the OpenAPI or Swagger spec to validate the parameters. It can also generate responses in the same way, ensuring that your response handling code is also correct.
The generating part is the interesting half. A test double that produces a response body matching the spec is not a mock you maintain by hand, it is one derived from the same description the client was built from. The README lists generative testing and non-generative testing as separate sections, so the library distinguishes generating a response from the description against supplying a hand-written one.
The practical effect is that a change to the API description breaks the tests, in both directions. If your code stops constructing a valid request, the test fails. If the spec changes, the generated response changes and the response handling code has to keep up. For a team where the API belongs to someone else, that is a mechanism for noticing drift before production does.
There is also a recording and playback module, martian-vcr, which the table of contents lists as its own section. A VCR-style record and replay sits naturally next to a spec-driven client, since the recording can be validated against the same description.
Beyond OpenAPI: local files, plain data, and defaults
The README's table of contents lists a good deal more than the OpenAPI path, and a few of those sections matter more than the headline feature.
Bootstrapping from a local file is a separate section from the remote URL bootstrap, which matters for reproducible builds and for working offline. The no Swagger, no problem section covers the case where there is no description at all, and the point there is that the same machinery still applies because the description is just data.
Idiomatic parameters and parameter defaults are their own sections, which tells you the library is concerned with how a call feels rather than only with what it does. Route name sources is a section too, and that addresses a practical question: where the operation name in :create-pet comes from, which in the OpenAPI world is the operationId.
Then the response side. Handlers validation, response validation and built-in media types are covered separately, along with custom media types and built-in encoders options. The split between encoding a request and validating a response is the interesting one, because the two live on opposite sides of the interceptor chain and can fail for different reasons.
The ClojureScript, Java and development-mode stories
Three sections in the table of contents cover things a pure library README rarely mentions.
The Java section exists because the library is not only for Clojure. A Clojure library that can be consumed from Java is a different adoption story, and it means the API surface was designed with an interop shape in mind rather than only with Clojure ergonomics in mind.
The re-frame module appears in the repository layout as a directory, and the description names re-frame as one of the supported things. In a ClojureScript application, the abstraction is valuable for a specific reason. The client is built at compile time from a description, and the interceptor chain runs in the browser, so the same Clojure code that talks to the API in a test can run against a mock in a browser build.
Development mode is a section of its own, and it is a sensible thing for this kind of library to offer. When you are writing against a described API, the slowest part of the loop is often the round trip to a server you do not control. A development mode that shortens that loop is more useful here than in a library whose contract is just a URL and a body.
The caveats section is worth reading before you commit, and the README does not summarise it.
Editorial conclusion
Martian is a good fit for a Clojure team that consumes an API described by OpenAPI or Swagger and wants the request construction, parameter coercion and validation to live in one place rather than spread across call sites, because the client is generated from the description and every HTTP library plugs in as an interceptor. It is a poor fit if you are calling an undocumented API with no description to hand, since the no-Swagger path means hand-writing the same data the library would otherwise derive, and a poor fit for a JavaScript or Python team, since the supported client list is Clojure, ClojureScript and Babashka specific. Before adopting, check that the API you need has an OpenAPI description, decide whether you want the OpenAPI path or the plain data path, and read the caveats section, because the library pushes validation work onto you as soon as you enable it.
Frequently asked questions
What does the Martian library do?
Martian takes a description of an API, from an OpenAPI or Swagger definition or as plain Clojure data, and provides a client interface that abstracts away HTTP, so you can call operations with parameters and keep URL construction, encoding and response coercion out of your codebase.
Which HTTP client libraries does Martian support?
The README lists hato, clj-http, clj-http-lite, http-kit, bb/http-client, cljs-http and cljs-http-promise, each shipped as a separate module with a platform matrix. The README also states that any other HTTP library can be used, because of the extensibility of the interceptor chain.
What does the martian-test module let me do?
It lets you assert that your code constructs valid requests to remote servers without ever calling them, using the OpenAPI or Swagger spec to validate the parameters, and it can generate responses the same way so your response handling code is checked as well. The README separates generative testing from non-generative testing.
Can I use Martian without an OpenAPI or Swagger definition?
Yes. The README has a section titled No Swagger, no problem, and the description says Martian takes a description of the details either from an OpenAPI or Swagger definition or just as lovely Clojure data, so the same machinery applies when there is no spec.
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/oliyh-martian)Was this useful?