Extending REST with Resource Specifications

REST offers API users with the ability to send an http request to query or modify a particular resource. However, one of the big downsides of REST is that in particular situations it can be very - even prohibitively inefficient. If a resource doesn't include all the data you require in its default response, you are forced to make multiple requests to the API and then manually reassemble the data to get what you need.

This can result in servers being spammed with requests, request limits being hit and sites slowing down to a crawl.

GraphQL was invented in part to tackle this problem and is implemented by many modern APIs. It is complicated to learn, but provides a lot of power in both retrieving and modifying data from an API. Unfortunately our Data API was created long before GraphQL came into existance - so we had to come up with a solution of our own. That solution is what we call "resource specifications".

Related resources

To construct a resource specification, you need to have a basic understanding of what resources are related so you can chain relevant ones together. The API documents itself in this regard - see the API discoverability documentation.

What is a resource specification

The Data API allows you to join resources into a single request by defining a "resource specification", allowing you to define related resources and operate much like an SQL outer join would. It allows you to specify related resources you would like to access in the same request by chaining them on with a + symbol.

Key attributes of a resource specification are:

  • The first resource in the specification is the "primary resource". This is what any search terms or specified key will apply to.
  • Any additional resources joined with a + symbol will be added into the provided result set.
  • Any invalid resources (misspelt or where no relationship exists between the resources) will be ignored.

So Product+Inventory+Merchandising+ProductAttributes says: "please provide a product, along with any related inventory, merchandising and product attribute records".

When retrieving data from the API using a resource specification, any related records to the primary resource being retrieved will be embedded in rimary resource node. So in this case, if related records exist the Product node representing a product will have an Inventory, Merchandising and one or more ProductAttribute nodes.

The above specification may retrieve a result that looks like this (attributes truncated for readability)

<ResultSet ...>
    <Product href="..." id="...">
        <sku>foo-sku</sku>
        <title>Foo product title</title>
        <!-- etc -->
        <Inventory ...>
            <quantity>2</quantity>
            <sku>foo-sku</sku>
        </Inventory>
        <Merchandising ...>
            <discount>40%</discount>
            <!-- etc -->
        </Merchandising>
        <ProductAttribute ...>
            <label>Material</label>
            <value>Aluminium</value>
            <!-- etc -->
        </ProductAttribute>
        <ProductAttribute ...>
            <label>Diameter</label>
            <value>20mm</value>
            <!-- etc -->
        </ProductAttribute>
    </Product>
</ResultSet>

Nesting resource specifications

Resource specifications can be extended further to include resources that are related to ones you have chained in - allowing you to nest resource specifications. To achieve this you wrap the nested resource specification in brackets.

For example if you wanted to retrieve an Order, the Customer who placed the order, any related Orderlines, and any OrderlineTax records relating to the orderlines - you could construct the following specification: Order+Customer+(Orderline+OrderlineTax). The brackets indicate that the OrderlineTax relationship is through the Orderline - not the Order.

In this instance, any OrderlineTax nodes will be nested inside their related Orderline nodes.

So let's examine a complex GET request to:

https://www.clientwebsite.nz/API/V3/Customer+(Order+Orderline)+(UserCustomerGroups+UserGroups)/foo@bar.nz

This will:

  1. Retrieve the Customer record where the username is foo@bar.nz.
  2. Retrieve any associated Orders, and embed any Orderlines that are associated with those orders.
  3. Retrieve any UserCustomerGroups associations, along with the actual UserGroup data (e.g. so we can get its title rather than its code).

This request may return the following XML result (truncated for brevity):

<ResultSet ...>
    <Customer href="..." id="...">
        <username>foo@bar.nz</username>
        <!-- etc -->
        <Order ...>
            <order_number>ORD00512</code>
            <username>foo@bar.nz</username>
            <!-- etc -->
            <Orderline ...>
                <code>ORD00512-1</code>
                <sku>06612</sku>
                <quantity>2</quantity>
                <!-- etc -->
            </Orderline>
            <Orderline ...>
                <code>ORD00512-2</code>
                <sku>46612</sku>
                <quantity>1</quantity>
                <!-- etc -->
            </Orderline>
        </Order>
        <UserCustomerGroup ...>
            <group_code>dealer</group_code>
            <!-- etc -->
            <UserGroup ...>
                <code>dealer</code>
                <title>Dealer</title>
                <!-- etc -->
            </UserGroup>
        </UserCustomerGroup>
        <!-- etc -->
    </Customer>
</ResultSet>

Writing to a resource specification

You can also POST back to a resource specification, allowing you to create / update multiple nested records in a single request. To do this, simply POST back XML in the same format that you would receive it if you requested a specific record using a GET request.

For example:

POST https://www.clientwebsite.nz/API/V3/Products+Inventory/

XML Body:

<?xml version="1.0"?>
<ResultSet>
  <Product>
    <Inventory>
      <quantity>55</quantity>
      <sku>NEW1</sku>
      <stock_message>New Stock Now!</stock_message>
    </Inventory>
    <advanced_pricing>false</advanced_pricing>
    <comment>New Comment 1</comment>
    <description>New Description 1!</description>
    <sku>NEW1</sku>
    <!-- etc -->
  </Product>
  <Product>
    <Inventory>
      <quantity>55</quantity>
      <sku>NEW2</sku>
    </Inventory>
    <advanced_pricing>false</advanced_pricing>
    <comment>New Comment 2</comment>
    <description>New Description 2!</description>
    <image>new_image2.jpg</image>
    <sku>NEW2</sku>
    <!-- etc -->
  </Product>
</ResultSet>

One important point is only the fields you wish to set need be defined.

Any passed inline attributes in the XML will be ignored (e.g. <Product href="..." attribute2="abc">).