API Documentation

ID Management Service
Service for creating, updating and managing links between identifiers.
Last updated: 2026-03-30
See revision history

Used for querying, creating and updating links in the system.

If a valid id is specified in the URL, this returns a JSON-LD response containing the link (a Web Annotation Model Annotation object - https://www.w3.org/TR/annotation-vocab/#annotation)

URL : /links/{LINK UUID}

Method : GET

Auth required : No

HTML/JSON : Both

Success Response

Code : 200 Okay

Example:

{
  "@context": "http://www.w3.org/ns/anno.jsonld",
  "body": {
    "generator": "https://data.getty.edu/local/thesaurus/generators/aspace",
    "id": "urn:getty-local:repositories/3/resources/1150",
    "format": null,
  },
  "created": "2020-07-07T17:44:19Z",
  "id": "https://services.getty.edu/id-management/links/883310f6-d759-4ec7-8d48-cb77e1c15882",
  "label": "testgroup",
  "dcterms:title": "Test Title",
  "dcterms:alternative": "Test Reverse Title",
  "modified": "2020-07-07T17:44:19Z",
  "creator": {
    "name": "Test User"
  },
  "motivation": "https://data.getty.edu/local/thesaurus/motivations/contains-accno",
  "target": {
    "generator": "https://data.getty.edu/local/thesaurus/organizations/GRI",
    "id": "urn:getty-local:2003.M.46",
    "format": null,
  },
  "type": "Annotation"
}

Error Responses

Code: 404 - if the link does not exist

This endpoint supports querying using simple GET url-encoded parameters. There is another endpoint /query/ (see below) which supports queries using JSON (which makes it easy to specify multiple ids for some fields).

(Returns a JSON-LD AnnotationCollection/AnnotationPage type response.)

If page is not specified, this will respond with an AnnotationCollection response that embeds the first page of results in the "first" property. (context for the WebAnnotation response standard: http://www.w3.org/ns/anno.jsonld)

The request can be filtered by passing GET parameters:

  • target_id The identifier URI for the target (this parameter can be included multiple times in a single request eg target_id=...&target_id=...&target_id=...)
  • body_id The identifier URI for the body (this parameter can be included multiple times in a single request)
  • any_id The identifier must be in EITHER the body id OR the target ID. This is the most useful parameter to use when exploring what links involve a given URI.
  • target_generator the generator for the target
  • body_generator the generator for the body
  • motivation The URI of the motivation (oa:linking, or subclass - full URI)
  • group_id The label assigned to a link as its group_id. This is the label in the Annotation
  • creator_name The name of the agent/system that created the link

Note that these are exact filters and the results will only include links that match each parameter given exactly.

Generator is being used from the Lifecycle information in the Annotation model. It is not strictly an intended fit for this, but the target and body IDs are not readily expressible as URIs. There are many use cases to filter by generator however, as this encodes the source of this identifier. https://www.w3.org/TR/annotation-model/#lifecycle-information

These filters will be combined into the final query, and the response will be the intersection of all of the applied filters. The items returned will satisfy all of the filters provided. The exception is when multiple values have been supplied as a target_id or body_id, the returned links only have to match one of these values for each term. Eg if multiple ids for target_id were in the request: ?target_id=ID1&target_id=ID2... target_id=ID10, then the response will be all the links that have any one of these IDs as its target_id. Further parameters will filter this list as a whole. Adding a motivation=... will filter out any links that do not have that motivation, for any of the links that match the list of target_ids given.

What filters were applied will be in the "label" of the AnnotationCollection/Page objects as a convenience. If a Generator URI or Motivation URI is not found in the system, it will NOT be included in the filter and it will return an HTTP 400 error response.

The amount of entries returned in the list is limited by a configuration parameter LINK_PAGE_LIMIT which is by default set to 500.

Example JSON body for the AnnotationCollection for a filtered list of links:

URL : /links/

URL : /links/page/{page number} - pagination API of a query that exceeds the page response limit.

Method : GET

Auth required : No

HTML/JSON : Both

Data requirements

GET parameter values MUST be URL encoded.

GET /links?any_id=urn%3Agetty-local%3Atms%3Aobject%3Aid%2F61343

Success Response

Code : 200 Okay

The JSON-LD response will be an AnnotationCollection object, which comprises a set of AnnotationPage object containing a set of links. This is to allow pagination of very large sets of data (10,000 links or more) which would not be feasible to return in one response.

As the list of links is the most important use of this endpoint, the first AnnotationPage object is embedded in the AnnotationCollection response. This provides the first page of links within the initial response to the end user, without requiring them to make a second request to get the first page of responses. Within normal use-cases, the set of links will fit comfortably within a single page so the aim with this is for it to be a single request.

The AnnotationCollection responses will always have an embedded “first” property which contains the core part of first AnnotationPage response.

The AnnotationCollection or AnnotationPage response will only include a "next" property if there is another page of results to return, and this will hold the value of the URI to that page. If it is not present, then all of the results will be contained in the 'first' value.

The responses are queries on a live database and they are stateless queries. The responses from the URIs are not stable, and will change when the underlining data changes.

(Note that LDP container accept requests to shape the response are not supported - the list of annotations will be given with full description in an AnnotationCollection as described above.)

Example responses:

An unfiltered query

This shows the response for an parameter-less query. It will attempt to show all of the links in the instance, presented in JSON-LD as a AnnotationCollection. This unfiltered response is not particularly useful as the system will contain millions of links in normal use.

{
  "@context": "http://www.w3.org/ns/anno.jsonld",
  "id": "https://services.getty.edu/id-management/links/",
  "label": "Collection of Link Annotations",
  "last": "https://services.getty.edu/id-management/links/page/10225",
  "next": "https://services.getty.edu/id-management/links/page/2",
  "total": 2044986,
  "type": "AnnotationCollection"
  "first": {
    "id": "https://services.getty.edu/id-management/links/page/1",
    "items": [
      {
        "body": {
          "generator": "https://data.getty.edu/local/thesaurus/generators/gcis",
          "id": "https://media.getty.edu/iiif/image/ee8db855-5367-4932-9015-3e49f78aee53",
          "format": null
        },
        "created": "2020-06-24T23:27:40Z",
        "id": "https://services.getty.edu/id-management/links/f5b9c890-ea30-4c32-8969-9a4ccc8c60e4",
        "label": "testdata",
        "dcterms:title": "Test Link",
        "dcterms:alternative": "Test Reverse Link",
        "creator": {
          "name": "Test User"
        },
        "modified": "2020-06-24T23:27:40Z",
        "motivation": "https://data.getty.edu/local/thesaurus/motivations/iiif_image_of",
        "target": {
          "generator": "https://data.getty.edu/local/thesaurus/generators/otmm",
          "id": "urn:getty-local:8f1ea3128622c8d9d43a0f05d5b59864076e25c3",
          "format": null
        },
        "type": "Annotation"
      },
      // ... RESULTS TRUNCATED ...
      {
        "body": {
          "generator": "https://data.getty.edu/local/thesaurus/generators/gcis",
          "id": "urn:getty-local:76104111-4bcd-4b23-b9ee-0af99da8c038",
          "format": null
        },
        "created": "2020-06-25T00:46:21Z",
        "id": "https://services.getty.edu/id-management/links/f981c4bc-a9bb-4bc0-9a46-6a095af273e6",
        "label": "newgroup",
        "dcterms:title": "Test Title",
        "dcterms:alternative": "Test Reverse Title",
        "modified": "2020-06-25T00:46:21Z",
        "creator": {
          "name": "Test User"
        },
        "motivation": "https://data.getty.edu/local/thesaurus/motivations/iiif_ap_collection_of",
        "target": {
          "generator": "https://data.getty.edu/local/thesaurus/generators/rosetta",
          "id": "urn:getty-local:IE207628",
          "format": null
        },
        "type": "Annotation"
      },
    ],
    "startIndex": 0,
    "type": "AnnotationPage"
  },
}

Resolving the 'next' URI "https://services.getty.edu/id-management/links/page/2" will result in a response that is a single AnnotationPage,

{
  "@context": "http://www.w3.org/ns/anno.jsonld",
  "id": "https://services.getty.edu/id-management/links/page/2",
  "items": [
    {
      "body": {
        "generator": "https://data.getty.edu/local/thesaurus/generators/gcis",
        "id": "https://media.getty.edu/iiif/manifest/5b2ecb8f-759a-4d2e-a5fc-057250657e84"
      },
      "created": "2020-06-25T01:14:55Z",
      "id": "https://services.getty.edu/id-management/links/7e9dbc3d-e87c-4a1b-8673-8fa32c2bd3eb",
      "label: "testdata",
      "modified": "2020-06-25T01:14:55Z",
      "motivation": "https://data.getty.edu/local/thesaurus/motivations/iiif_manifest_of",
      "target": {
        "generator": "https://data.getty.edu/local/thesaurus/generators/tms",
        "id": "urn:getty-local:8132"
      },
      "type": "Annotation"
    },
    // ... RESULTS TRUNCATED ...
  ],
  "label": "Full list of Links",
  "next": "https://services.getty.edu/id-management/links/page/3",
  "partOf": {
    "id": "https://services.getty.edu/id-management/links/",
    "label": "Collection of Link Annotations",
    "total": 2121657,
    "type": "AnnotationCollection"
  },
  "startIndex": 200,
  "type": "AnnotationPage"
}
Query with a filter:

This shows the response to the request GET /links?motivation=https%3A//data.getty.edu/local/thesaurus/motivations/contains-refid which will return only the links that have this specific motivation.

{
  "@context": "http://www.w3.org/ns/anno.jsonld",
  "first": {
    "id": "https://services.getty.edu/id-management/links/page/1?motivation=https%3A//data.getty.edu/local/thesaurus/motivations/contains-refid",
    "items": [
      // RESPONSE ITEMS OMITTED
    ],
    "startIndex": 0,
    "type": "AnnotationPage"
  },
  "id": "https://services.getty.edu/id-management/links/?motivation=https%3A//data.getty.edu/local/thesaurus/motivations/contains-refid",
  "label": "Collection of Link Annotations for [[('motivation', 'https://data.getty.edu/local/thesaurus/motivations/contains-refid')]]",
  "last": "https://services.getty.edu/id-management/links/page/2399?motivation=https%3A//data.getty.edu/local/thesaurus/motivations/contains-refid",
  "next": "https://services.getty.edu/id-management/links/page/2?motivation=https%3A//data.getty.edu/local/thesaurus/motivations/contains-refid",
  "total": 479775,
  "type": "AnnotationCollection"
}

And the corresponding 'next' AnnotationPage response:

{
  "@context": "http://www.w3.org/ns/anno.jsonld",
  "id": "https://services.getty.edu/id-management/links/page/2?motivation=https%3A//data.getty.edu/local/thesaurus/motivations/contains-refid",
  "items": [
    // RESPONSE ITEMS OMITTED
  ],
  "label": "Filtered list of Links [[('motivation', 'https://data.getty.edu/local/thesaurus/motivations/contains-refid')]]",
  "next": "https://services.getty.edu/id-management/links/page/3?motivation=https%3A//data.getty.edu/local/thesaurus/motivations/contains-refid",
  "partOf": {
    "id": "https://services.getty.edu/id-management/links/?motivation=https%3A//data.getty.edu/local/thesaurus/motivations/contains-refid",
    "label": "Collection of Link Annotations for [[('motivation', 'https://data.getty.edu/local/thesaurus/motivations/contains-refid')]]",
    "total": 479775,
    "type": "AnnotationCollection"
  },
  "startIndex": 200,
  "type": "AnnotationPage"
}

Error Response

Code: 400 Bad Request

Typically, this will be because the query specifies a motivation or generator that is not registered with the ID Manager instance.

Query for a set of links (POST JSON)

Query endpoint for retrieving Web Annotations using a JSON POSTed query instead of the HTTP GET parameter route. This can be clearer when querying for multiple values for the same fields, and requires only JSON encoding, rather than URL encoding the values.

The prev/next/last links while present are the GET parameter version of the query, and pagination will have to be handled by the client by adding in a 'page' key, set to the page number to retrieve.

URL : /query/

Method : POST

Auth required : No

HTML/JSON : JSON

Data requirements

All keys are optional. The query should only need to specify the values for filtering. NB any_id will take precedence over target_id/body_id values.

{
  "target_id": "",        // Can be a list, ["URI1", "URI2", ...]. Response will be a inclusive OR on these
  "target_generator": "", // Target Generator URI
  "body_id": "",          // Can be a list, ["URI1", "URI2", ...]
  "body_generator": "",   // Body Generator URI
  "any_id": "",           // Will match any link with this value as the body or target ID. 
  "motivation": "",       // Motivation URI 
  "group_id": "",         // Group ID
  "creator_name": "",     // Filter by name of link creator
  "page": ""              // Large responses may be spread over multiple pages
}

Success Response

Code : 200 Okay

The responses will be the same AnnotationCollection/AnnotationPage objects that the /links/ endpoint above described.

Example JSON query message

Getting the second page of links with multiple named target ids and the given motivation):

POST /query/
    {
        "target_id": ["id1","id2","id3"],
        "motivation": "https://motivation.uri",
        "page": 2
    }
Query Pagination details

As this no longer can be done by 'following your nose' on the 'first'/'next'/'prev'/'last' GET URLs, a good pattern would be:

Test the response: Is there a 'next' key?

  • No? - this is the final/only page
  • Yes? - There are more pages. Resend the JSON query, but with json'page' = json'page' + 1

If no page parameter is supplied, page will default to 1

If a request is made for a page without any results, an HTTP 404 error will be returned.

Endpoint for creating or updating links. One request can create or update a single link, or multiple links.

  • Single Link creation: HTTP POST a JSON body with the necessary keys (see data requirements below). Returns HTTP 201 if it created a record.
  • Bulk Link creation: HTTP POST a JSON list of Link information. Each item on the list MUST be valid for the changes to be committed to the IDM. The list items are the same data as for creating a single Link as described below. This will respond with HTTP 200 as this API call causes multiple new resources to be created.

URL : /links/

Method : POST

Auth required : YES

HTML/JSON : Both (HTML has its own form views for adding single links)

Data requirements

The minimum set of keys is the following:

{
  "motivation": "...",      // must be a registered motivation URI
  "body": {
    "id": "...",            // Body ID (URI)
    "generator": "..."      // must be a registered generator URI
  },
  "target": {
    "id": "...",            // Target ID (URI)
    "generator": "..."      // must be a registered generator URI
  }
}

This combination of fields will be unique in the ID Manager. Attempting subsequently to create a link with the same values for these five fields will not succeed, and will only update the metadata for the existing link. A HTTP 409 Conflict response may be returned if anything breaks this uniqueness constraint.

Alongside these mandatory fields, there are a number of metadata fields.

  • The dcterms:title (or title), and dcterms:alternative (alternative) fields can be set to a text value which should describe the link in some manner (aligning with the definitions of dcterms:title and dcterms:alternative).
  • The creator can be given a name, and will often reflect the process or system agent that created the link.
  • A group_id can be specified. This is largely a logistical grouping, and does not imply a semantic connection between the links beyond that they may have been derived from the same data, or were part of the same project. Groups can be listed and deleted easily, and can be a useful way to deal with updates when the links are not easily connected by the other field values.
  • Both body and target can have a format (dc:format) mimetype specified. This is to aid presentation by web interfaces which likely wish to present a link to a PDF in a different manner than a direct link to video.

A fully specified link might look like the following:

{
  "motivation": "https://data.getty.edu/local/thesaurus/motivations/iiif_link",
  "group_id": "rosetta://IE23212",
  "dcterms:title": "IIIF Link Title",
  "dcterms:alternative": "Rosetta Image",
  "creator": {
    "name": "GCIS ETL"
  },
  "body": {
    "id":"rosetta://12345",
    "generator": "https://data.getty.edu/local/thesaurus/systems/rosetta",
    "format": "image/tiff",
  },
  "target": {
    "id": "http://media.getty.edu/iiif/manifest/6c2219dd-fd11-46e9-8cc3-83cc946e3c64",
    "generator": "https://data.getty.edu/local/thesaurus/systems/gcis",
    "format": "image/jpeg",
  }
}
Batch creating/updating

The same endpoint can be sent a list of the link objects, as long as they all conform to the same requirements as outlined for a single link above. All links sent will be added as part of the same transaction, so they will either all succeed to be added or updated, or they will all fail. This can happen if one of the links breaks the uniqueness constraint within the supplied links or if a link uses a generator or motivation that is not yet registered.

If links are sent which match links already in the system, then the service may response with an HTTP 409 Conflict error. The ID Manager can be run with an environment flag set (UPSERT_ON_CONFLICT) so that in this case of conflict, the metadata for those links are updated with the newly supplied metadata (title, creator, etc), and the date modified is updated. (An Upsert is a combination of SQL insert and update, and can be used in postgresql to handle a combination of inserts, or updates in cases where the uniqueness constraint would be broken by an insert).

Success Response

Code : 201 Created, 200 Okay (single/multiple)

The responses will be the Annotation object(s) for the link or links, returned in the same manner as resolving the link URI (eg GET /links/{link uri}).

This request will delete the specified link and metadata. Multiple links may be specified as part of the call to delete links as a batch. Note that this URI is the same URI as given as the 'id' for a given Annotation, eg "id": "https://services.getty.edu/id-management/links/7e9dbc3d-e87c-4a1b-8673-8fa32c2bd3eb"

For bulk deletion, supply a list of the Link ids (as UUIDs) as part of the URL. For example:

DELETE /links/8a98bca3-c76a-4692-aab5-89ddbee6f768,8e94e1ca-54eb-471d-b84c-798ec68aa123,6a9bff42-d289-444f-8828-91f54cee5ce6

The response format will be JSON. If a single Link deletion is attempted but the Link does not exist, the response will be HTTP 404. If the response is HTTP 200, then either the single link deletion has succeeded, or this was part of a bulk deletion, and clients should parse the JSON reponse to see which deletions succeeded or failed (due to not existing). These will as a tuples of the UUID and True/False, depending on success.

For activities that will regularly delete in bulk, it is recommended to use groups instead as a means to delete sets of related links.

URL : /links/{id}

URL : /links/{id1},{id2},{id3} - link uuids, comma-separated

Method : DELETE

Auth required : YES

HTML/JSON : Both (HTML has its own view response)

Success Response

Code : 200 Okay

Example single deletion response
{
  "title": "Delete response",
  "items": [
    [
      "8a98bca3-c76a-4692-aab5-89ddbee6f768",
      true
    ]
  ]
}

If the above link did not exist, an HTTP 404 response would be given.

Example bulk deletion response
{
  "title": "Delete response",
  "items": [
    [
      "8a98bca3-c76a-4692-aab5-89ddbee6f768",
      true
    ],
    [
      "8e94e1ca-54eb-471d-b84c-798ec68aa123",
      true
    ],
    [
      "6a9bff42-d289-444f-8828-91f54cee5ce6",
      false
    ]
  ]
}

Link ID 6a9bff42-d289-444f-8828-91f54cee5ce6 did not exist, and so the response is false. The HTTP response will still be HTTP 200 in this case.