New endpoint for the API - No more SOAP!

Based on the feedback we’ve received from developers working with the API, the SOAP protocol (Simple Object Access Protocol) did prove to add a great deal of friction when trying to programmatically post a simple request to the API. Every post needed to be wrapped in a SOAP Envelope, not only did this add extra payload to the request and response stream it was also very unforgiving if the envelope did not precisely confirm to the spec.

Simple, cleaner API communication

Today we have added a new endpoint:

https://quickfile.co.uk/WebServices/API/invoices.ashx

You can now dispense with the SOAP envelope and just HTTP POST the xml (in the message body) directly to this URL. Here is a VB.NET example on how this can be coded in your application, examples in other languages will be posted shortly.

VB.NET Example

    Dim req As HttpWebRequest = WebRequest.Create("https://quickfile.co.uk/WebServices/API/invoices.ashx")
    req.Method = "POST"
    req.ContentType = "application/xml;charset=UTF-8"
    req.KeepAlive = False
    req.Accept = "application/xml;charset=UTF-8"
    Dim writer As New StreamWriter(req.GetRequestStream())
    writer.WriteLine("<Invoice_Get>....</Invoice_Get>") '<<<<<< INSERT YOUR XML HERE
    writer.Close()
    Dim rsp As WebResponse
    rsp = req.GetResponse()
    Dim sr As New StreamReader(rsp.GetResponseStream)
    Dim xmlStringResponse as string = sr.ReadToEnd

C# Example

HttpWebRequest req = WebRequest.Create("https://quickfile.co.uk/WebServices/API/invoices.ashx");
req.Method = "POST";
req.ContentType = "application/xml;charset=UTF-8";
req.KeepAlive = false;
req.Accept = "application/xml;charset=UTF-8";
StreamWriter writer = new StreamWriter(req.GetRequestStream());
writer.WriteLine("<Invoice_Get>....</Invoice_Get>"); //<<<<<< INSERT YOUR XML HERE
writer.Close();
WebResponse rsp = default(WebResponse);
rsp = req.GetResponse();
StreamReader sr = new StreamReader(rsp.GetResponseStream);
string xmlStringResponse = sr.ReadToEnd;
2 Likes

PHP Example

An example using cURL. This will simply show the first client from your account.

    $url = 'https://api.quickfile.co.uk/xml';
    $accountNo = '1234567890';
    $appID = 'abc-12345-def';
    $subNo = '12345678abcd';
    $apiKey = 'ABC-DEF-GHI';
    
    $data = '<Client_Search xmlns="http://www.QuickFile.co.uk" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.QuickFile.co.uk http://www.quickfile.co.uk/WebServices/API/Schemas/invoices/Client_Search.xsd">
      <Header>
        <MessageType>Request</MessageType>
        <TestMode>false</TestMode>
        <SubmissionNumber>'.$subNo.'</SubmissionNumber>
        <Authentication>
          <AccNumber>'.$accountNo.'</AccNumber>
          <MD5Value>'.md5($accountNo.$apiKey.$subNo).'</MD5Value>
    <ApplicationID>'.$appID.'</ApplicationID>
        </Authentication>
      </Header>
      <Body>
          <SearchParameters>
            <ReturnCount>1</ReturnCount>
            <Offset>0</Offset>
            <OrderResultsBy>CompanyName</OrderResultsBy>
            <OrderDirection>ASC</OrderDirection>
        </SearchParameters>
      </Body>
    </Client_Search>';
    
    $ch = curl_init($url);
    curl_setopt( $ch, CURLOPT_URL, $url );
    curl_setopt( $ch, CURLOPT_POST, true );
    curl_setopt( $ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml'));
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
    curl_setopt( $ch, CURLOPT_POSTFIELDS, $data );
    $result = curl_exec($ch);
    
    if($result === false) {
        echo 'Curl error: ' . curl_error($ch);
    } else {
        echo 'Curl successful!';
    }
    
    curl_close($ch);

Note: This is omitting the ‘https’ protocol, and using the ‘http’ protocol. While trying to use HTTPS, I ran into an error, and had to add this line to the cURL:

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
1 Like

Example in Groovy of creating an invoice using RESTClient from the HttpBuilder library.

@Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.7.1')
import groovyx.net.http.*
import static groovyx.net.http.ContentType.XML

def apiKey = '...'
def accountNumber = '...'
def appId = '...'

def http = new RESTClient("https://www.quickfile.co.uk/WebServices/API/invoices.ashx")

// generate a subscription number that's likely to be unique
def subno = System.currentTimeMillis()

// Authentication header - each time this is used it will increment the subno, so
// it is safe to use for several calls within the same script
def header = {
  def digest = java.security.MessageDigest.getInstance("MD5")
  digest.update((accountNumber + apiKey + subno).getBytes('UTF-8'))
  def md5Val = digest.digest().encodeHex()

  MessageType('Request')
  SubmissionNumber(subno++)
  Authentication {
    AccNumber(accountNumber)
    MD5Value(md5Val)
    ApplicationID(appId)
  }
}

// the lines we want to put in the invoice
def lines = [
  [ desc:'Bar of soap', unitCost:0.76, qty:2, vat:20 ],
  [ desc:'Tin of beans', unitCost:0.99, qty:3, vat:0 ],
]

def createResponse = http.post(contentType:XML, requestContentType:XML, body:{
  mkp.declareNamespace('':'http://www.QuickFile.co.uk') 
  mkp.declareNamespace('xsi':'http://www.w3.org/1999/XMLSchema-instance') 
  Invoice_Create('xsi:schemaLocation':'http://www.QuickFile.co.uk http://www.quickfile.co.uk/WebServices/API/Schemas/invoices/Invoice_Create.xsd') {
    Header(header) // uses the header calculation from above
    Body {
      InvoiceData {
        InvoiceType('INVOICE')
        ClientID('...')
        Currency('GBP')
        TermDays('2')
        Language('en')
        SingleInvoiceData {
          IssueDate('2016-10-29')
        }
        InvoiceLines {
          ItemLines {
            for(line in lines) {
              ItemLine {
                ItemID('0')
                ItemDescription(line.desc)
                Tax1 {
                  TaxName('VAT')
                  TaxPercentage(line.vat)
                }
                UnitCost(String.format('%.2f', line.unitCost))
                Qty(line.qty)
              }
            }
          }
        }
      }
    }
  }
})

if(createResponse.data.Error.size()) {
  println "Error creating invoice"
  createResponse.data.Error.each {
    println it.text()
  }
  System.exit(1)
}

def newInvoiceId = createResponse.data.Body.InvoiceID.text()
def newInvoiceNumber = createResponse.data.Body.InvoiceNumber.text()

println "Created invoice ${newInvoiceNumber}"


3 Likes

Python - Invoice_GetPDF

url = 'https://api.quickfile.co.uk/1_2/invoice/getpdf'
accountNo = '123456'
appID = 'ABC-123-XYZ'
apiKey = 'ACDR-98765-ZYXW'
submission_num = 5
invoice_id = 123456789

import json
import requests
from hashlib import md5

encode_md5=(accountNo + apiKey + str(submission_num).zfill(8)).encode('utf-8')
auth={"AccNumber": accountNo, "MD5Value": md5(encode_md5).hexdigest(), "ApplicationID": appID}
header={"MessageType":"Request", "SubmissionNumber": str(submission_num).zfill(8),"Authentication":auth}
payload={"payload":{"Header":header, "Body":{"InvoiceID":invoice_id}}}
#print(json.dumps(payload,indent=4)) #error tracing

resp = requests.post(url, json=payload)
#print(format(resp.json())) #error tracing

if "Invoice_GetPDF" in resp.json():
    print(format(resp.json()["Invoice_GetPDF"]["Body"]["InvoiceDetails"]["PDFUri"]))
elif "Errors" in resp.json():
    for error in resp.json()["Errors"]:
        print(resp.json()["Errors"][error])
3 Likes

I have successfully integrated Quickfile API into n8n

N8n Integration: Using HTTP Request node

but it gave me grief due to the “payload”.

Falling foul to

  • Cons:
    • Increase in verbosity and complexity for the client.
    • Potentially unnecessary overhead in simpler use cases.

My question is …

Why add a “payload” wrapper around the Header and Body in the API request

  • In RESTful APIs, this extra nesting is less common, as REST prioritizes simplicity.
  • In SOAP APIs, which are highly structured, a wrapper equivalent to payload (like <Envelope>) is standard.

Why Include a “Payload” Wrapper?

  1. Encapsulation for Consistency:
  • The payload acts as a single container for all the message content. This can make it easier for the server to parse the request, especially if the API uses a standardized format for all message types.
  • Encapsulation ensures that additional top-level properties (e.g., metadata, versioning) can be added outside the payload without interfering with the structure of the core data.
  1. Versioning and Extensibility:
  • Wrapping Header and Body under payload provides flexibility for future versions. If the API evolves, the developer could add properties outside payload (e.g., timestamp, signature) without affecting the structure inside it.
  1. Standardization Across Different APIs:
  • Some APIs are designed with uniform data structures, especially when multiple endpoints or services are involved. Wrapping Header and Body in payload ensures that all requests follow a similar structure, simplifying implementation across different teams or systems.
  1. Compatibility with Middleware:
  • Middleware systems or gateways that process API requests might expect a consistent top-level key (like payload) for easier parsing and routing.

Is This Common?

This practice isn’t uncommon but depends on the API design philosophy. Some systems prioritize:

  • Minimalism: Avoid unnecessary nesting for simplicity and clarity.
  • Structure and Standardization: Use wrappers like payload to support extensibility and uniformity.

For instance:

  • In RESTful APIs, this extra nesting is less common, as REST prioritizes simplicity.
  • In SOAP APIs, which are highly structured, a wrapper equivalent to payload (like <Envelope>) is standard.

Is It Necessary?

From a purely functional perspective, you’re correct:

  • The Header and Body together already constitute the “payload.”
  • Adding the payload wrapper introduces an additional nesting level that may seem redundant and increases verbosity.

However, the decision often comes down to design preferences or specific requirements of the API ecosystem.

Trade-offs:

  • Pros:
    • Adds structure and flexibility for future changes.
    • Consistent schema for parsing and validation tools.
    • Easier integration with middleware or multi-service platforms.
  • Cons:
    • Increases verbosity and complexity for the client.
    • Potentially unnecessary overhead in simpler use cases.

Conclusion:

While it’s not strictly necessary, using a payload wrapper can reflect a deliberate design choice for extensibility and uniformity. Whether it’s “unnecessary” depends on the specific goals and standards of the API.

So it’s less of a question that needs a fix but more about getting an idea of the general design philosophy chosen.