Skip to content

Presentation API — contract

The post-implementation contract for iiiris's IIIF Presentation API support (roadmap Phase 1). Operator/consumer how-to lives in ../presentation.md; this file is the durable "what this subsystem guarantees" a re-implementation must satisfy.

What it does

iiiris serves IIIF Presentation manifests and collections in both 3.0 and 2.1, alongside the Image API. An identifier is resolved through the same hook + source path the Image API uses: if it names a stored JSON document it is served; if it names a directory it is auto-derived into a manifest (one canvas per image) or a collection (one member per sub-directory). A bundled viewer renders any manifest. The surface is stateless and on by default — part of the zero-config path.

Surface

HTTP routes

Method Path Purpose
GET /iiif/presentation/3/{id} 3.0 manifest or collection (shape depends on what {id} resolves to)
GET /iiif/presentation/2/{id} 2.1 manifest or collection
GET /view/{id} Bundled viewer for the manifest of {id} (Mirador default; ?viewer=uv for Universal Viewer)
GET /static/viewer/{path…} Self-hosted viewer assets (Mirador bundle)
GET /static/viewer/uv.html UV bootstrap shim (text/html) the ?viewer=uv iframe loads

{id} may contain slashes. All routes are mounted only when the Presentation API is enabled (default).

Public Go API — internal/presentation

Version-agnostic, pure (no I/O, no govips); the server layer supplies the listing + probe.

  • Parse / URLs: ParsePath(v, rest) (Request, error), CanonicalID(base, v, id), ContextURI(v), IDField(v).
  • Serve normalisation: NormalizeID(v, body, canonicalID) ([]byte, error) — rewrites only the top-level id/@id.
  • Derive builders: BuildManifest(v, ManifestInput), BuildCollection(v, CollectionInput) → JSON. Inputs: ManifestInput, CollectionInput, Canvas, Member, MetadataPair.
  • Descriptors: Descriptor, ChildOverride, MetadataPair; ParseSidecar(name, data), Merge(base, over), Descriptor.ChildLabel/LabelOr; SidecarNames; the optional Describer hook capability.
  • Conformance: Validate(v, body) ([]Problem, error), Problem.

Server wiring — server.Deps

ManifestCache cache.InfoCache · PresentationEnabled bool · ViewerDefault string (mirador|uv) · PresentationValidate bool.

Config

presentation:
  enabled: true          # default true; false = image-only server
  default_viewer: mirador # mirador (self-hosted) | uv (CDN)
  validate: false        # warn-only structural conformance checks
caches:
  manifest: { backend: heap, max_bytes: 16777216 }  # dedicated slot

All fields are optional; defaults keep the surface working with no config.

On-disk format — sidecar descriptor

A manifest.yaml / manifest.yml / manifest.json in a derived directory (looked up in that order):

label: <string>
metadata:
  - { label: <string>, value: <string> }
canvases:
  <filename>: { label: <string> }   # per-child label override

Contracts

  • Default-on, stateless, zero-config. The Presentation routes mount unless presentation.enabled: false; nothing under presentation: is required for the default path. No new state is introduced (derived docs are cached, not stored).
  • Resolution is the Image API's. hook.Resolve(id) → (backend, key) then source.Open(key). A child image identifier is dir + "/" + filename, so the same hook maps it consistently.
  • Document → serve as-is. Only the top-level id (@id in 2.1) is rewritten to the canonical /iiif/presentation/{v}/{id} URL; nested values are byte-preserved. iiiris never rewrites or transcodes a stored document beyond that.
  • Directory → derive. Images → a Manifest (one canvas per image); sub-directories → a Collection (one member per sub-directory, classified Manifest-vs-Collection one level deep, members resolved lazily on their own request). Non-image files are skipped; a directory with neither is 404.
  • Canvas dimensions come from a header-only image.Probe — no full decode. govips stays single-consumer (internal/image); the Presentation code calls only image.Probe.
  • Each derived canvas paints an Image API service on this same server, with strict version pairing: a v3 manifest links an ImageService3 at /iiif/3/{imgid}; a v2 manifest an ImageService2 at /iiif/2/{imgid} (profile level2.json). Plus a confined-size thumbnail.
  • Derive override precedence: conventions < sidecar < hook. Filename/path conventions are the always-present baseline; a directory sidecar overrides them; a Describer hook is authoritative. A malformed override is logged and ignored — derivation never fails because an override is bad.
  • No cross-version conversion. A stored 3.0 manifest is served as-is under /3/; it is not transcoded to 2.1 for /2/ (and vice versa). Derivation emits the route's version from the source directory.
  • Dedicated manifest cache slot. Derived/served documents cache via ManifestCache (the cache.InfoCache interface, built by cache.BuildInfo from caches.manifest), keyed by host + version + identifier — a separate slot from InfoCache. Served stored documents additionally carry HTTP cache semantics (ETag, conditional GET → 304) and application/ld+json content negotiation.
  • Validation is warn-only. With presentation.validate: true, served and derived documents are checked against the version's required structure and problems are logged at WARN; documents are never rejected. Every document iiiris derives is structurally valid — enforced by the test suite and the iiif-presentation-validate CI stage (a live-server harness reusing Validate).
  • Viewer. /view/{id} renders Mirador by default — self-hosted via go:embed, served under /static/viewer/, referenced root-relative so it loads over the page's own scheme (offline, no mixed content). ?viewer=uv (or default_viewer: uv) serves an iiiris-hosted uv.html shim (text/html) that loads Universal Viewer from a version-pinned CDN and reads the manifest from the URL hash.
  • Scheme correctness is the operator's job. The manifest URL and the image-service URLs within it are built from the request scheme/host (publicBase); behind a TLS-terminating proxy the proxy must send X-Forwarded-Proto: https (and X-Forwarded-Host), or a browser blocks the http:// URLs as mixed content.

Out of scope

  • Manifest author/edit (write APIs) — stateful; roadmap Phase 3 / 1.x.
  • Cross-version conversion of stored documents — see contracts.
  • Annotation content / Content Search — roadmap Phase 3.
  • Presentation versions other than 3.0 / 2.1.
  • Self-hosting Universal Viewer — UV 4.x's ~10–21 MB runtime asset tree is loaded from a CDN; only Mirador (a single self-contained bundle) is embedded.
  • Deriving the source root itself — a Presentation identifier must name a sub-path; the empty/root identifier is not derivable.