Live, fluid RESTing with the Tradeshift API and Groovy (part 2)

This is part 2 of a series of blog posts on how to work with the Tradeshift API interactively, and deals with documents. Please make sure your also check out part 1 on connections.

Uploading an invoice

Tradeshift uses the OASIS UBL standard for document exchange, which defines XML document types for business documents like invoices, purchase orders, and others. Tradeshift uses document profiles to designate use of a particular document type (e.g. invoice) and conventions (e.g. Danish EasyTrade invoices). A document on Tradeshift is always associated with a document profile.

In order to upload an invoice, it has to adhere to a document profile. The easiest way to get started is to base our invoice on the example invoices that Tradeshift provides for each document profile. For this blog, we'll take as our document profile; please refer to our API documentation for a full list.

Let's get the sample invoice, and feed it into Groovy's XMLSlurper in order to interact with the XML:
groovy:000> docProfile = ''
groovy:000> invoice = new XmlSlurper().parse("${ts.uri}info/documentprofiles/${docProfile}/sample")
(note: the contents of the invoice variable looks a bit strange, because the toString() method of this Groovy object will just append all text nodes of the XML tree. It is in fact a proper XML document.)

Let's give the invoice a random document ID (to make it unique), and convert it to a String so we can send it on its way:
groovy:000> invoice.ID = new java.text.DecimalFormat("00000000").format(new Random().nextInt(99999999))
===> 02158931
groovy:000> body = new groovy.xml.StreamingMarkupBuilder().bind { mkp.xmlDeclaration(); mkp.yield invoice } as String
===> <?xml version='1.0'?>
<tag0:Invoice xmlns:tag0='urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'> .....
The first statement simply replaces the text content of the tag named ID with the new contents. The second statement converts the XMLSlurper invoice to a String.

Copy the full output of the second statement into your favourite IDE and auto-format it to read the result. It should look something like this. We now have a valid XML document that adheres to the document profile we started out with. Let's upload it to Tradeshift.
groovy:000> docid = UUID.randomUUID()
===> f592af8e-7855-4bd2-bce9-7867c2b04c12
groovy:000> doc = ts.put(path: "documents/${docid}", query:[documentProfileId:docProfile, draft:'true'], headers:['Content-Type':'text/xml'], body: body)
groovy:000> doc.statusLine
===> HTTP/1.1 204 No Content
We generate a random UUID for the document, and PUT it into tradeshift as a draft. Tradeshift will return a 204 status code on a successful document upload. Note that the UUID of the document is just the way by which the document can be retrieved using HTTP: it is unrelated to the "business" document ID (invoice.ID), although both must be unique for dispatched documents. Only the "business" document ID will appear on the actual invoice.

Let's GET our document back to confirm that it was stored successfully, and check it's metadata:
groovy:000> invoice = ts.get(path: "documents/${docid}", contentType:ContentType.XML).data
===> .....
groovy:000> ts.get(path: "documents/${docid}/metadata").data
===> {"DocumentId":"f592af8e-7855-4bd2-bce9-7867c2b04c12","DocumentType":
For all new documents uploaded this way, a conversation is created along with the document. A conversation is the stream of a document and all related communication like comments, credit notes for invoices, invoices for purchase orders, etc.

Finding the connection for an uploaded draft

Now that we have uploaded a new draft, please login to the Tradeshift account that you've uploaded it into. There should be a new draft in your document page, with the content and ID that we've just put in. Note also that an external (manual) connection has been created automatically, for the recipient of the invoice (this happens only for uploaded drafts). You'll find it in your network named as "Tradeshift" with a fictional address and GLN.

Let's use the Tradeshift /network/suggest API to find the connection belonging to our new invoice, based on the invoice customer's GLN (which we can easily retrieve with a Groovy expression).
groovy:000> gln = invoice.AccountingCustomerParty.Party.PartyIdentification.text()
===> 1234567890123
groovy:000> ts.get(path: 'network/suggest', query:[identifierScheme: 'GLN',identifierValue: gln]).data
===> {"numPages":null,"pageId":null,"itemsPerPage":1,"itemCount":1,"Connection":[
groovy:000> connectionid = ts.get(path: 'network/suggest', query:[identifierScheme: 'GLN',identifierValue: gln]).data.Connection[0].ConnectionId
===> 5f4843b9-5969-443a-9442-fb2bf1f67d95

Sending a document

Sending an uploaded document is done by creating a dispatch, which represents the action of sending. In order to send our newly uploaded invoice along the external connection that was automatically created for it, all we need to do is PUT a dispatch to that connection.
groovy:000> dispatchid = UUID.randomUUID()
===> d05ebb98-525a-4775-a803-fb78f8c23f01
groovy:000> disp = ts.put(path: "documents/${docid}/dispatches/${dispatchid}", body: [ConnectionId: connectionid])
groovy:000> disp.statusLine
===> HTTP/1.1 201 Created
groovy:000> disp.headers.Location
All we need to fill in for a new Dispatch resource, is the connection that the dispatch is to. The Location response header is telling us that the new Dispatch can be retreived on the /latest URL. Let's try that (we'll need to use https instead of http though).
groovy:000> ts.get(path: "documents/${docid}/dispatches/latest").data
===> {"DispatchId":"d05ebb98-525a-4775-a803-fb78f8c23f01",
The Dispatch shows as completed. Now, let's login to our Tradeshift account from the web again. Since we've now dispatch this draft invoice, the document should now show up under the Sales tab in the documents view.

Where to go from here

After having worked through these examples, please do try out some of the other Tradeshift API calls, and let us know if you have any suggestions for this blog or other topics to cover!