Source Model — IIIF: by reference in, standards out, Fichero as a server — Design Spec (#5323)
Milestone: source-model Manual: TBD — a section in “Bringing sources in” that explains opening a collection from a library’s IIIF manifest without copying it, what stays remote and what is cached, and a section in “Sharing” that explains publishing a library as IIIF for other viewers and researchers.
Design-led (Testing Constitution). Status: DRAFT (approved by the maintainer 2026-10-04,
5323; APPROVED status waits on the manual section, the Test matrix and a citing test; see
Rulings). A slice of the source model: read
source-model.mdfirst. It homes the IIIF work thatimporter.mdand #5120 recorded as homeless. Formats-level reading and writing (the georeference extension, W3C annotation shapes) stays informats-and-training.mdandmaps-and-georeference.md; this slice owns how a IIIF source lives in a library and how a library is served as IIIF.
Intent
Most of the world’s digitised manuscripts, maps and archives are already published through IIIF. A researcher should be able to work on them in Fichero without first downloading the collection: paste a manifest or collection link, and the pages appear in the library at once, drawn from the holding institution’s image server at the size the screen needs. Transcription, segmentation, models, notes and the knowledge graph then work on those pages exactly as on a local scan. The pixels stay where the institution keeps them; what Fichero adds (segments, readings, claims) is Fichero’s.
In the other direction, everything Fichero knows about a source can leave as IIIF: a manifest whose pages point back at the original image service, with Fichero’s segments and readings as W3C annotations on them, so Mirador, the Universal Viewer, Tify or another Fichero can show the work. And a library that is shared can be a IIIF endpoint (images, manifests, annotations and search), so colleagues can read it in any IIIF viewer, and a static export can publish it.
Prior art / best practices
- IIIF Image API 3.0 (regions, sizes,
info.json, compliance levels 0–2) and Presentation API 3.0 (Collection, Manifest, Canvas, AnnotationPage). Fichero adopts both, reads 2.1 as well (much of the world still publishes 2.1), and writes 3.0. - W3C Web Annotation motivations (
commenting,tagging,linking,bookmarking,highlighting,assessing) plus IIIF’s own (painting,supplementing), and the Text Granularity extension (page,block,paragraph,line,word,glyph): the agreed way to publish transcription lines and words on a canvas. Transcription issupplementingwith a granularity; a person’s highlight stayshighlighting, a notecommenting, as the export does today. - Content Search API 2.0 (search inside a manifest, with hits as annotations) and Content State API 1.0 (a link that opens a viewer on a region of a canvas).
- Authorization Flow API 2.0 for access-controlled images, and Change Discovery API 1.0 (an activity stream) so harvesters can follow a published library (not in this slice’s behaviours; a later one, once there is a published library to follow).
- By reference, not by mirror. Arkindex stores a page as a pointer to a IIIF image server and fetches regions on demand; eScriptorium and Transkribus import a manifest by downloading every image. Fichero takes Arkindex’s model, with an explicit, per-source choice to keep a local copy.
- Servers in the field: Cantaloupe and IIPImage (Image API), Omeka S’s IIIF Server module,
and static IIIF (level 0, pre-cut tiles) generated by tools like
iiif-tiler. Fichero’s static-site export (#3177) is level 0; a running shared library is level 1 or 2.
What exists today
- Import, downloading.
importers/iiif_import.pyreads a local folder of Presentation 3.0 manifests, collections and W3C AnnotationPages (linked from a canvas’sannotations) into documents, page text, entities and per-page transcript results (#1646). It does not read 2.x (sequences) manifests; only the single-manifest remote loader below does.loaders/iiif_loader.pyfetches a remote manifest and downloads each canvas’s image at up to 1500 px, refusing unsafe URLs (security/url_security.py). Nothing keeps a page remote. - Serving, dev tier only.
api/routes/ingest/iiif.pyserves local images through Image API 2.1 at level 1 (/api/iiif/{id}/info.json,/{region}/{size}/{rotation}/{quality}.{format}; pixel regions and sizes only, no percentage regions or rotation) and a Presentation 3.0 manifest per document whose image service is that 2.1 endpoint (/api/iiif/manifest/{document_id}), with the person’s annotations as an annotation page. Its tier isdev(feature_tiers_generated.py), so no released build has it. - Export. Parquet, IIIF, W3C annotation and RDF leave through one record stream
(
export_service.py); the georeference extension is read and written (formats/iiif_georef.py). The static-site export of manifests is designed, not built (#3177).
The design
A remote page is a page
A IIIF canvas brought in by reference becomes an ordinary page node whose image is a remote rendition: the image service’s base URI, its profile and size, and the canvas size. Everything that draws or reads a page asks the storage layer for pixels at a size and region, never for a file path (the standing “no local paths” rule). For a remote page the storage layer requests exactly that region and size from the service and keeps it in a bounded cache. Thumbnails, the Preview at any zoom, the Reader and the on-image segments all work this way.
Coordinates are always the canvas’s coordinates, so a segment drawn on a remote page stays put whatever resolution was fetched, and an exported annotation lands in the same place in any viewer.
What has to be fetched, and when
- Viewing fetches only what is on screen, at the size the screen needs.
- Models need real pixels. A run on a remote page fetches the region it needs at the size
the model asks for (
fullor a stated maximum), and records which URI and size it saw in the run’s provenance, so a result can always be traced to the pixels behind it. - Keep a local copy is a per-source choice (a collection, a manifest or a page): it fetches the full image once, stores it as a normal rendition, and from then on the page works offline. It is off by default and says how much it will download before it starts.
- Offline. A remote page that is not cached shows its last thumbnail and says it is remote and unreachable. It never shows a blank.
IIIF folders: a folder that is a collection, in or out (2026-10-01)
Two kinds of special folder in the library, so IIIF is a place rather than a command:
- A IIIF source folder is bound to a collection or manifest URL (the British Library’s Endangered Archives Programme, a university’s digital library, another Fichero’s published folder). Its contents are that collection, by reference: a canvas becomes a page, a manifest a document, a sub-collection a folder. It follows the source: canvases the institution adds appear; ones it withdraws are shown as withdrawn (never silently deleted, since a person’s segments and readings hang on them). Work done inside it (segments, readings, claims) is the library’s own, as everywhere.
- A IIIF published folder is served as a IIIF collection (when the library is shared) and written as one in a static export: its documents as manifests, with their segments and readings as annotations. Moving or copying a document into it publishes it; out of it, unpublishes it. What the rights record withholds stays out.
A folder can be both: a source folder whose work is published back as annotations on the institution’s own canvases.
Like any folder, either can follow its own recipe (source/models-chains-and-projects.md,
the cascade): an EAP collection of Persian manuscripts in one folder runs a different reader from
the Spanish deeds next to it.
A million pages without slowing down (2026-10-01)
A IIIF collection by reference can be very large (a national library’s digitised holdings run to millions of canvases), and Fichero should take a million pages without the Mac slowing down. That is possible because by reference a page costs almost nothing locally:
- What the library stores is small. Per canvas: its identity, its image service, its size, its label and its place in the tree; a few hundred bytes. A million canvases is a few hundred megabytes of rows, not terabytes of images. Importing walks the collection as a background job in the one queue (throttled, resumable, polite to the institution’s server: it respects its rate limits and retries with backoff).
- No thumbnails are made in advance. A thumbnail is fetched from the image service, at thumbnail size, only when it scrolls into view, and kept in the bounded cache. The Library’s lists and grids load lazily, so a folder of a million pages opens as fast as one of a hundred.
- Heavy work runs where the compute is, and fetches from the source. A recipe step on a million pages is sent to a cluster (ACENET), a GPU service or a Docker server. That machine fetches each image directly from the institution’s image service, at the size the model needs, never through the Mac; it returns only the results (passes, readings, claims, and the pixels-seen provenance), which land as usual. The Mac only coordinates.
- Search and the knowledge graph grow with the results, indexed in batches as results land, not page by page.
Honesty about a remote source
- It can change or vanish. Fichero records the service’s identity (URI, and an ETag or a checksum of the fetched full image where it has one). If the image later differs, the page says so, and segments drawn on the old image are flagged for checking instead of silently sitting on new pixels.
- Rights travel with it. The manifest’s
rights,requiredStatementandproviderare stored on the source, shown in the Inspector, and carried into every export. A canvas behind IIIF authorization asks for access through the Authorization Flow; Fichero never stores the institution’s credentials, and the access tokens the flow issues are kept for the session only, so no token is ever at rest in the library file. - The institution’s text is a reading, not the truth. Transcription annotations in a
manifest (
supplementing, with text granularity) arrive as a pass whose maker is the publishing institution, like any other imported result. Fichero’s own corrections are new readings, never edits to theirs.
Out
- Export a manifest. Any folder, document or selection exports as a Presentation 3.0 manifest (or collection). A page that came in by reference points at the original image service, so nothing is re-hosted; a local page points at Fichero’s own service (when shared) or at static tiles (in a static export).
- What goes with it. Segments become annotations with SVG or box selectors on the canvas,
with text granularity (
block,line,word,glyph). The counting reading of each segment is its text, with its language and maker. Other readings, notes, editorial facts and claims go as further annotation pages, each labelled, so a viewer can turn them on and off. - Round trip. A manifest Fichero exported and then imports again lands where it came from: the same canvases, the same segments, no duplicate pages (the “test per path” rule: export, then import into a fresh library and into the one it came from).
Fichero as a IIIF server
A library that is shared (sharing spec, #5049) can also be read as IIIF, through the same
port, the same accounts and the same permissions:
- Image API 3.0 for local images (2.1 kept for old viewers), from the renditions Fichero already keeps: level 1 first (what today’s 2.1 route does), then level 2 (percentage regions, rotation), which Mirador and the Universal Viewer use. Remote pages are not proxied: their manifests point at the original.
- Presentation API 3.0 for the library tree: the library is a collection, folders are collections, documents are manifests.
- Annotations for segments and readings, as above.
- Content Search API 2.0 inside a manifest, answered from Fichero’s own search but returned in the API’s own envelope: hits as annotations with text quote and position selectors, in an annotation page.
- Content State links: “Copy IIIF Link” on a selection gives a link that opens any viewer on that region of that page. The link is the API’s Base64URL-encoded form, and the same form pasted into Fichero opens or imports what it points at.
- Access. Only what the viewer’s account may read is served. What the rights record or a permission withholds is absent from manifests and refused by the image service (the ruling of 2026-09-28 that a deny hides, not marks), and an anonymous viewer sees only what the library publishes openly.
- Static publishing. The static-site export (#3177) writes the same manifests and annotations with level 0 tiles, so a library can be published on any web host with no Fichero running.
The server is one of the library’s sharing surfaces, so it has no toggle of its own (the two sharing toggles stay the only two).
Behaviors
In:
- iiif.import.by-reference — [PARTIAL] (#5324) Built (c8dcfb28a): a manifest or collection URL (Presentation 2 or 3, nested collections) becomes image pages keeping canvas id, Image API service and canvas size, fetching only the manifests; fichero import-iiif --iiif <URL>; pinned by fichero-server/tests/unit/importers/test_iiif_by_reference.py. Not built: pasting a link in the app. a pasted manifest or collection link adds its
canvases as pages at once, without downloading their images.
- iiif.folder.source — [GAP] (#5324) a folder can be bound to a IIIF collection or manifest URL; its
contents are that collection by reference, and it follows the source: additions appear,
withdrawals are shown as withdrawn, never silently deleted.
- iiif.folder.published — [GAP] (#5327) a folder can be published as a IIIF collection (served when
the library is shared, written in a static export); putting a document in publishes it,
taking it out unpublishes it; withheld material stays out.
- iiif.scale.million-pages — [GAP] (#5324) a collection of a million canvases imports by reference as a
throttled, resumable background job that respects the source’s rate limits; the library holds
only per-canvas metadata, and opening a folder of a million pages is as fast as one of a hundred.
- iiif.scale.no-advance-thumbnails — [PARTIAL] (#5324) Built (c8dcfb28a): import warms no preview; a viewed page is fetched once at display size into storage; pinned by fichero-server/tests/unit/importers/test_iiif_by_reference.py. Not built: the bounded cache’s eviction. no thumbnail of a remote page is made in advance;
it is fetched at thumbnail size when it scrolls into view and kept in the bounded cache.
- iiif.scale.remote-runs-fetch-from-source — [PARTIAL] (#5324, #5240) Built (3550e833e, b8bf84e53): a read package lists Image API services, not files, and the runner fetches each at the reader’s size (on a cluster’s login node before the array, since compute nodes have no internet); pinned by fichero-server/tests/unit/remote_read/test_reading_at_scale.py. Pixels seen: see iiif.run.records-pixels-seen. a step run on a cluster, GPU service or
server fetches each image directly from the source’s image service, never through the Mac, and
returns only results with the pixels-seen provenance.
- iiif.import.reads-v2-and-v3 — [PARTIAL] (#1646) Presentation 2.1 and 3.0 manifests and
collections both import. Built: a local folder of manifests (iiif_import.py).
Not built: a remote collection walked by reference, and 2.x (sequences) manifests in the
folder importer.
- iiif.view.fetches-what-is-shown — [GAP] (#5324) a remote page is drawn from region and size
requests for what is on screen, through a bounded cache; no file path is ever involved. The
cache’s bound is a share of free disk with a floor; a model run’s full image is kept while the
source is in use and evicted with the cache; remote images are never proxied to other viewers;
local and remote pages answer one storage call (ruled 2026-10-04, #5323).
- iiif.coords.canvas-space — [PARTIAL] (#5324) Built (b6fd6a8bb): a remote run’s lines are scaled from the fetched size to the canvas on landing; pinned by fichero-server/tests/unit/remote_read/test_read_at_scale_job.py and fichero-server/tests/unit/remote_read/test_reading_at_scale.py. Not built: segments drawn in the app on a remote page. segments on a remote page are stored in canvas
coordinates and stay put whatever resolution was fetched.
- iiif.run.records-pixels-seen — [PARTIAL] (#5324) Built for remote runs: the runner names the exact Image API request and the size it read as the PAGE file’s imageFilename/imageWidth/imageHeight, and that file is what landing keeps as the pass’s evidence (import_original); pinned by fichero-server/tests/unit/remote_read/test_reading_at_scale.py. Not built: a run on this Mac reading a remote page. a model run on a remote page fetches what it
needs and records the image URI and size it saw in its provenance.
- iiif.keep-local-copy — [PARTIAL] (#5324) a source can be kept locally, after saying how much
it will download. Built: today’s loader always downloads, capped at 1500 px.
- iiif.offline-is-said — [GAP] (#5324) an unreachable remote page shows its last thumbnail and
says it is remote and unreachable.
- iiif.change-is-detected — [GAP] (#5325) a remote image that changed is reported, and the
segments drawn on it are flagged for checking.
- iiif.rights-travel — [PARTIAL] (#5325) Built (c8dcfb28a): the manifest’s rights and metadata are stored on each imported page and carried into its exported manifest (rights, requiredStatement); pinned by fichero-server/tests/unit/importers/test_iiif_by_reference.py. Not built: shown in the Inspector, carried into the other exports. the manifest’s rights, required statement and provider
are stored, shown in the Inspector and carried into every export.
- iiif.auth-flow — [GAP] (#5325) a canvas behind IIIF authorization asks for access through
the Authorization Flow; credentials are never stored in the library.
- iiif.import.their-text-is-a-pass — [PARTIAL] (#1646) a manifest’s transcription annotations
arrive as a pass made by the publisher. Built: text arrives as page text. Not built:
lines and words with text granularity becoming segments and readings.
Out:
- iiif.export.manifest — [PARTIAL] (#5326) a folder, document or selection exports as a
Presentation 3.0 manifest or collection. Built: a Presentation 3.0 manifest per document with an
Image API 2.1 service, dev tier.
- iiif.export.points-at-original — [PARTIAL] (#5326) Built: a by-reference page’s manifest paints the archive’s own image at the canvas’s size with the image service exactly as the archive published it (kept at import), and carries the archive’s rights and required statement; pinned by fichero-server/tests/unit/importers/test_iiif_by_reference.py. Not built: a folder or collection export, the static site’s manifests. a page that came in by reference exports
pointing at its original image service; nothing is re-hosted.
- iiif.export.segments-as-annotations — [PARTIAL] (#5326) Built: a canvas names an annotation page of its working pass’s lines (GET /api/iiif/iiif/lines/{id}), each the counting reading’s text and language, textGranularity: line, its maker (Software or Person) and its place in canvas pixels, read through the one page exporter; pinned by fichero-server/tests/unit/api/test_iiif_segments_as_annotations.py. Not built: other readings, notes and claims as their own labelled pages. segments export as annotations with
selectors and text granularity, carrying the counting reading’s text, language and maker; other
readings, notes and claims go as separate, labelled annotation pages.
- iiif.round-trip — [PARTIAL] (#5326) Built for pages: a by-reference page’s exported manifest keeps the archive’s canvas id, so importing it again into the original library lands on the same pages with none doubled; pinned by fichero-server/tests/unit/importers/test_iiif_by_reference.py (mutation-checked). Not built: the lines coming back as the same segments (import reading a remote annotation page). a manifest exported by Fichero and imported again, into a
fresh library or the original one, lands on the same canvases with the same segments and no
duplicate pages.
Server:
- iiif.serve.image-api — [PARTIAL] (#5327) a shared library serves Image API 3.0 for local
images, level 1 then level 2. Built: Image API 2.1 level 1, in the dev tier only.
- iiif.serve.presentation — [PARTIAL] (#5327) the library tree is served as collections
and manifests. Built: one manifest per document, dev tier.
- iiif.serve.search — [GAP] (#5327) Content Search API 2.0 inside a served manifest.
- iiif.serve.content-state — [GAP] (#5327) “Copy IIIF Link” on a selection gives a Content
State link that opens any viewer on that region.
- iiif.serve.only-what-may-be-read — [GAP] (#5327) only what the viewer’s account may read
is served; withheld material is absent from manifests and refused by the image service.
- iiif.serve.no-own-toggle — [GAP] (#5327) the IIIF server is part of sharing and has no
separate switch (sharing and multi-user stay the only two toggles, library-sharing.md).
- iiif.publish.static — [GAP] (#3177) the static-site export writes the same manifests and
annotations with level 0 tiles.
Documentation matrix, preview harness, accessibility identifiers, UX completeness
Filled at approval, from the surfaces this spec settles (see Open questions). Listed here as missing so the gap is visible: none of the four is written yet. [MISSING]
Test matrix
To be filled at approval. The round trip is tested per path (export, then import into a fresh library and into the one it came from), against real manifests from at least two institutions (one 2.1, one 3.0) and one with authorization.
Rulings
- 2026-10-04 (#5323): the spec is approved by the maintainer with its four recommendations.
(1) The cache is a share of free disk with a floor, not a fixed cap. (2) A model run keeps the
full image of a remote page while the source is in use, evicted with the cache. (3) Remote
images are not proxied for viewers of a shared library; the manifest points at the original.
(4) One storage call answers for local and remote pages (pixels for a page, a region and a
size), and its shape is designed before
iiif.import.by-referenceis built. Status stays DRAFT: approved by the maintainer 2026-10-04; APPROVED status waits on aManual:section (it reads TBD), a filled Test matrix and a test that cites this spec.
Open questions
All four answered 2026-10-04 by taking each recommendation; see Rulings. Kept for the reasoning.
- Answered (maintainer 2026-10-04: IIIF approved as recommended): As the spec recommends. Cache size. A fixed cap (for example 2 GB) with least-recently-used eviction, or a share of free disk? Recommendation: a share of free disk with a floor, because the Air showed how little room a small Mac has.
- Answered (maintainer 2026-10-04: IIIF approved as recommended): As the spec recommends. Models on remote pages. Fetch at full resolution every run, or keep the full image after the first run? Recommendation: keep it for as long as the source is in use, and evict it with the cache.
- Answered (maintainer 2026-10-04: IIIF approved as recommended): As the spec recommends. Proxying remote images for viewers of a shared library who cannot reach the institution. Recommendation: no; the manifest points at the original. Revisit if a real case appears.
- Answered (maintainer 2026-10-04: IIIF approved as recommended): As the spec recommends. The storage call for a remote page. Today the storage layer resolves local files
(
db/storage.py). A remote page needs one call that both kinds of page answer (pixels for a page, a region and a size), so the Preview, thumbnails and model runs have one caller. Its shape is designed beforeiiif.import.by-referenceis built, not during it.
Triaged from the backlog (2026-10-04)
iiif.import.json-type-strips-compact-prefixes— [GAP] (#5405) iiif_import._json_type stripssc:,oa:anddctypes:prefixes so a v2 canvas reads as Canvas, and the test that pinssc:Canvasis updated.