Connector API - Specification Version 1.0

The Jangle connector is an application that translates the back-end business logic, data models, relationships and workflows into the Jangle entities. In version 1.0, connectors must be REST based and follow the URI structure of standard Jangle resources (although the resources themselves can have a different name locally, see the section on connector service responses for more information). All responses are a combination of HTTP status codes and JSON data structures.

There are four kinds of responses a Jangle connector may return:

  1. Services: An explanatory response of the entities available in the connector, the paths to those entities, whether or not an entity is searchable, what kinds of mime-types are accepted in POST and PUT operations and what category filters can be used with a particular entity.
  2. Feed: A paged list of resources. Can include the relationships between resources and links to alternate metadata formats available.
  3. Explain: Analogous to an OpenSearch Description Document but also contains elements found in an SRU Explain response.
  4. Search: Basically identical to a feed response, a search response allows a Jangle core to return the results using OpenSearch extension elements.

All Jangle connectors must return a services response at {base_url}/services/.

Jangle connectors should not define absolute URLs internally, since it is possible that the same connector could be used by multiple Jangle cores (which, in turn, would mint different URIs for the same resource). Instead, connectors should be written to expect an HTTP Request header, $HTTP_X_CONNECTOR_BASE, to be sent by the Jangle core, and use that to construct absolute URIs. In the absence of this header, it is permissible for a connector to send a relative URI.

While connector paths do not have to conform to Jangle entity names, the URIs they create do, so if "actors" is mapped to /borrowers/ and "items" is mapped to /holdings/, URIs would still need to be returned as /actors/{id}/items/ and not /borrowers/{id}/holdings/.

Connector HTTP Status Codes -Specification Version 1.0

HTTP Status Codes

Note, the only status codes defined here are in relation to GET requests at specific resources. Since version 1.0 of the spec only defines GET, it is out of scope of this document to define responses to other HTTP methods.

For a successful request that returns a valid Jangle JSON response, return a 200 (OK).

Return a 301 (Moved Permanently) or 302 (Found) for requests that need to be redirected to the appropriate URI. Examples would be redirecting a request for /resources to /resources/ or if identifiers changed (say after an ILMS migration, for example) /resources/1234 to /resources/9876.

Connectors can utilize the 304 (Not Modified)/etags response for any request, although Jangle core servers may not be able to use it (depending on whether or not they maintain caches).

Authentication/Authorization (401 and 403 responses) is at the discretion of the implementer. They are legal on *any* URI.

For any invalid path or id, return a 404 (Not Found) response.

For a URI that was once valid but is no longer (whether via deletion or deactivation of some sort), it is acceptable to send either a 410 (Gone) or a 404 (Not Found).

For version 1.0 of this spec, clients should expect to receive a 405 (Method Not Allowed) by sending any HTTP method besides GET to a resource. However, it is possible and acceptable for implementers to create their own methods for handling create, update and delete requests, so this is not mandatory.

Connector Services Response - Specification Version 1.0

Connector service responses must resolve at base_url/services/ (or be redirected to from there).

Example:
http://connector.jangle.org/services/

Methods allowed: GET, HEAD

Content-type required: application/json

JSON Schema:

{
"description":"Services available from this Jangle connector",
"type":"object",
"properties":{
  "type":{"type":"string","pattern":"/^services$/"},
  "version":{"type":"string","pattern":"/^1\.0$/"},
  "title":{"type":"string",},
  "request":{"type":"string"},
  "entities":{"type":"object",
  "properties:{
    "Actor:{"optional":true,"type":"object",
      "properties":{
        "title":{"type":"string"},
        "searchable":{"type":["boolean","string"],"format":"uri"},
        "path":{"type":"string"},
        "categories":{"optional":true,"type":"array"}          
    },
    "Collection:{"optional":true,"type":"object",
      "properties":{
        "title":{"type":"string"},
        "searchable":{"type":["boolean","string"],"format":"uri"},
        "path":{"type":"string"},
        "categories":{"optional":true,"type":"array"}
    },
    "Item:{"optional":true,"type":"object",
      "properties":{
        "title":{"type":"string"},
        "searchable":{"type":["boolean","string"],"format":"uri"},
        "path":{"type":"string"},
        "categories":{"optional":true,"type":"array"}
    },
    "Resource:{"optional":true,"type":"object",
      "properties":{
        "title":{"type":"string"},
        "searchable":{"type":["boolean","string"],"format":"uri"},
        "path":{"type":"string"},
        "categories":{"optional":true,"type":"array"}
    }
  },
  "categories":{"optional":true,"type":"object"}
}

Which, in real terms, would return a response that looks something like:

{
    "title": "openbiblio",
    "version" "1.0",
     "type": "services",
     "entities": 
    {
        "Resource": 
        {
            "searchable": "/resources/search/description",
             "title": "Bibliographic records",
             "path": "/resources/",
             "categories": ["opac"]
        },
         "Collection": 
        {
            "searchable": false,
             "title": "Categories",
             "path": "/collections/"
        },
         "Item": 
        {
             "searchable": false,
             "title": "Holdings records",
             "path": "/items/"
        },
         "Actor": 
        {
            "searchable": false,
             "title": "Borrowers",
             "path": "/actors/"
        }
    },
     "request": "/services/",
    "categories":{
      "opac":{
        "scheme": "http://jangle.org/vocab/terms#dlf-ilsdi-resource",
        "label": "Resources that are available for harvesting in a discovery interface"
      }
    }
     
}

The definitions of each field are:

type:
The type of API response. This must be "services".
version:
The version of the API response. This must be "1.0".
title:
The name of the service. Is used by the Jangle core to set the URI paths. Cannot contain spaces or any non-alphanumeric characters.
request:
The request URI echoed to the request client.
entities:
A hash of the entities available for requesting from this server. (possible values: Actors, Collections, Items, Resources)
categories:
A hash of the filters that can be applied to entities.

And the definitions of the fields for the entities are:

title:
A human readable string for the contents of the entity.
path:
The base path (from the connector base url) to the entity.
searchable:
The URI of the entity's explain response or a boolean false if search is not supported.
categories:
An array of the category label that can be used with this entity.

And the categories are defined like this:

term:
The string used to identify the category. Note these must be unique across a Jangle connector service. In the JSON output this is the key of the categories hash.
scheme:
This is optional, but could have the value of a URI that explicitly defines what the category is (i.e. http://jangle.org/vocab/terms#recall)
label:
An optional human readable string describing the category.

Connector Feed Response - Specification Version 1.0

Feed responses are returned from any of the entity paths, including requests for single resources and relationships.

Example:
http://connector.jangle.org/resources/

Methods allowed: GET, HEAD

Content-type required: application/json

JSON schema:

{
"description":"A feed of entity resources",
"type":"object",
"properties":{
  "type":{"type":"string","pattern":"/^feed|search$/"},
  "request":{"type":"string","format":"uri"},
  "time":{"type":""string","format":"date-time"},
  "offset":{"type":"integer"},
  "totalResults":{"type":"integer"},
  "extensions":{"type":"object","optional":true,"additionalProperties":{
    "type":"string","format":"uri"
    }
  },
  "formats":{"type":"array","items":{"type":"string","format":"uri"}},
  "alternate_formats":{"type":"object","optional":true,
    "additionalProperties":{
      "type":"string","format":"uri"
    }
  },
  "stylesheets":{"type":"array","optional":true,"items":{"type":"string","format":"uri"}},
  "categories":{"type":"array","optional":true,"items":{"type":"string"}},
  "data":{"type":"array","items":[
    "type":"object",
    "properties":{
      "id":{"type":"string","format":"uri"},
      "title":{"type":"string"},
      "updated":{"type":string","format":"date-time"},
      "created":{"type":string","format":"date-time","optional":true},
      "description":{"type":"string","optional":true},
      "content":{"type":"string","optional":true,"requires":["content_type","format"]},
      "content_type":{"type":"string","requires":["content","format"],"optional":true},
      "stylesheet":{"type":"string","optional":true,"format":"uri"},
      "format":{"type":"string","format":"uri","requires":["content","content_type"]},
      "alternate_formats:{"type":"object","optional":true,
        "additionalProperties":{
          "type":"string","format":"uri"
        }
      },
      "relationships":{"type":"object","optional":true,
        "additionalProperties":{
          "type":"string","format":"uri"
        }
      },
      "categories":{"type":"array","optional":true,"items":{"type":"string"}},
      "author":{"type":"string","optional":true},
      "links":{"type":"object","optional":true,"properties":{
        "alternate":{"type":"object","optional":true,"properties":{
          "type":{"type":"string"},
          "title":{"type":"string","optional":true},
          "href":{"type":"string","format":"uri"}
          }
        },
        "related":{"type":"object","optional":true,"properties":{
          "type":{"type":"string"},
          "title":{"type":"string","optional":true},
          "href":{"type":"string","format":"uri"}
          }
        }
      },
      "additionalProperties":{"type":"object"}
    },
    "additionalProperties":{"type":"any"}
  }
}

An actual JSON feed response returning MARCXML records:

{
    "type": "feed",
     "totalResults": 6077,
     "time": "2008-09-30T16:11:03-04:00",
     "offset": 0,
     "request": "http://demo.jangle.org/openbiblio/resources/",
     "alternate_formats": 
     {
       "http://jangle.org/vocab/formats#http://purl.org/dc/elements/1.1/": "http://demo.jangle.org/openbiblio/resources/?format=dc",
       "http://jangle.org/vocab/formats#http://www.loc.gov/mods/v3": "http://demo.jangle.org/openbiblio/resources/?format=mods",
       "http://jangle.org/vocab/formats#http://www.openarchives.org/OAI/2.0/oai_dc/": "http://demo.jangle.org/openbiblio/resources/?format=oai_dc",
       "http://jangle.org/vocab/formats#application/marc21": "http://demo.jangle.org/openbiblio/resources/?format=marc"
     },
     "formats":["http://jangle.org/vocab/formats#http://www.loc.gov/MARC21/slim"],
     "data": 
    [
        {
            "alternate_formats": 
            {
                "http://jangle.org/vocab/formats#http://purl.org/dc/elements/1.1/": "http://demo.jangle.org/openbiblio/resources/5878?format=dc",
                 "http://jangle.org/vocab/formats#http://www.loc.gov/mods/v3": "http://demo.jangle.org/openbiblio/resources/5878?format=mods",
                 "http://jangle.org/vocab/formats#http://www.openarchives.org/OAI/2.0/oai_dc/": "http://demo.jangle.org/openbiblio/resources/5878?format=oai_dc",
                 "http://jangle.org/vocab/formats#application/marc21": "http://demo.jangle.org/openbiblio/resources/5878?format=marc"
            },
             "links": 
            {
                "alternate": 
                [
                    {
                        "type": "text/html",
                         "title": "Link to native interface",
                         "href": "http://catalog.jangle.org/openbiblio/shared/biblio_view.php?bibid=5878\u0026tab=opac"
                    }
                ],
                 "related": 
                [
                    {
                        "type": "application/atom+xml",
                         "href": "http://demo.jangle.org/openbiblio/resources/5878/collections/"
                    }
                ]
            },
             "title": "The Untamed",
             "author": "Brand, Max,",
             "updated": "2008-03-18T15:57:00-04:00",
             "categories": 
            [
                "opac"
            ],
             "content": "\u003Crecord xmlns='http://www.loc.gov/MARC21/slim'\u003E\u003Cleader\u003E      Z   22        4500\u003C/leader\u003E\u003Cdatafield tag='245' ind1='0' ind2='0'\u003E\u003Csubfield code='a'\u003EThe Untamed\u003C/subfield\u003E\u003Csubfield code='b'\u003E\u003C/subfield\u003E\u003Csubfield code='c'\u003Eby Max Brand\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='100' ind1='0' ind2='z'\u003E\u003Csubfield code='a'\u003EBrand, Max\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='42' ind1='z' ind2='z'\u003E\u003Csubfield code='a'\u003Edc\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='100' ind1='z' ind2='z'\u003E\u003Csubfield code='d'\u003E1892-1944\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='245' ind1='z' ind2='z'\u003E\u003Csubfield code='h'\u003E[electronic resource]/\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='260' ind1='z' ind2='z'\u003E\u003Csubfield code='b'\u003EProject Gutenberg,\u003C/subfield\u003E\u003Csubfield code='c'\u003E2004\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='500' ind1='z' ind2='z'\u003E\u003Csubfield code='a'\u003EProject Gutenberg\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='506' ind1='z' ind2='z'\u003E\u003Csubfield code='a'\u003EFreely available.\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='516' ind1='z' ind2='z'\u003E\u003Csubfield code='a'\u003EElectronic text\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='830' ind1='z' ind2='z'\u003E\u003Csubfield code='a'\u003EProject Gutenberg\u003C/subfield\u003E\u003Csubfield code='v'\u003E10886\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='856' ind1='z' ind2='z'\u003E\u003Csubfield code='u'\u003Ehttp://www.gutenberg.org/etext/10886\u003C/subfield\u003E\u003Csubfield code='3'\u003ERights\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='856' ind1='z' ind2='z'\u003E\u003Csubfield code='u'\u003Ehttp://www.gutenberg.org/license\u003C/subfield\u003E\u003C/datafield\u003E\u003C/record\u003E",
             "content_type": "application/xml",
             "format":"http://jangle.org/vocab#http://www.loc.gov/MARC21/slim",
             "created": "2008-03-18T15:57:00-04:00",
             "id": "http://demo.jangle.org/openbiblio/resources/5878",
             "relationships": 
            {
                "http://jangle.org/rel/related#Collection": "http://demo.jangle.org/openbiblio/resources/5878/collections/"
            }
        }
    ]
}

The definitions of each field are:

type:
Sets the feed type to allow the Jangle core to set the proper serialization of the JSON response. This can be either "feed" or "search" depending on the request type.
request:
The URI of the requested resource (including query parameters).
time:
An ISO 8601 formatted timestamp denoting when the response was created.
offset:
The offset (from zero) of the first entry in the result set.
totalResults:
The number of resources available at this URI.
extensions:
A hash of namespaces of any extension data being send in the results. The syntax is "extensions": {"xmlns prefix:{"xmlns":"Namespace URI","xsd":"Schema URL (optional)"}}
alternate_formats:
A hash of the URIs of feeds that represent the same resources in a different data format. Takes the form: "alternate_formats":{"Jangle URI of format (i.e. http://jangle.org/vocab/formats#http://purl.org/dc/elements/1.1/)":"URI of resource with alternate format"}
stylesheets:
An optional array of XSLT stylesheet URLs to transform the data after it has been serialized as Atom. The stylesheets will be applied in the order of the array.
categories:
An optional array of category terms that apply to the resource.
formats:
An array of Jangle Format URIs that appear in the current list of resources.
data:
An array of objects that contain the actual resources requested.

The objects in the data array have the following elements:

id:
The URI of the resource.
title:
A human readable string to identify the resource.
updated:
An ISO 8601 formatted timestamp of the last modification time of the resource.
created:
An optional ISO 8601 formatted timestamp of the creation date of the resource.
description:
A human readable description of the resource. Optional.
content:
This element contains the actual resource data itself in the format requested by the client. While technically "optional", this element should only be omitted if the client explicitly requests the results in Atom. The content element requires the presence of content_type in the same object.
content_type:
The mime-type of the content element. Required if the content element exists.
format:
The Jangle Format URI of the data in the "content" element.
stylesheet:
The URL of an XSLT stylesheet to apply to the data contained in the content element. Optional.
alternate_formats:
A hash of the URIs of feeds that represent the same (single) resource in a different data format. Same syntax as the root level element.
relationships:
A hash of feeds of resources related this resource. Takes the form: "relationships":{"Jangle URI of related entity (i.e. http://jangle.org/vocab/Entity#Item)":"URI to resource"}
categories:
An optional array of category terms that apply to the resource.
author:
An optional string identifying the creator of the resource.
links:
A representation of the Atom link element. These do not need to include alternate_formats and relationships links.

Additional elements can be added to data objects, their keys need to conform to a convention of xmlns_prefix:element_name. Object syntax of data contained in an extension element has not yet been decided.

Connector Explain Response - Specification Version 1.0

Explain responses are means to advertise the search attributes of a given entity. At their simplest, they provide enough information to create an OpenSearch Description Document, but they can also include the supported CQL indexes, relations and modifiers available for a search client to perform more sophisticated queries.

Example:
http://connector.jangle.org/resources/search/description/

Methods allowed: GET, HEAD

Content-type required: application/json

JSON Schema:

{
"description":"A Jangle Explain Response",
"type":"object",
"properties":{
  "type":{"type":"string","pattern":"/^explain$/"},
  "request":{"type":"string","format":"uri"},
  "shortname":{"type":"string","optional":true,"maxLength":16},
  "description":{"type":"string","maxLength":1024},
  "template":{"type":"string","format":"uri"},
  "contact":{"type":"string","format":"email","optional":true},
  "tags":{"type":"array","items":{"type":"string"},"optional":true},
  "longname":{"type":"string","optional":true,"maxLength":48},
  "image":{"type":"object","optional":true,"properties":{
    "height":{"type":"integer","minimum":0,"optional":true},
    "width":{"type":"integer","minimum":0,"optional":true},
    "type":{"type":"string","optional":true},
    "location":{"type":"string","format":"url"}}
  },
  "query":{"type":"object","optional":true,"properties":{
    "example":{"type":"string","optional":true},
    "context-sets",{"type":"array","items":{"type":"object","optional":true,"properties":{
      "identifier":{"type":"string","format":"uri"},
      "name":{"type":"string"},
      "indexes":{type":"array","items":{"type":"string"}}
      }
    }
  },
  "developer":{"type":"string","optional":true,"maxLength":64},
  "attribution":{"type":"string","optional":true,"maxLength":256},
  "syndicationright":{"type":"string","enum":
    ["open","limited","private","closed"],"optional":true},
  "adultcontent":{"type":"boolean","optional":true},
  "language":{"type":["string","array"],"items":{"type":"string"},"optional":true},
  "inputencoding":{"type":["string","array"],"items":{"type":"string"},"optional":true},
  "outputencoding":{"type":["string","array"],"items":{"type":"string"},"optional":true}
  }
}

An example JSON response:


{
    "type":"explain",
    "shortname":"Bibliographic records",
    "longname":"Search Bibliographic records in OpenBiblio",
    "request":"http:\/\/connector.jangle.org\/resources\/search\/description\/",
    "description":"Bibliographic records search.  Defaults to keyword anywhere.",
    "template":
      "http:\/\/connector.jangle.org\/resources\/search\/description\/?offset={startIndex?}&
count={count?}&query={searchTerms?}&format={jangle:format?}",
    "tags": ["catalog", "library"],
    "syndicationright":"open",
    "query":{
      "example":"dc.creator=thomas",
      "context-sets":[
        {
        "name":"dc",
        "identifier":"info:srw/cql-context-set/1/dc-v1.1",
        "indexes":["title","creator","subject","publisher","format","identifier"]
        },
        {
        "name":"rec",
        "identifier":"info:srw/cql-context-set/2/rec-1.1",
        "indexes":["identifier","collectionName","lastModificationDate","creationDate"]
        },
        {
        "name":"cql",
        "identifier":"info:srw/cql-context-set/1/cql-v1.2",
        "indexes":["allRecords","allIndexes","anyIndexes","keywords"]
        }
      ],
    },
  }
}

The definitions of each field are:

type:
Sets the type of the feed for the Jangle core to serialize properly. Must be explain
request:
The URI of the explain document.
shortname:
A brief label for the search. Optional. If not set, Jangle will use the entity name.
description:
A human readable description of the search target.
template:
A URL template conforming to OpenSearch URL parameters syntax.
contact:
An email address of the maintainer of the connector search. Optional
tags:
An optional array of terms to classify the search target.
longname:
An optional, human-readable string to identify the search target.
image:
An optional hash to provide an icon or image for the search target.
The elements of the hash are:
height:
The height, in pixels, of the image. Optional.
width:
The width, in pixels, of the image. Optional.
type:
The mime-type of the image. Optional.
location:
The URL of the image.
query:
A hash that sets the query definitions. Optional.
The elements of the hash are:
example:
An optional example query string that will return results for this search target.
context-sets:
An array of objects declaring the indexes available to search. Optional.
The elements of this object are:
name:
The prefix used in queries to declare the context set to use.
identifier:
The URI of the context set.
indexes:
An array of indexes available to use within the context set.
developer:
A human readable string of the maintainer or creator of the application. Optional
attribution:
An optional string containing any sources that should be credited for the content.
syndicationright:
An enumeration of redistribution privileges. Options are open, limited, private or closed. This element is optional (although omission defaults to "public".
adultcontent:
Boolean. Optional. Omission assumes "false".
language:
An optional string or array of language codes.
inputencoding:
Optional. Defines the character encoding of the query string. Default is UTF-8.
outputencoding:
Optional. Defines the character encoding of the query string. Default is UTF-8. Relevant for MARC-8 encoded data.

Connector Search Response - Specification Version 1.0

Search JSON responses follow the same syntax as feed responses, the only difference being the "type" element, which should be "search".

Example:
http://connector.jangle.org/resources/search/?offset=100&count=100&query=rec.lastModificationDate>=2008-08-08

Methods allowed: GET, HEAD

Content-type required: application/json

All searchable resources must be declared as searchable in the services response and have a corresponding explain response. Connectors do not need to explicitly send a link element advertising the URI of the explain resource, since the Jangle core can glean this from the services resource.

The query terms must be in CQL syntax. If no indexes or relations are sent, the search should be translated to "cql.serverChoice all {query_terms}" (i.e. indexes of the implementor's choice, boolean ANDed). It is acceptable for a search service to support only this (i.e. no defined indexes, relations or modifiers).