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 Orderline
s, 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:
- Retrieve the
Customer
record where the username isfoo@bar.nz
. - Retrieve any associated
Order
s, and embed anyOrderline
s that are associated with those orders. - Retrieve any
UserCustomerGroups
associations, along with the actualUserGroup
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">
).