annorepo

AnnoRepo: REST API

REST API usage


General notes

For the basic annotation CRUD handling, AnnoRepo has a /w3c/ endpoint that implements part of the W3C Web Annotation Protocol
As the protocol does not specify how to create, update or delete annotation containers, AnnoRep implements endpoints for that in a way similar to that used by elucidate


Annotation Containers

Creating an annotation container (πŸ”’)

If the annorepo instance has authorization enabled, the user creating a container will automatically get ADMIN rights to that container. (see Container Users)

Request

POST http://localhost:8080/w3c/ HTTP/1.1

Accept: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"
Content-Type: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"

{
  "@context": [
    "http://www.w3.org/ns/anno.jsonld",
    "http://www.w3.org/ns/ldp.jsonld"
  ],
  "type": [
    "BasicContainer",
    "AnnotationCollection"
  ],
  "label": "A Container for Web Annotations",
  "readOnlyForAnonymousUsers": true
}

Response

HTTP/1.1 201 Created

Location: http://localhost:8080/w3c/137d2619-7bcd-41c9-abc3-7ec78733993c/
Content-Location: http://localhost:8080/w3c/137d2619-7bcd-41c9-abc3-7ec78733993c/
Vary: Accept
Link: <http://www.w3.org/ns/ldp#BasicContainer>; rel="type"
Link: <http://www.w3.org/TR/annotation-protocol>; rel="http://www.w3.org/ns/ldp#constrainedBy"
Allow: HEAD,DELETE,POST,GET,OPTIONS
ETag: W/"-1172057598"
Content-Type: application/ld+json;profile="http://www.w3.org/ns/anno.jsonld"
Content-Length: 548

{
  "@context": [
    "http://www.w3.org/ns/anno.jsonld",
    "http://www.w3.org/ns/ldp.jsonld"
  ],
  "id": "http://localhost:8080/w3c/137d2619-7bcd-41c9-abc3-7ec78733993c/",
  "type": [
    "BasicContainer",
    "AnnotationCollection"
  ],
  "total": 0,
  "label": "A Container for Web Annotations",
  "first": {
    "id": "http://localhost:8080/w3c/137d2619-7bcd-41c9-abc3-7ec78733993c/?page=0",
    "type": "AnnotationPage",
    "partOf": "http://localhost:8080/w3c/137d2619-7bcd-41c9-abc3-7ec78733993c/",
    "startIndex": 0,
    "items": []
  },
  "last": "http://localhost:8080/w3c/137d2619-7bcd-41c9-abc3-7ec78733993c/?page=0"
}

Use a slug header to choose your own container name. If a container with the same name already exists, AnnoRepo will generate a new one. Setting readOnlyForAnonymousUsers to true will give anonymous users (those without an api-key) read-only access to the public endpoints. Default is false

Request

POST http://localhost:8080/w3c/ HTTP/1.1

Accept: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"
Content-Type: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"
Slug: "my-container"

{
  "@context": [
    "http://www.w3.org/ns/anno.jsonld",
    "http://www.w3.org/ns/ldp.jsonld"
  ],
  "type": [
    "BasicContainer",
    "AnnotationCollection"
  ],
  "label": "A Container for Web Annotations"
}

Response

HTTP/1.1 201 Created

Location: http://localhost:8080/w3c/my-container/
Content-Location: http://localhost:8080/w3c/my-container/
Vary: Accept
Link: <http://www.w3.org/ns/ldp#BasicContainer>; rel="type"
Link: <http://www.w3.org/TR/annotation-protocol>; rel="http://www.w3.org/ns/ldp#constrainedBy"
Allow: HEAD,DELETE,POST,GET,OPTIONS
ETag: W/"2133202336"
Content-Type: application/ld+json;profile="http://www.w3.org/ns/anno.jsonld"
Content-Length: 452

{
  "@context": [
    "http://www.w3.org/ns/anno.jsonld",
    "http://www.w3.org/ns/ldp.jsonld"
  ],
  "id": "http://localhost:8080/w3c/my-container/",
  "type": [
    "BasicContainer",
    "AnnotationCollection"
  ],
  "total": 0,
  "label": "A Container for Web Annotations",
  "first": {
    "id": "http://localhost:8080/w3c/my-container/?page=0",
    "type": "AnnotationPage",
    "partOf": "http://localhost:8080/w3c/my-container/",
    "startIndex": 0,
    "items": []
  },
  "last": "http://localhost:8080/w3c/my-container/?page=0"
}

Reading an annotation container (πŸ”’)

Request

GET http://localhost:8080/w3c/{containerName}/ HTTP/1.1

Accept: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"

Response

HTTP/1.1 200 OK

Content-Location: http://localhost:8080/w3c/my-container/
Vary: Accept
Link: <http://www.w3.org/ns/ldp#BasicContainer>; rel="type"
Link: <http://www.w3.org/TR/annotation-protocol/>; rel="http://www.w3.org/ns/ldp#constrainedBy"
Allow: HEAD,DELETE,POST,GET,OPTIONS
ETag: W/"2133202336"
Content-Type: application/ld+json;profile="http://www.w3.org/ns/anno.jsonld"
Vary: Accept-Encoding
Content-Length: 452

{
  "@context": [
    "http://www.w3.org/ns/anno.jsonld",
    "http://www.w3.org/ns/ldp.jsonld"
  ],
  "id": "http://localhost:8080/w3c/my-container/",
  "type": [
    "BasicContainer",
    "AnnotationCollection"
  ],
  "total": 0,
  "label": "A Container for Web Annotations",
  "first": {
    "id": "http://localhost:8080/w3c/my-container/?page=0",
    "type": "AnnotationPage",
    "partOf": "http://localhost:8080/w3c/my-container/",
    "startIndex": 0,
    "items": []
  },
  "last": "http://localhost:8080/w3c/my-container/?page=0"
}

Deleting an annotation container (πŸ”’)

If the container contains annotations, the delete will return a 400 Bad Request with a warning to delete the annotations first.

You can override this by adding ?force=true to the request.

The If-Match header must contain the ETag of the container.

Request

DELETE http://localhost:8080/w3c/{containerName}/ HTTP/1.1

If-Match: "{etag}"

Response

HTTP/1.1 204 No Content

Changing the read-only for anonymous users setting for an annotation container (πŸ”’)

Send true to the endpoint to make the container read-only for anonymous users. Send false to the endpoint to lock the container for anonymous users.

Request

PUT http://localhost:8080/services/{containerName}/settings/isReadOnlyForAnonymous HTTP/1.1

Content-Type: application/json

true

Response

HTTP/1.1 200 OK

Annotations

Adding an annotation to a given annotation container (πŸ”’)

As with the container creation, you can let annorepo pick the annotation name:

Request

POST http://localhost:8080/w3c/{containerName}/ HTTP/1.1

Accept: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"
Content-Type: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"

{
  "@context": "http://www.w3.org/ns/anno.jsonld",
  "id": "http://example.org/annotations/myannotation"
  "type": "Annotation",
  "body": {
    "type": "TextualBody",
    "value": "I like this page!"
  },
  "target": "http://www.example.com/index.html"
}

Response

HTTP/1.1 201 CREATED

Location: http://localhost:8080/w3c/my-container/0bb16696-245c-4614-955f-78dec7065f60
Vary: Accept
Allow: HEAD,DELETE,POST,GET,OPTIONS,PUT
Link: <http://www.w3.org/ns/ldp#Resource>; rel="type"
Link: <http://www.w3.org/ns/ldp#Annotation>; rel="type"
ETag: W/"-1699442532"
Content-Type: application/ld+json;profile="http://www.w3.org/ns/anno.jsonld"
Content-Length: 305

{
   "@context": "http://www.w3.org/ns/anno.jsonld",
   "id": "http://localhost:8080/w3c/my-container/0bb16696-245c-4614-955f-78dec7065f60",
   "type": "Annotation",
   "body": {
      "type": "TextualBody",
      "value": "I like this page!"
   },
   "target": "http://www.example.com/index.html",
   "via": "http://example.org/annotations/myannotation"
}

or, you can add a Slug header to set the name. When the preferred name is already in use in the container, AnnoRepo will pick the name:

POST http://localhost:8080/w3c/{containerName}/ HTTP/1.1

Accept: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"
Content-Type: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"
Slug: hello-world

{
  "@context": "http://www.w3.org/ns/anno.jsonld",
  "id": "http://example.org/annotations/my-annotation",
  "type": "Annotation",
  "body": {
    "type": "TextualBody",
    "value": "Hello!"
  },
  "target": "http://www.example.com/world.html"
}

Response

HTTP/1.1 201 CREATED

Location: http://localhost:8080/w3c/my-container/hello-world
Vary: Accept
Allow: HEAD,DELETE,POST,GET,OPTIONS,PUT
Link: <http://www.w3.org/ns/ldp#Resource>; rel="type"
Link: <http://www.w3.org/ns/ldp#Annotation>; rel="type"
ETag: W/"1303452440"
Content-Type: application/ld+json;profile="http://www.w3.org/ns/anno.jsonld"
Content-Length: 270

{
   "@context": "http://www.w3.org/ns/anno.jsonld",
   "id": "http://localhost:8080/w3c/my-container/hello-world",
   "type": "Annotation",
   "body": {
      "type": "TextualBody",
      "value": "Hello!"
   },
   "target": "http://www.example.com/world.html",
   "via": "http://example.org/annotations/my-annotation"
}

Reading an annotation (πŸ”’)

Request

GET http://localhost:8080/w3c/{containerName}/{annotationName} HTTP/1.1

Accept: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"

Response

HTTP/1.1 200 OK

Allow: HEAD,DELETE,POST,GET,OPTIONS,PUT
Link: <http://www.w3.org/ns/ldp#Resource>; rel="type"
Link: <http://www.w3.org/ns/ldp#Annotation>; rel="type"
ETag: W/"-1518598207"
Content-Type: application/ld+json;profile="http://www.w3.org/ns/anno.jsonld"
Vary: Accept-Encoding
Content-Length: 272

{
   "id": "http://localhost:8080/w3c/my-container/my-annotation",
   "type": "Annotation",
   "body": {
      "type": "TextualBody",
      "value": "Hello!"
   },
   "@context": "http://www.w3.org/ns/anno.jsonld",
   "target": "http://www.example.com/world.html",
   "via": "http://example.org/annotations/my-annotation"
}

Updating an annotation (πŸ”’)

When updating an annotation, you need to send its ETag in the If-Match header.

Request

PUT http://localhost:8080/w3c/{containerName}/{annotationName} HTTP/1.1

Accept: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"
Content-Type: application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"
If-Match: "{etag}"

{
  "@context": "http://www.w3.org/ns/anno.jsonld",
  "id": "http://example.org/annotations/my-annotation",
  "type": "Annotation",
  "body": {
    "type": "TextualBody",
    "value": "Goodbye!"
  },
  "target": "http://www.example.com/world.html"
}

Response

HTTP/1.1 200 OK

Vary: Accept
Allow: HEAD,DELETE,POST,GET,OPTIONS,PUT
Link: <http://www.w3.org/ns/ldp#Resource>; rel="type"
Link: <http://www.w3.org/ns/ldp#Annotation>; rel="type"
ETag: W/"1303452440"
Content-Type: application/ld+json;profile="http://www.w3.org/ns/anno.jsonld"

{
  "@context": "http://www.w3.org/ns/anno.jsonld",
  "id": "http://localhost:8080/w3c/my-container/hello-world",
  "type": "Annotation",
  "body": {
    "type": "TextualBody",
    "value": "Goodbye!"
  },
  "target": "http://www.example.com/world.html",
  "via": "http://example.org/annotations/my-annotation"
}

Deleting an annotation (πŸ”’)

When deleting an annotation, you need to send its ETag in the If-Match header.

Request

DELETE http://localhost:8080/w3c/{containerName}/{annotationName} HTTP/1.1

If-Match: "{etag}"

Response

HTTP/1.1 204 No Content

Uploading multiple annotations to a given annotation container (πŸ”’) (experimental)

Request

POST http://localhost:8080/batch/{containerName}/annotations HTTP/1.1
Content-Type: application/json

[
    {
        "@context": "http://www.w3.org/ns/anno.jsonld",
        "type": "Annotation",
        "motivation": "classifying",
        "body": {
            "type": "TextualBody",
            "purpose": "classifying",
            "value": "attendant",
            "id": "urn:example:republic:person-1"
        },
        "target": {
            "source": "urn:example:republic:text-1",
            "type": "Text",
            "selector": {
                "type": "urn:example:republic:TextAnchorSelector",
                "start": 100,
                "end": 130
            }
        }
    },
    {
        "@context": "http://www.w3.org/ns/anno.jsonld",
        "type": "Annotation",
        "motivation": "classifying",
        "body": {
            "type": "TextualBody",
            "purpose": "classifying",
            "value": "recipient",
            "id": "urn:example:republic:person-2"
        },
        "target": {
            "source": "urn:example:republic:text-1",
            "type": "Text",
            "selector": {
                "type": "urn:example:republic:TextAnchorSelector",
                "start": 190,
                "end": 200
            }
        }
    },
    {
        "@context": "http://www.w3.org/ns/anno.jsonld",
        "type": "Annotation",
        "motivation": "classifying",
        "body": {
            "type": "TextualBody",
            "purpose": "classifying",
            "value": "attendant",
            "id": "urn:example:republic:person-3"
        },
        "target": {
            "source": "urn:example:republic:text-1",
            "type": "Text",
            "selector": {
                "type": "urn:example:republic:TextAnchorSelector",
                "start": 200,
                "end": 220
            }
        }
    },
    {
        "@context": "http://www.w3.org/ns/anno.jsonld",
        "type": "Annotation",
        "motivation": "classifying",
        "body": {
            "type": "TextualBody",
            "purpose": "classifying",
            "value": "attendant",
            "id": "urn:example:republic:person-4"
        },
        "target": {
            "source": "urn:example:republic:text-1",
            "type": "Text",
            "selector": {
                "type": "urn:example:republic:TextAnchorSelector",
                "start": 300,
                "end": 315
            }
        }
    },
    {
        "@context": "http://www.w3.org/ns/anno.jsonld",
        "type": "Annotation",
        "motivation": "classifying",
        "body": {
            "type": "TextualBody",
            "purpose": "classifying",
            "value": "attendant",
            "id": "urn:example:republic:person-5"
        },
        "target": {
            "source": "urn:example:republic:text-2",
            "type": "Text",
            "selector": {
                "type": "urn:example:republic:TextAnchorSelector",
                "start": 90,
                "end": 110
            }
        }
    }
]

Response

The batch request will return a list of the generated annotation names:

HTTP/1.1 200 OK

Content-Type: application/json
Content-Length: 23
    
[
   "9de49b24-97d8-4e7a-8167-c043723ef672",
   "82866a01-dd23-4f4b-896b-3e30cb7bff5c",
   "8f2aa0c5-379b-4705-b1be-9c72627fb153",
   "623fa415-6206-4b0f-9268-ae13be1b4fba",
   "a1f6d1a5-af08-4779-b35a-d36e695a9d4e"
]

Querying a container

Create a query (πŸ”’) (experimental)

Request

POST http://localhost:8080/services/{containerName}/search HTTP/1.1

{
  "purpose": "tagging",
  "body.type": {
    ":isNotIn": [
      "Line",
      "Page"
      ]
  },
  ":overlapsWithTextAnchorRange": {
    "start": 12,
    "end": 134,
    "source": "https://textrepo.republic-caf.diginfra.org/api/rest/versions/42df1275-81cd-489c-b28c-345780c3889b/contents"
  }
}

This request body (the query) consists of three parts:

This query will return only those annotations in my-container that match with all three sub-queries.

In general, a query must consist of at least 1 field query, extended field query or query function call, and the returned annotations must match with all the sub-queries.

field names

example: "purpose": "tag" will not match with "purpose": "tagging" or "Purpose": "tag"

example: given this body:

{
  "body": [
    {
      "type": "Object",
      "metadata": {
        "height": 12,
        "width": 10,
        "color": "yellow"
      }
    },
    {
      "type": "Text",
      "value": "Hello, World!"
    }
  ]
}

This json contains the queryable fields:

Use the fields endpoint to see the fields available in the container.

(simple) field query

This matches the given field with the given value.
A value can be a:

extended field query

In an extended field query you can use one of the following query operators:

query function call

A query function in AnnoRepo is a pre-programmed combination of simple and extended field queries, and can be called with a parameter map. The query function

Currently, the following query functions are available:

or query

Using the :or query construction, you can match annotations with at least one of a given list of field query s

example:

{
  ":or": [
    { "motivation": "classifying" },
    { "body.type": "Entity" }
  ]
}

Response

HTTP/1.1 200 OK

location: http://localhost:8080/services/my-container/search/f3da8d25-701c-4e25-b1be-39cd6243dac7

The Location header contains the link to the first search result page.


Get a search result page (πŸ”’) (experimental)

Request

GET http://localhost:8080/services/{containerName}/search/{searchId} HTTP/1.1

Response

HTTP/1.1 200 OK

Content-Type: application/json
Vary: Accept-Encoding

{
  "id": "http://localhost:8080/services/my-container/search/d6883433-de41-43fb-93d2-85c1cd9570ee?page=0",
  "type": "AnnotationPage",
  "partOf": "http://localhost:8080/services/my-container/search/d6883433-de41-43fb-93d2-85c1cd9570ee",
  "startIndex": 0,
  "items": [
    ....
  ]
}

The Location header contains the link to the first search result page.


Querying globally

To query all the containers the user has read-access to, use the /global/search endpoint in a similar way as querying a specific container.

Create a global query (πŸ”’) (experimental)

Request

POST http://localhost:8080/global/search HTTP/1.1

{
  "purpose": "identifying"
}

Response

HTTP/1.1 201 Created
Location: http://localhost:8080/global/search/73f62348-7dcc-4d13-9748-fcb8f5a8a367
Link: <http://localhost:8080/global/search/73f62348-7dcc-4d13-9748-fcb8f5a8a367/status>; rel="status"
Content-Type: application/json

{
  "query" : {
    "purpose": "identifying"
  },
  "startedAt" : "2023-05-02T12:49:32",
  "finishedAt" : null,
  "expiresAfter" : null,
  "state" : "RUNNING",
  "containersSearched" : 0,
  "totalContainersToSearch" : 11,
  "hitsFoundSoFar" : 0,
  "processingTimeInMillis" : 2
}

Creating the global query returns its location in the Location header. The body returned is a representation of the status of the search, with the fields:


Get a global search status (πŸ”’) (experimental)

Request

GET http://localhost:8080/global/search/{searchId}/status HTTP/1.1

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
    "query": {
        "type": "Annotation"
    },
    "startedAt": "2023-05-09T11:00:20",
    "finishedAt": null,
    "expiresAfter": null,
    "state": "RUNNING",
    "containersSearched": 3,
    "totalContainersToSearch": 11,
    "hitsFoundSoFar": 1114,
    "processingTimeInMillis": 41
}

Get a global search result page (πŸ”’) (experimental)

Request

GET http://localhost:8080/global/search/{searchId} HTTP/1.1

Response

If the search is still ongoing,

HTTP/1.1 202 Accepted

is returned, with the search status in the body.

If the search has finished:

HTTP/1.1 200 OK

Content-Type: application/json
Vary: Accept-Encoding

{
  "id": "http://localhost:8080/global/search/73f62348-7dcc-4d13-9748-fcb8f5a8a367?page=0",
  "type": "AnnotationPage",
  "partOf": "http://localhost:8080/global/search/73f62348-7dcc-4d13-9748-fcb8f5a8a367",
  "startIndex": 0,
  "items": [
    ....
  ]
}

The Location header contains the link to the first search result page.


Custom Queries

The way the queries you can do via the Querying a container endpoint work means that you can’t have a permanent URL that points to the query results. The custom query endpoints will give you this permanent URL.

Create a custom query (πŸ”’)

Request

The json sent to the endpoint is the CustomQuerySpecs, with the fields:

POST http://localhost:8080/global/custom-query HTTP/1.1

{
    "name": "with-motivation",
    "query": {
        "motivation": "<motivation>"
    },
    "label": "motivation=<motivation>",
    "description": "This custom query returns those annotations where the motivation is the given value",
    "public": true
}

In query and label the parameter motivation is used.

Response

The Location header contains the url to the custom query:

HTTP/1.1 201

Location: http://localhost:8080/global/custom-query/with-motivation

Read a custom query (πŸ”’)

This endpoint can be used by anonymous users if the custom query was made public.

Request

GET http://localhost:8080/global/custom-query/{customQueryName} HTTP/1.1

Response

The information from the CustomQuerySpecs is returned, with additionally the fields:

HTTP/1.1 200

Content-Type: application/json

{
    "name": "with-motivation",
    "description": "This custom query returns those annotations where the motivation is the given value",
    "label": "motivation=<motivation>",
    "created": "2024-08-15T15:02:59+0000",
    "createdBy": "userName",
    "public": true,
    "queryTemplate": "{\"motivation\":\"<motivation>\"}",
    "parameters": [
        "motivation"
    ]
}

Delete a custom query (πŸ”’)

Request

DELETE http://localhost:8080/global/custom-query/{customQueryName} HTTP/1.1

Response

HTTP/1.1 204

Show the custom query with given parameters (πŸ”’)

This endpoint can be used by anonymous users if the custom query was made public.

Request

The customQueryCall is a combination of the custom query name and the parameter values, Base64 encoded.

For example, calling the with-motivation custom query with the value identifying for the motivation parameter requires the following customQueryCall:

with-motivation:motivation=aWRlbnRpZnlpbmc=

where aWRlbnRpZnlpbmc= is the Base64-encoded form of identifying

parameter assignments in the customQueryCall are separated with , so adding a second parameter par with value par would give this customQueryCall:

with-motivation:motivation=aWRlbnRpZnlpbmc=,par=cGFy

GET http://localhost:8080/global/custom-query/{customQueryCall}/expand HTTP/1.1

Response

HTTP/1.1 200

Content-Type: application/json

{
    "motivation": "identifying"
}

List all custom queries

Request

GET http://localhost:8080/global/custom-query HTTP/1.1

Response

HTTP/1.1 200

Content-Type: application/json

[
    {
        "name": "with-motivation",
        "description": "This custom query returns those annotations where the motivation is the given value",
        "label": "motivation=<motivation>",
        "created": "2024-08-15T15:02:59+0000",
        "createdBy": ":root:",
        "public": true,
        "queryTemplate": "{\"motivation\":\"<motivation>\"}",
        "parameters": [
            "motivation"
        ]
    }
]

Get a custom query search result page for a container

You can use the custom query in a container you have read access to, provided

Request

GET http://localhost:8080/services/{containerName}/custom-query/{queryCall} HTTP/1.1

Response

The matching annotations are returned in an AnnotationPage

The Link header gives the url for the custom query definition (using) and the expanded query (query)

HTTP/1.1 200

Link: <http://localhost:8080/global/custom-query/with-motivation>; rel="using", <http://localhost:8080/global/custom-query/with-motivation:motivation=aWRlbnRpZnlpbmc=/expand>; rel="query"
Content-Type: application/json

{
    "@context": [
        "http://www.w3.org/ns/anno.jsonld"
    ],
    "id": "http://localhost:8080/services/{containerName}/custom-query/{queryCall}?page=0",
    "type": "AnnotationPage",
    "partOf": {
        "id": "http://localhost:8080/services/{containerName}/custom-query/{queryCall}/collection",
        "type": "AnnotationCollection",
        "label": "{queryCallLabel}"
    },
    "startIndex": 0,
    "items": [
      ...
    ]
}


Get a custom query search collection for a container

Request

GET http://localhost:8080/services/{containerName}/custom-query/{queryCall}/collection HTTP/1.1

Response

HTTP/1.1 200

Content-Type: application/json

{
    "@context": "http://www.w3.org/ns/anno.jsonld",
    "id": "http://localhost:8080/services/{containerName}/custom-query/{queryCall}/collection",
    "type": "AnnotationCollection",
    "label": "{queryCallLabel}",
    "creator": ":root:",
    "first": {
        "id": "http://localhost:8080/services/{containerName}/custom-query/{queryCall}?page=0",
        "type": "AnnotationPage"
    }
}

Indexes

When some fields are queried often, creating an index on that field will speed up the querying.

Add index (πŸ”’)

Request

PUT http://localhost:8080/services/{containerName}/indexes/{fieldName}/{indexType} HTTP/1.1

(no body required)

Available options for indexType:

Response

HTTP/1.1 201 CREATED
Location: http://localhost:8080/services/my-container/indexes/body.type/hashed
Link: <http://localhost:8080/services/my-container/indexes/body.type/hashed/status>; rel="status"
Content-Type: application/json

{
    "startedAt": "2023-07-05T18:11:04",
    "finishedAt": null,
    "expiresAfter": null,
    "state": "RUNNING",
    "errors": [

    ],
    "processingTimeInMillis": 1
}

As creating an index for a container that already has a lot of annotations might take a while, this endpoint starts the index creation in the background, and returns the current status of the index creation in the body, and the url where to see an up-to-date status in a Link header.


Read index creation status (πŸ”’)

Request

GET http://localhost:8080/services/{containerName}/indexes/{fieldName}/{indexType}/status HTTP/1.1

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
    "startedAt": "2023-07-05T19:09:53",
    "finishedAt": "2023-07-05T19:09:53",
    "expiresAfter": "2023-07-05T20:09:53",
    "state": "DONE",
    "errors": [

    ],
    "processingTimeInMillis": 6
}

Read index (πŸ”’)

Request

GET http://localhost:8080/services/{containerName}/indexes/{fieldName}/{indexType} HTTP/1.1

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "field" : "body.metadata",
  "type" : "HASHED",
  "url" : "http://localhost:8080/services/my-container/indexes/body.metadata/hashed"
}

List all indexes for a container (πŸ”’)

Request

GET http://localhost:8080/services/{containerName}/indexes HTTP/1.1

Response

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "field" : "body.metadata",
    "type" : "HASHED",
    "url" : "http://localhost:8080/services/my-container/indexes/body.metadata/hashed"
  },
  ...
]

Delete index (πŸ”’)

Request

DELETE http://localhost:8080/services/{containerName}/indexes/{fieldName}/{indexType} HTTP/1.1

Response

HTTP/1.1 204 No Content

Admin

Get users (πŸ”’πŸ”’)

Request

GET http://localhost:8080/admin/users HTTP/1.1

Response

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "userName" : "user",
    "apiKey" : "1234567890abcdefghijklmnopqrstuvwxy"
  },
  ...
]

Add users (πŸ”’πŸ”’)

Request

POST http://localhost:8080/admin/users HTTP/1.1
Content-Type: application/json

[
  {
    "userName": "user1",
    "apiKey": "88bbfbe6cdb3965e55a3a2f0d5286d0e"
  },
  {
    "userName": "user2",
    "apiKey": "161cbd4930910a4455a830bfb95ebb6c"
  },
  ...
]

Response

{
  "added": [
    "user1",
    "user2"
  ],
  "rejected": []
}

Users will be rejected if:

Delete user (πŸ”’πŸ”’)

Request

DELETE http://localhost:8080/admin/{userName} HTTP/1.1

Response

HTTP/1.1 204 No Content

Container Users

These endpoints are only functional on annorepo servers that have authentication enabled.

Only users with admin rights to the given container (and the root user) can use these endpoints.

Users that have been previously added via the add users endpoint can be added to the given container, with one of these roles:

Get Container users (πŸ”’)

Request

GET http://localhost:8080/services/{containerName}/users HTTP/1.1

Response

HTTP/1.1 200 OK
Content-Type: application/json

[
    {
        "userName": "user1",
        "role": "ADMIN"
    },
    {
        "userName": "user2",
        "role": "GUEST"
    }
]

Add Container users (πŸ”’)

Request

POST http://localhost:8080/services/{containerName}/users HTTP/1.1

Content-Type: application/json

[
  { "userName": "username1", "role": "EDITOR" },
  { "userName": "username2", "role": "ADMIN" }
]

Response

HTTP/1.1 200 OK
Content-Type: application/json

[
    {
        "userName": "username1",
        "role": "EDITOR"
    },
    {
        "userName": "username2",
        "role": "ADMIN"
    },

    {
        "userName": "user1",
        "role": "ADMIN"
    },
    {
        "userName": "user2",
        "role": "GUEST"
    }
]

Delete Container user (πŸ”’)

Request

DELETE http://localhost:8080/services/{containerName}/users/{userName} HTTP/1.1

Response

HTTP/1.1 200 OK

Show containers for user (πŸ”’)

Calling this endpoint returns the containers that the authenticated user has access to, grouped by role.

Request

GET http://localhost:8080/my/containers HTTP/1.1

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
    "ADMIN": [
        "my-container"
    ],
    "GUEST": [
        "7696f94e-5809-45a2-b575-4872ce693bd2",
        "b4421353-debb-4be7-8891-ccb510bbdff7",
    ]
    "EDITOR": [
        "1ad90412-6247-47d4-b7bf-c9a4ec8bf8a4"
    ]
}

Miscellaneous

Get Annotation Field Count (πŸ”’)

When composing a search query, it helps to know what annotation fields you can search on, and also how many annotations contain that field. The services/fields endpoint serves this purpose: it will return a map/dictionary of all the annotation fields available in the given container, plus the number of annotations that field is used in.

Request

GET http://localhost:8080/services/{containerName}/fields HTTP/1.1

Response

HTTP/1.1 200 OK

Content-Type: application/json
Vary: Accept-Encoding

{
  "body.id" : 1,
  "body.type" : 15,
  "body.value" : 15,
  "target" : 15,
  "type" : 15
}

This response, for example, indicates there are 15 annotations in my-container with a target field, and only 1 with a body.id field.


Get Distinct Annotation Field Values (πŸ”’)

This endpoint will list the distinct values for the given annotation field in the given container.

Request

GET http://localhost:8080/services/{containerName}/distinct-values/{fieldName} HTTP/1.1

Response

HTTP/1.1 200 OK

Content-Type: application/json
Vary: Accept-Encoding

[
  value1,
  value2
]


OpenAPI

AnnoRepo provides an openapi API definition via swagger

The basic swagger UI is available via

GET http://localhost:8080/swagger HTTP/1.1

and the definition file is available as json via

GET http://localhost:8080/swagger.json HTTP/1.1

or

GET http://localhost:8080/openapi.json HTTP/1.1

and as yaml via

GET http://localhost:8080/swagger.yaml HTTP/1.1

or

GET http://localhost:8080/openapi.yaml HTTP/1.1

Server info

Get information about the servers

Request

GET http://localhost:8080/about HTTP/1.1

Response

HTTP/1.1 200 OK
...
Content-Type: application/json
...

{
  "appName" : "AnnoRepo",
  "version" : "0.3.0-beta",
  "startedAt" : "2022-09-22T15:30:24.854713Z",
  "baseURI" : "http://localhost:8080",
  "withAuthentication" : false,
  "sourceCode" : "https://github.com/knaw-huc/annorepo",
  "mongoVersion" : "5.0.8"
}