We run a VuFind based service called Sabre. You can read the background to it here, but essentially it is a catalogue for searching the bibliographic holdings of the University of Sussex, the University of Brighton and the local NHS libraries providing a key service to our joint BSMS Medical School students and staff who have access to these libraries.

VuFind is an open source based library catalogue software service which sits on top of the a SOLR index.

When we set up sabre the code was modified to adapt it to working with, at the time, two different library systems (the NHS was added a couple of years later). The key change was how the system made a real time check of an item’s availability and status, this needed to know which library, or libraries, had the item in question and make the request to their system, rather than just connecting to one system for every record. Much of this was adapted from the University of Swansea who had, coincidentally, done something similar at the time.

Due to the internal changes we have made we have stayed on the same version of VuFind, rather than upgrading to new releases. While the work to re-implement this into a VuFind version 2 codebase should be achievable, it’s something we have never allocated time to do.

In July the University of Sussex moved to Alma. There was no Alma driver for VuFind (VuFind has a number of ‘out the box’ drivers for different originating LMS/ILS though no one using VuFind had yet needed to create one for Alma). However Alma does come with a number of APIs which should make creating such a thing possible.

You can see the specification for the required data here.

One surprise is that though Alma has many APIs around bibliographic records, it has no one api for retrieving a list of items, along with their location/type, classmark, loan status and due date if on loan. It’s common in libraries to have a MARC based bibliographic record to describe a work, and a separate list of items; their loan rules, shelfmark and availability, provided separately so it is common for systems to provide a simple api call for this information, and I would say it is a fairly common use case, for example our reading list system also needs to retrieve this information.

Having said this, the information is available, just not in one API call, as far as I can tell.

My method is as follows. To start with we have the unique control number / bib id for this record, which Alma calls an MMS. With this we can get a list of Holdings IDs for the record. These are not items, they are locations (locations define rules such as loan length as well as specifying which building / section it is in), and each location contains items.

GET /​almaws/​v1/​bibs/​{mms_id}/​holdings (I suspect that url will not work, so try this)

The response looks a little like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<holdings total_record_count="2">
  <holding link="https://api-na.hosted.exlibrisgroup.com/almaws/v1/bibs/9963106302461/holdings/2283709200002461">
    <library desc="Library">USL</library>
    <location desc="Short">SHORT</location>
    <call_number>BJ 53 BLO</call_number>
  <holding link="https://api-na.hosted.exlibrisgroup.com/almaws/v1/bibs/9963106302461/holdings/2283708100002461">
    <library desc="Library">USL</library>
    <location desc="Core">CORE</location>
    <call_number>BJ 53 BLO</call_number>
  <bib_data link="https://api-na.hosted.exlibrisgroup.com/almaws/v1/bibs/9963106302461">
    <title>Corporate responsibility /</title>
    <author>Blowfield, Mick.</author>
    <isbn>019958107X (paperback)</isbn>
    <complete_edition>Second edition.</complete_edition>
    <place_of_publication>Oxford :</place_of_publication>
    <publisher>Oxford University Press</publisher>
      <network_number>(DLC)  2011420565</network_number>

From this we have two holdings for this MMS, and we have the IDs for both.

We can now call an api which lists all items for a holdings.

GET /almaws/v1/bibs/{mms_id}/holdings/{holding_id}/items

This returns everything you could ever want to know about each item. Except its due date. We need to call this for each Holdings, and obviously we can only make these calls once the first api had returned it’s response.

Finally, we can get any current loans for the record, which includes the due date for each. We can map the item id returned here with those returned above.

GET /almaws/v1/bibs/{mms_id}/loans

So that gets what we need. However this method requires at least three API calls. For a record with multiple copies in multiple locations/libraries this could be much more.

One way to alleviate this was to use curl_multi which can make a number of curl requests at once. This improved the speed of my code by quite a bit. I did do some comparison tests which annoyingly I don’t have to hand as I write but it was in the order of seconds. This isn’t an ideal solution. It calls the first ‘get holdings’ call and the current loans call at the same time, once they have both been returned it has resumed. Better would be for the script to start calling the api for the list of items for each holdings api as soon as the holdings list has been returned. Instead the current loans api may hold it up. I guess the correct solution would involve some sort of threads to handle the two sides of this (one side, get holdings list, and then items for each holdings, the other side, get current loans) and then once each of these has finished the results can be merged and returned.

In any case, I’m sharing the code here, it may be of use to the VuFind community, and to anyone else wanting item availability for Alma. You can also see the script working (at the time of writing) here.

protected function curlMultiRequest($urls) {
$ch = array();
$results = array();
$mh = curl_multi_init();
foreach($urls as $key => $val) {
$ch[$key] = curl_init();
curl_setopt($ch[$key], CURLOPT_URL, $val);
//curl_setopt ($ch[$key], CURLOPT_FAILONERROR,1);
// follow line enables https
curl_setopt($ch[$key], CURLOPT_SSL_VERIFYPEER, false);
curl_multi_add_handle($mh, $ch[$key]);
$running = null;
do {
$status = curl_multi_exec($mh, $running);
$info = curl_multi_info_read($mh);
if ( $info['result'] != 0 ) {
while ($running > 0);
// Get content and remove handles.
foreach ($ch as $key => $val) {
$results[$key] = curl_multi_getcontent($val);
curl_multi_remove_handle($mh, $val);
return $results;
public function getHolding($id) {
$mmsid = $id;
$apikey = urlencode('xxxxxxxxxxxxxx');
$baseurl = 'https://api-na.hosted.exlibrisgroup.com/almaws/v1/bibs/';
$holdinglist = array(); // for holding the holdings
$items = array(); // what we will return
$urllist = array();
// prepare loans url
$paramurl = '?apikey=' . $apikey;
$urllist['loans'] = $baseurl . urlencode($mmsid) . '/loans' . $paramurl;
// prepare holdings url
$suffixurl = '/holdings?apikey=' . $apikey;
$urllist['holdings'] = $baseurl . urlencode($mmsid) . $suffixurl;
// get loans and holdings from api
// returns an array, keys are loans/holdings.
// values are the xml responses
$results2 = $this->curlMultiRequest($urllist);
$loaninfo = array();
// loans xml
$record = new SimpleXMLElement($results2["loans"]);
// process loans xml creating array with barcode as key
$loans = array();
// if no loans there will be no item_loan
if ($record) {
foreach ($record->item_loan as $itemloan) {
$duedate = $itemloan->due_date;
$barcode = $itemloan->item_barcode;
$loans["$barcode"] = $duedate;
// now get each holdings id number
$record = new SimpleXMLElement($results2["holdings"]);
$holdinglist = array();
$datafields = $record->xpath('/holdings/holding/holding_id');
foreach ($datafields as $hid) {
$holdinglist[] = $hid;
// for each holdings, prepare urls
// GET /almaws/v1/bibs/{mms_id}/holdings/{holding_id}/items
$urllist = array();
$paramurl = '/items?apikey=' . $apikey . '&limit=90';
foreach ($holdinglist as $hid) {
$holdingsurl = '/holdings/' . urlencode($hid);
$urllist["$hid"] = $baseurl . urlencode($mmsid) . $holdingsurl . $paramurl;
// call each of the holdings urls at the same time
$results3 = $this->curlMultiRequest($urllist);
// for each holding id, get its items
$items = array();
foreach ($holdinglist as $hid) {
//create xml object based on this holdings xml
$record = new SimpleXMLElement($results3["$hid"]);
$datafields = $record->xpath('/items/item');
// for each item within this holding
foreach ($datafields as $item) {
$duedate = "";
$barcode = (string) $item->item_data->barcode;
$items["$count"] = array();
$items["$count"]["id"] = $mmsid;
$items["$count"]["barcode"] = $barcode;
$items["$count"]["number"] = $count;
$items["$count"]["item_id"] = $barcode;
$items["$count"]["availability"] = (string) $item->item_data->base_status;
if ($items["$count"]["availability"]) {
$items["$count"]["status"] = "Available";
} else {
$items["$count"]["status"] = "Not Available";
$items["$count"]["location"] = $item->item_data->library['desc'] . " – " . (string) $item->item_data->location['desc'];
$items["$count"]["callnumber"] = (string) $item->holding_data->call_number;
$items["$count"]["reserve"] = "0";
if (isset($loans["$barcode"])) {
$duedate = (string) $loans["$barcode"];
if ($duedate) {
// ISO 8601
$items["$count"]["duedate"] = date('l jS F Y', strtotime($duedate));
return $items;

view raw
hosted with ❤ by GitHub




One Comment

Leave a Reply