Vendor MIME types
API versioning by vendor MIME type — application/vnd.acme.v1+json
and friends — is a common alternative to /v1/ URL prefixes. The
runtime supports it, but each MIME registers as its own entry: the
+json structural suffix is not sniffed automatically.
Spec
Server registration
Both versions decode the same JSON wire format, so they share the codec. They still need separate registrations:
api := untyped.NewAPI(doc).WithJSONDefaults()
api.RegisterConsumer("application/vnd.acme.v1+json", runtime.JSONConsumer())
api.RegisterProducer("application/vnd.acme.v1+json", runtime.JSONProducer())
api.RegisterConsumer("application/vnd.acme.v2+json", runtime.JSONConsumer())
api.RegisterProducer("application/vnd.acme.v2+json", runtime.JSONProducer())Full source: docs/examples/contenttypes/vendortypes/main.go
JSONConsumer() and JSONProducer() are side-effect free — calling
them per registration is fine.
Picking the version inside the handler
The matched Content-Type is on the request context — recover it via
runtime.ContentType(r.Header):
handlePost := func(r *http.Request, body any) (any, error) {
ct, _, _ := runtime.ContentType(r.Header)
switch ct {
case "application/vnd.acme.v1+json":
return handleV1(body)
case "application/vnd.acme.v2+json":
return handleV2(body)
}
return nil, errors.New(http.StatusUnsupportedMediaType, "unsupported version")
}Full source: docs/examples/contenttypes/vendortypes/main.go
For the response side, the runtime has already chosen a producer
that matches the client’s Accept — your handler returns a value
and the matched Producer writes it. If you need the response shape
to differ between versions, branch on the negotiated content-type
the same way (see Context.ResponseFormat).
Matching rules — what about MIME parameters?
The
asymmetric matching rule
applies. If your spec lists a parameterised type
(application/vnd.acme+json;version=1), an inbound request with no
version parameter does not match. Recommend the simpler form —
parameter-distinct types are rarely worth the surprise.
For the v0.30 parameter-honouring change and the per-call opt-out, see standalone / content negotiation.
Adding a non-JSON vendor type
The exact same shape — register a separate Consumer/Producer per declared MIME, even when several share the same codec:
See custom codec for the msgpackcodec package
itself.
When not to do this
Vendor MIME types compose poorly with browser clients
(Accept: */* is unspecific), with caches that key on URL alone, and
with HTTP middleware that inspects the URL. URL-based versioning
(/v1/...) sidesteps all three. Pick vendor MIME types when the API
is server-to-server and you genuinely need the same URL to serve
multiple representations.