Tech Blog

How we’re building APIs at Ex Libris

As a part of the launch of the Developer Network, we at Ex Libris have done some work on standardizing our external APIs. The goal of standardization is to allow those developing applications against Ex Libris products to learn one style and leverage that knowledge across our products. These standards are being implemented for all new APIs for both Alma and Primo. Adoption for other products depends on the various development road maps and existing styles for those products.

While much of the standardization can be derived from the API documentation and the Getting Started Guide, this blog entry articulates our thinking and style choices.

Happy programming!

Quick Links

REST URLs

All of our new APIs are being developed in the REST style. REST has been described well enough in many places, so we don’t review it here. But our approach does mean that you’ll see our APIs in the form of nouns, such as:
  • /bibs
  • /bibs/ABFE1234
  • /courses
  • /courses/5586/readinglists

URL Format

All REST URLs are built in the following manner:

HTTP Methods

The REST approach means we will use HTTP verbs to describe the action you wish to take. In general, we map HTTP verbs to CRUD actions in the following manner:
CRUD ActionHTTP Verb
C / CreatePOST
R / ReadGET
U / UpdatePUT
D / DeleteDELETE

This translates to the following style for CRUD actions on Ex Libris entity objects:

ResourcePOST
Create
GET
Read
PUT
Update
DELETE
Delete
/coursesCreate a new courseList of coursesNot supportedNot supported
/courses/1234Not supportedGet course detailsUpdate courseDelete course

Non-CRUD Services

The action to HTTP verb mapping works well for standard CRUD operations. However, there is no natural mapping for non-CRUD services. For example, in the library world, we have a service such as “renew a loan” or “receive an item”. We want to avoid falling into the trap of defining SOAP/RPC-like endpoints, and it’s important to stick with the REST standard of nouns as URIs.  So a service such as POST /items/1234/loans/renew or POST /items/1234/renewLoan is undesirable.
Given this, our decision was to POST to the object with the operation in the query string. A PUT request updates the object, while POST is reserved to other undefined actions. The renew loan service therefore looks like POST /items/1234/loans/5678?operation=renew.

Default values

Note that for POST we don’t add the entity ID to the URL. It is sent in the XML, or generated automatically by the system. When creating a new entity with POST, missing elements will be given default values (and if they are mandatory, an error message will be returned).

When updating with PUT, the same behavior is followed: missing elements will be given default values. We “swap-all”. Calling applications are expected to run a GET before PUT, and send the entire entity with the relevant fields modified.

API Descriptions

WADLs

For each API, we make a WADL document available. Look for a link on the relevant API documentation page.

OpenAPI

OpenAPI descriptions for available for many APIs. For more information, see OpenAPI Support.

XSDs

We make XSDs available for all of our REST APIs which accept or return objects.

HTTP Responses

We support the following HTTP response codes for all HTTP methods:

HTTP CodeDescription
200Request was successful
204Request was successful but no content returned e.g. for DELETE requests
401Unauthorized
400Logical error- check the error message, fix your data, and try again
500Server error- try your request again, and if unsuccessful, submit a support case

Error format

In the case of a 400 or 500 error, the following error object will be returned:

{
  "errorsExist":true,
  "errorList":
    {
      "error": [
        {
          "errorCode":"401861",
          "errorMessage":"User with identifier fdsa was not found.",
          "trackingId":"E02-0811142608-O9VFN-AWAE57127058"
        }
      ]
    }
  }
}

 

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<web_service_result xmlns="http://com/exlibris/urm/general/xmlbeans">
  <errorsExist>true</errorsExist>
  <errorList>
    <error>
      <errorCode>90101</errorCode>
      <errorMessage>Table does not exist.</errorMessage>
    </error>
  </errorList>
</web_service_result>

404 Not Found

There is a long standing argument on the web regarding when to return a 404 Not Found error. We have decided to return 404 only when the service does not exist. If the client requested the correct service but specified an incorrect identifier, we will return a 400 Bad Request with an appropriate code and description in the error object.

For example, a request to https://api-na.hosted.exlibrisgroup.com/almaws/v1/userss/me@library.org (with two s’s in the URL) will return 404. A request to https://api-na.hosted.exlibrisgroup.com/almaws/v1/users/baduser@library.org will return 400 with error code 401861 returned in the error object (as specified here).

Client applications can catch 400 errors and examine the error object to determine the appropriate response to the user.

Content Types

Our APIs support either JSON or JSON and XML. You can use either a query string parameter or an HTTP header to specify the content type you’re sending or wish to receive.

Querystring

https://...?format=json

https://...?format=xml

HTTP Header

GET:

Accept: application/json

Accept: application/xml

POST/PUT:

Content-Type: application/json

Content-Type: application/xml

Other Notes

Dates & Times

We support XML date/time format for all date/time fields. (For those into such things, that’s ISO-8601.) For GET requests, we will always return the time in UTC with a Z at the end. If you don’t provide an offset in your value, we’ll assume the time of the data center for your instance.

Since this is a popular topic, we’ve written a separate blog entry which includes some examples: Time-zones and Dates in Alma APIs.

Clearing/deleting fields

PUT requests can include fields with an empty string in order to remove all content from the field.
In XML: <field></field>  or : <field/>
JSON: “field”: “”

Pagination

We support pagination in list APIs (e.g. GET users), with the following query-string parameters:
  • limit- the number of rows to return
  • offset- the row number to start with

The response to the GET list API includes the total number of entities in the list in total_record_count attribute. For example: <users total_record_count=”6448″>. This information can be used by the calling application in order to retrieve the list in chunks, untill there is no more entities in the list.

It is common that an application displays the first chunk of entities, and retrieves the next chunk only upon a request from the end user.Examples:

  • GET /almaws/v1/users – Retrieve the first 10 users in the list (based on limit/offset defaults)
  • GET /almaws/v1/users?offset=10 – Retrieve the 10 users, starting with the 11th user (this is the second chunk of users, in case each chunk is 10 users)
  • GET /almaws/v1/users?limit=50 – Retrieve the first 50 users in the list

Codes & Descriptions

Whenever a field has both a code and a description, we will provide the description as an XML attribute:
<user_group desc="Staff">08</user_group>

Or as a structure in JSON:

"user_group": 

      {

        "value": "08",

        "desc": "Staff",

}

Brief Search

Often a higher level URL will allow searching or listing the results of the lower level entities. For example, /courses returns a list of courses, each accessible by its own URL (eg. /courses/1234). The /courses URL takes search parameters to limit the results returned.

Use the ‘q’ querystring parameter to limit your search. Expected parameter values are the field name (check each APIs documentation for a list of supported search fields) and the field value, separated by a tilde:

/almaws/v1/courses?apikey={apikey}&q=name~History

To search for a phrase, separate words with an underscore (or in some cases with “+” encoded as %2b ):

/almaws/v1/courses?apikey={apikey}&q=name~Introduction_to_Biology

The AND operator is supported for some of the APIs:

/almaws/v1/courses?apikey={apikey}&q=code~MED%20AND%20year~2015

and in some cases “*” is supported:

/almaws/v1/users?q=all~Joh*%2bSm*h

Brief Objects in Search Results

In most cases, we return a brief version of the object in the search results. This is especially true if the detailed object is large and resource-intensive. This approach improves the performance of the API and reduces the size of the response from the server. It also provides the calling application with greater flexibility, allowing it to populate a master screen quickly and make subsequent calls to fill in the details if required.

Order of fields in JSON objects

It should not be assumed that our REST APIs return attributes in any particular order. According to the JSON specification, JSON objects are unordered lists of properties and values. This means that developers should access properties by name and not count on a particular position within the JSON document. For example, it is possible that a user JSON object might be returned as either:

{
  "primary_id": 1234,
  "first_name": "Harold"
  "last_name": "Jones",
}

Or:

{
  "first_name": "Harold",
  "last_name": "Jones",
  "primary_id": 1234
}

Applications should access the property as user.first_name, such that the order of fields does not matter.

Leave a Reply