API
May 22, 2026 ยท View on GitHub
This document describes aiiinotate's API.
Introductory notes
Terminology
In the docs below,
Parametersdescribes route parameters (dynamic segments of a route's URL).Querydescribes the query string in a key-value format
IIIF Version
aiiinotate is meant to be able to handle both IIIF presentation APIs: the most common 2.x and the more recent 3.x. Both APIs define a data structure for manifests, annotations, lists of annotations and collections of manifests.
HOWEVER, in aiiinotate, v2 and v3 data are isolated: they form two separate collections, and no conversion is done between IIIF 2.x and 3.x data. This means that:
- when communicating with aiiinotate, you must specify a IIIF presentation version in the query URL. In the docs, this is described by the
iiif_versionkeyword. - when inserting/updating data, the data structure you provide must match the URL's
iiif_version: you can't insert an annotation in v3 if youriiif_versionis2. - TLDR:
- your data must match the
iiif_versionargument - if you insert an Annotation following the API v3.x, you can't search for it using
iiif_version=2.
- your data must match the
This is because
- the IIIF standard is quite complex and there are breaking changes between v2 and v3
- handling conversions between v2 and v3 is error prone, would increase calculations and slow the app down
Generic routes
IIIF search API
GET /search-api/{iiif_version}/manifests/{manifest_short_id}/search
Implementation of the IIIF Search API, to search one or several annotations within a manifest.
Request
- Parameters:
iiif_version(2 | 3): the IIIF aearch API version. 2 is for IIIF Presentation API 3.x, 1 is for IIIF Presentation API 2.xmanifest_short_id(string): the ID of the manifest. See the IIIF URIs section.
- Query:
q(string): query string.- if
iiif_version=1,qis searched in the fields:@id,resource.@idorresource.charsfields
- if
motivation(painting | non-painting | commenting | describing | tagging | linking): values for themotivationfield of an annotationcanvasMin(number): a positive integercanvasMax(number): a positive integercanvasMaxmust be greater thancanvasMin- if
canvasMaxis undefined, then we will only return the annotations that target a canvas at thecanvasMinposition in its manifest.
page(number): results are paginated. This specifies the page numberpageSize(number): number of annotations to display per page. Defaults toprocess.env.AIIINOTATE_PAGE_SIZE.onlyIds(boolean): return just the value of@idfields of matched annotations as astring[]instead of returning all the annotations. IfonlyIds=true, there is no pagination,pageandpageSizewon't have any effect.
Response
AnnotationList | AnnotationPage | string[]
- if
onlyIds=true, return astring[](array of the IDs of all matched annotations) - otherwise,
- if
iiif_versionis1, return anAnnotationList - else, return an
AnnotationPage.
- if
Notes
canvasMin,canvasMaxandonlyIdsare non-standard query parameters that are NOT part of the IIIF Search API specification.- If
qandmotivationare unused, it will return all annotations for the manifest - Only exact matches are allowed for
qandmotivation - About
canvasMinandcanvasMax:- they are used to search for annotations that target a range of canvases: for example, fetch all anotations between pages 3 and 30 of a manuscript.
- they are 0-indexed: the 1st canvas of a manifest is indexed
0, the 2nd is indexed1... - See section Create/update an annotation for more information and possible issues with canvas indexes.
Delete an annotation or a manifest
DELETE /{collection_name}/{iiif_version}/delete
Request
- Parameters:
collection_name(annotations | manifests): delete an annotation or a manifestiiif_version(2 | 3): IIIF presentation version
- Query:
- if
collection_name = manifests:uri: the full URI of the manifest to deletemanifestShortId: the manifest's identifier
- if
collection_name = annotation:uri: the full URI of the annotation to deletemanifestShortId: a manifest's identifier, to delete all annotations for a manifestcanvasUri: the full URI to an annotation's target canvas, to delete all annotatons for the canvastag: delete all annotations with a certain tag in a manifest- MUST be used in conjunction with
manifestShortIdto indicate on which manifest to delete annotations by tag:delete?tag=<your-tag>&manifestShortId=<your-manifest-id>
- MUST be used in conjunction with
- if
Response
{ deletedCount: <integer> }
Manifests routes
Get an index of all manifests
GET /manifests/{iiif_version}
Returns a Collection of all manifests in your aiiinotate instance.
Request
- Parameters:
iiif_version(2 | 3): the IIIF Presentation API version
Response
A IIIF Collection, following the IIIF Presentation API 2 or 3, depending of the value of iiif_version.
Insert a manifest
POST /manifests/{iiif_version}/create
Request
- Parameters:
iiif_version(2 | 3): the IIIF Presentation API version of your manifest
- Body (
JSON): the manifest to index in the database
Response
{
insertedIds: string[],
preExistingIds: string[],
rejectedIds: []
}
insertedIds: the list of IDs of inserted manifestspreExisingIds: the IDs of manifests that were aldready in the databaserejectedIds: the IDs of manifests on which an error occurred
Annotation routes
Get all annotations for a canvas
GET /annotations/{iiif_version}/search
Request
- Parameters:
iiif_version(2 | 3): the IIIF Presentation API of your manifests
- Query:
canvasUri(string): the URI of the target canvaspage(number): results are paginated. Specifies the page number.pageSize(number): number of items per page. Defaults toprocess.env.AIIINOTATE_PAGE_SIZE.
Response
Results are paginated.
AnnotationList | AnnotationPage
Count annotations
GET /annotations/{iiif_version}/count
Request
- Parameters:
iiif_version(2 | 3): the IIIF Presentation API of your manifests
- Query:
uri(string): the annotation's@idcanvasUri(string): the annotation's target canvas (on.full)manifestShortId(string): the short ID of the annotation's target manifest (on.manifestShortId)
Response
{ count: integer }
Get a single annotation
GET /data/{iiif_version}/{manifest_short_id}/annotation/{annotation_short_id}
This route allows to query an annotation by its ID by defering its @id | id field. This URL follows the IIIF specification
Request
- Parameters:
iiif_version(2 | 3): the IIIF version of the annotationmanifest_short_id(string): the identifier of the manifest the annotation is related toannotation_short_id: the unique part of the annotation URL
Response
Object: the annotation. Its format follows the IIIF Presentation specification 2 or 3, based on the value of iiif_version.
Create/update an annotation
POST /annotations/{iiif_version}/{action}
Create or update a single annotation
Request
- Parameters:
iiif_version(2 | 3): the IIIF version of the annotationaction(create | update): the action to perform: create or update an annotation
- Query:
throwOnCanvasIndexError(boolean): throw an error if there's a problem fetching the annotation's target canvas index. See Appendix 1throwOnXywhError(boolean): throw an error if target bounding box calculation fails. See Appendix 1
- Body (
Object): a IIIF annotation that follows the IIIF Presentation API 2 or 3 (depending on the value ofiiif_version)
Response
{
insertedIds: string[],
preExistingIds: string[],
rejectedIds: string[],
fetchErrorIds: string[]
}
Insert several annotations
POST /annotations/{iiif_version}/createMany
Batch insert multiple annotations.
Request
- Parameters:
iiif_version(2 | 3): the IIIF version of the annotation
- Query:
throwOnCanvasIndexError(boolean): throw an error if there's a problem fetching the annotation's target canvas index. See Appendix 1throwOnXywhError(boolean): throw an error if target bounding box calculation fails. See Appendix 1
- Body: either:
- a full
AnnotationList | AnnotationPageembedded in the body (type must matchiiif_version: AnnotationPage for IIIF 3, AnnotationList for IIIF 2). AnnotationList[] | AnnotationPage[](type must matchiiif_version): an array of annotation lists or pages{ uri: string }: an object containing a reference to anAnnotationListorAnnotationPage{ uri: string }[]: an array of objects containing a reference to anAnnotationListorAnnotationPage.
- a full
Response
{
insertedIds: string[],
preExistingIds: string[],
rejectedIds: []
}
Notes
- Calling this route using parallel processes (i.e.,
Promise.all) can cause data races which will cause inserts to fail - Be wary of maximum body size, especially when sending AnnotationLists in your body. If possible, using
{ uri: string }is better. - All annotations within a single AnnotationList/Page may have different target canvases or manifests.
- See Create/update an annotation.
Appendix 1: annotation canvas index and bounding box calculation
Two non-standard side-effects happen when inserting an annotation:
- the annotation's target manifest is also fetched and inserted in the database. The annotation's target canvas index is fetched and added to the annotation.
- calculating the XYWH bounding box of an annotation.
Annotations in aiiinotate contain nonstandard fields. In IIIF presentation 2.x,
manifestUri,manifestShortId,canvasIdx:annotation.on.manifestUri: the URI of the manifest on which is an annotationannotation.on.manifestShortId: the unique identifier of the manifest on which is an annotationannotation.on.canvasIdx: the position of an annotation's target canvas within the target manifest, as an integer.canvasIdxis 0-indexed: the 1st canvas in a manifest is indexed from0- this relies on reconstructing an annotation's target manifest URL and fetching it. If this process fails, the fields above will be
undefined. - fetching an annotation's target manfest is error prone: it depends on the manifest being available through HTTP, which is not in our control.
- you can control error throwing (throw an error if a canvas index can't be found) using:
- at route-level, the
throwOnCanvasIndexErrorquery parameter - at app-level, the
AIIINOTATE_STRICT_MODEenv variable. - if
AIIINOTATE_STRICT_MODEorthrowOnCanvasIndexError, an error will be thrown if an error appears anywhere in the process of fetching the target manifest and populating thecanvasIdxfield.
- at route-level, the
annotation.on.xywh: the annotation's bounding box on its target canvas.- when inserting an annotation, aiiinotate will attempt to extract its XYWH bounding box and store it as an
[number,number,number,number]. - this only works for
oa:SvgSelectors,oa:FragmentSeletors,oa:Choicecontaining either anSvgSelectoror aFragmentSelector, or a string-target (an URI with a#xywh=fragment selector). - you can control error-throwing (throw an error if a bounding-box can't be calculated) using:
- at route-level, the
throwOnXywhErrorquery parameter - at app-level, the
AIIINOTATE_STRICT_MODEenv variable.
- at route-level, the
- when inserting an annotation, aiiinotate will attempt to extract its XYWH bounding box and store it as an
About throwOnCanvasIndexError, throwOnXywhError and AIIINOTATE_STRICT_MODE:
- as indicated above, fetching an annotation's target canvas index and calculating an annotation's target bounding boxc is error prone.
- in turn, you can decide wether or not to throw errors using:
- at route-level, the
throwOnCanvasIndexError(for canvas index) andthrowOnXywhError(for bounding box) query parameters - at app-level, the
AIIINOTATE_STRICT_MODEenv variable. Setting this totruewill set the value ofthrowOnCanvasIndexErrorandthrowOnXywhErrortotrueby default. - the value of
AIIINOTATE_STRICT_MODEcan be overridden by setting a value to query parametersthrowOnCanvasIndexErrorandthrowOnXywhError.
- at route-level, the
- if those are set to
true, an error will be thrown if a canvas index can't be found. Otherwise, the values will be set toundefined. - in controlled environments where you know your manifests will be available and where you rely heavily on the
canvasIdxfield (like AIKON), throwing an error will ensure that thecanvasIdxfield is always defined.
Appendix 2: App logic: URL prefixes
URL anatomy is a mix of SAS endpoints and IIIF specifications. In turn, we define the following prefixes:
data: for all IIIF URIs: URIs of annotations and annotation listsannotations: operations on annotationsmanifests: operations on manifestssearch-api: endpoint to access the IIIF search API
In turn, URL anatomy is:
{host}/{prefix}/{iiif_version}/{slug}
Where:
host: the host of your appprefix:data | annotations | manifestsiiif_version:- if
prefixissearch-api,1 | 2: IIIF Search API version used - otherwise,
2 | 3, the IIIF Presentation API version your data is in
- if
slug: the rest of the qurty URI
There is an extra URL prefix: schemas. It is only used internally (not accessible to clients or accessible through HTTP) to define the IDs of all JsonSchemas, so we won't talk about it here.
Appendix 3: IIIF URIs
IIIF URIs in the Presentation 2.1 API are:
Collection {scheme}://{host}/{prefix}/collection/{name}
Manifest {scheme}://{host}/{prefix}/{manifest_short_id}/manifest
Sequence {scheme}://{host}/{prefix}/{manifest_short_id}/sequence/{name}
Canvas {scheme}://{host}/{prefix}/{manifest_short_id}/canvas/{name}
Annotation (incl images) {scheme}://{host}/{prefix}/{manifest_short_id}/annotation/{name}
AnnotationList {scheme}://{host}/{prefix}/{manifest_short_id}/list/{name}
Range {scheme}://{host}/{prefix}/{manifest_short_id}/range/{name}
Layer {scheme}://{host}/{prefix}/{manifest_short_id}/layer/{name}
Content {scheme}://{host}/{prefix}/{manifest_short_id}/res/{name}.{format}