Use Case
As a manifest creator or host, or as a viewer or client developer, you may want to allow access to multiple versions of a manifest, each conforming to a different IIIF Presentation API major version. For the manifest creator or host, you may want to allow a wider range of clients the ability to view the resources in the manifests. For a client developer, you may want your client software to privilege one version of the API while allowing users to view manifests from a wide range of hosts with unpredictable and possibly uneven API conformance.
Implementation notes
This recipe describes publishing IIIF v2 and v3 resources using the Presentation API at the same URL by using HTTP Content Negotiation. It is presented as an alternative approach both to publishing version-specific URLs and either requiring direct requests for retrieving the desired resource version or redirecting manifest requests from an neutral URL to a version-specific one. (HTTP Content Negotiation can be used with the Image API, but is a different matter.)
Using Content Negotiation is useful in cases where changing the location of these resources would cause annotations targeting those resources, particularly those created and stored by third-party users, to no longer work. For example, an annotation targeting a region of a canvas that relies on resolving that canvas and any media (image, video, or audio) to show to end users. Using multiple URLs risks losing any work your users have done to annotate these canvases. Content Negotiation also provides a stable URL to reference a IIIF Manifest that will work, even as providers transition through supported IIIF versions.
Content Negotiation is an established method of requesting varying responses from a server. In this case, the server will be asked to vary its response for either a version 2 or version 3 of a IIIF resource formatted in JSON-LD. This can be accomplished by using the Accept
HTTP header. The value of this header contains a profile
section that varies according to the IIIF version that is desired. Servers that implement this method should also provide a default response if the request does not contain these values. The examples below illustrate this process.
The IIIF API specification gives the values for the header. It follows a straightforward format:
application/ld+json;profile="http://iiif.io/api/presentation/{VERSION}/context.json"
If successful, the server should respond with a value in the Content-Type
header that mirrors the requested value.
If the server is unable to respond as requested — for example, it does not support the version requested — then it may either choose to return its default response (a manifest for a different IIIF Presentation version), or it may choose to return a 406 Not Acceptable
status code. Returning such an error implies support for Content Negotiation.
Where possible, servers are encouraged to return by default the latest IIIF Presentation API version. Since the server may substitute a different version than the one requested if the requested one is not available, clients will need to check the version information in the returned manifest. Simultaneously, client software must account for the response indicating the server does not support Content Negotiation and is responding with the manifest available at the requested URL. Put another way, clients must consider multiple possibilities and inconsistent affordances when requesting IIIF manifests from a server.
Restrictions
There are several restrictions to using this pattern. Perhaps the most notable is that typical people using a web browser to view IIIF resources cannot be expected to know how to vary their Accept
headers, so their viewing will likely be dependent on the client they are using. To view varying responses, a person must have the ability to set the HTTP request headers. A web-based client can provide this capability, but none currently do. People with sufficient combination of knowledge, access, and support can also use non-browser tools such as Postman.
This is an active area of work in web specifications, and may change as methodologies develop. The W3C has a current working group looking at Content Negotiation by Profile. This specification offers dedicated Accept-Profile
and Content-Profile
headers; however, these recommendations are
still in draft form.
Example
These examples will use the cURL
command-line HTTP client to control the request and view the response from the server. The -H
flag controls the value of the request header. The -v
flag makes the process “verbose” so that the response headers can be inspected. The leading $
is used to illustrate a prompt and is not part of the command.
The first example shows a basic request to an HTTP service, but with an explicitly-set Accept
header:
$ curl -v -H "Accept: application/ld+json;profile=http://iiif.io/api/presentation/2/context.json" "https://iiif.io/api/cookbook/recipe/0057-publishing-v2-and-v3/manifest.json"
This provides a default response of a IIIF v2 manifest. Looking at some of the request (>
) and response (<
) values from cURL:
> GET /api/cookbook/recipe/0057-publishing-v2-and-v3/manifest.json HTTP/2
> Host: iiif.io
> User-Agent: curl/8.7.1
> Accept: application/ld+json;profile=http://iiif.io/api/presentation/2/context.json
< HTTP/2 200
< server: nginx
< date: Fri, 07 Mar 2025 15:49:39 GMT
< content-type: application/json
The response content should be a v2 manifest:
To request a IIIF v3 manifest at the same URL the Accept
header value can be adjusted:
$ curl -v -H "Accept: application/ld+json;profile=http://iiif.io/api/presentation/3/context.json" "https://iiif.io/api/cookbook/recipe/0057-publishing-v2-and-v3/manifest.json"
Looking at the same request (>
) and response (<
) values from cURL as for the previous example, we see the difference in the request’s Accept
header:
> GET /api/cookbook/recipe/0057-publishing-v2-and-v3/manifest.json HTTP/2
> Host: iiif.io
> User-Agent: curl/8.7.1
> Accept: application/ld+json;profile=http://iiif.io/api/presentation/3/context.json
< HTTP/2 200
< server: nginx
< date: Fri, 07 Mar 2025 15:54:11 GMT
< content-type: application/json
The response now should be a v3 manifest. Note that the value of the manifest’s id
field is the same as in the v2 manifest.