Downloading documents via Excel VBA

Hi All,

I’m fairly new to this but i seem to be having issues. I’m trying to search and download a document using the Companies House API via Excel VBA.

Below is the code I’m using.

Sub DownloadStatementOfAffairs()
Dim companyNumber As String
Dim apiKey As String
Dim filingUrl As String
Dim downloadUrl As String
Dim jsonResponse As String
Dim documentID As String
Dim request As Object
Dim json As Object
Dim item As Object
Dim filename As String
Dim downloadRequest As Object
Dim downloadStream As Object
Dim descriptionText As String

' Set your API key here
apiKey = "Redacted"

' Get the company number from cell A2
companyNumber = ThisWorkbook.Sheets(1).Range("A2").Value

' API URL to get the filing history
filingUrl = "https://api.company-information.service.gov.uk/company/" & companyNumber & "/filing-history"

' Debug: Print the URL for filing history
Debug.Print "Filing URL: " & filingUrl

' Create the XML HTTP request to get filing history
Set request = CreateObject("MSXML2.XMLHTTP")
request.Open "GET", filingUrl, False
request.setRequestHeader "Authorization", "Basic " & Base64Encode(apiKey & ":")
request.send

' Parse JSON response
Set json = JsonConverter.ParseJson(request.responseText)

' Debugging output: Loop through items to see descriptions
Debug.Print "Filing history items for company " & companyNumber & ":"
For Each item In json("items")
    Debug.Print item("description")  ' Print each description in the Immediate Window
Next item

' Find the "Statement of Affairs" document ID

documentID = “”
For Each item In json(“items”)
descriptionText = LCase(item(“description”))
If InStr(1, descriptionText, “statement-of-affairs”, vbTextCompare) > 0 Then
documentID = Replace(item(“links”)(“document_metadata”), “https://document-api.company-information.service.gov.uk”, “”)
Exit For
End If
Next item

If documentID = “” Then
MsgBox “Statement of Affairs not found in filing history.”, vbExclamation
Exit Sub
End If

’ Construct the download URL for the document
downloadUrl = “https://document-api.company-information.service.gov.uk” & documentID & “/content”

’ Debug: Print the download URL
Debug.Print "Download URL: " & downloadUrl

' Create the XML HTTP request to download the document
Set downloadRequest = CreateObject("MSXML2.XMLHTTP")
downloadRequest.Open "GET", downloadUrl, False
downloadRequest.setRequestHeader "Authorization", "Basic " & Base64Encode(apiKey & ":")

' Debugging: Print Authorization Header
Debug.Print "Authorization Header: Basic " & Base64Encode(apiKey & ":")

' Error handling for the send request
On Error Resume Next
downloadRequest.send
If Err.Number <> 0 Then
    MsgBox "Error sending request: " & Err.Description, vbExclamation
    Exit Sub
End If
On Error GoTo 0

' Check if the document was found and downloaded
If downloadRequest.Status = 200 Then
    ' Save the document as a PDF
    filename = ThisWorkbook.Path & "\Statement_of_Affairs_" & companyNumber & ".pdf"
    Set downloadStream = CreateObject("ADODB.Stream")
    downloadStream.Type = 1 ' Binary
    downloadStream.Open
    downloadStream.Write downloadRequest.responseBody
    downloadStream.SaveToFile filename, 2 ' Overwrite
    downloadStream.Close
    
    MsgBox "Document downloaded successfully: " & filename, vbInformation
Else
    MsgBox "Failed to download the document.", vbExclamation
End If

End Sub

’ Helper function to encode API key in Base64
Function Base64Encode(text As String) As String
Dim arr() As Byte
arr = StrConv(text, vbFromUnicode)
Dim objXML As Object
Dim objNode As Object
Set objXML = CreateObject(“MSXML2.DOMDocument”)
Set objNode = objXML.createElement(“b64”)
objNode.DataType = “bin.base64”
objNode.nodeTypedValue = arr
Base64Encode = Replace(objNode.text, vbLf, “”)
End Function

When i run the macro i get the error “Error sending request: Accessed is denied” I’m pretty sure my API key is okay and i think i should have access. Could someone assist?

Cheers

Paul

Don’t have the time or current knowledge to go through your code but:

a) Are you getting a connection in the first place e.g. does your filing history bit work?
b) You’ll help yourself (a little…) if you note the http response codes (sorry can’t remember offhand the details of that in VBA)
c) The document downloading process is a bit tricky, and depends on how your http client implements following redirect links and sending http headers (and any options / defaults set). Specifically - does it send the http Basic Authorization header if it follows a redirect (http 302 redirect) and does it still do so if that link is to a different host name (site)?
Things like the curl command-line utility (recommended) can help you debug here - that can be set to just return the first 302 response from Companies House (with the link to the document - these are actually hosted on some AWS server) OR correctly follow the links (in this case - NOT sending your http Basic Authorization header to Amazon as that confuses their server because they already have an authorization implemented via the URL contents).

The following may help (some of these are more general VBA / Excel Companies House how-to’s):

Basic VBA login to CH issues:

Some Excel helpers (caveat - no connection to me, these are old and I have never personally tried them - make your own evaluation before use!)

Good luck.

1 Like

I use MS VBA for company document downloads, but not in Excel, so there may be a few differences. However, looking at this line in your code
downloadUrl = “https://document-api.company-information.service.gov.uk” & documentID & “/content”

If you break point the code at that line when running an example would the ‘downloadUrl’ value be in this format?
https://document-api.company-information.service.gov.uk/document/9l17auiJiMaV0_lvFsTE2fgmEPSl5OCVN3P7wtFLO4U/content

I used coy: 02500912 and on 31/03/2022 for the CONFIRMATION-STATEMENT

I’m also not sure that (apiKey & “:”) in this line is needed - just try Base64Encode(apiKey)
request.setRequestHeader “Authorization”, "Basic " & Base64Encode(apiKey & “:”)
I think the ‘:’ is for the streaming api key

this is my VBA;
'** Encrypt the API Key
encodedAPIkey = EncodeBase64(BASE_API_KEY)
With objhttp
.Open “GET”, APiBuildQry, False
.setRequestHeader “Authorization”, "Basic " & encodedAPIkey
.setRequestHeader “ContentType”, “.pdf”
.send

1 Like

Hi mH,

Thanks for your reply. I’m actually really new to all of this and i’ve been using AI to help me build it. I’m sure your amendments would work but i managed to get it working properly and so in that vein i’ve decided to paste it into this reply for anyone else that might want to use it. Currently, its search a column in excel for a company number and then retrieving the Statement of Affairs or Administrators Proposals of Companies in Liquidation or Administration (A very niche code i know lol)

Thank you for your reply though. I really appreciate it.

Sub DownloadStatementsForEachCompany()
Dim apiKey As String
Dim filingUrl As String
Dim documentID As String
Dim downloadUrl As String
Dim request As Object
Dim json As Object
Dim item As Object
Dim ws As Worksheet
Dim row As Long
Dim companyNumber As String
Dim companyName As String
Dim descriptionText As String
Dim documentFound As Boolean

' Set your API key here
apiKey = "API Key Here"

' Set the worksheet and starting row
Set ws = ThisWorkbook.Sheets(1)
row = 2  ' Start from row 2, assuming row 1 has headers

' Loop through each company number in Column A until an empty cell is reached
Do While ws.Cells(row, 1).Value <> ""
    ' Get the company number and name from the current row in Columns A and B
    companyNumber = ws.Cells(row, 1).Value
    companyName = ws.Cells(row, 2).Value
    Debug.Print "Processing company number: " & companyNumber & ", Company name: " & companyName
    
    ' API URL to get the filing history
    filingUrl = "https://api.company-information.service.gov.uk/company/" & companyNumber & "/filing-history"
    
    ' Debug: Print the URL for filing history
    Debug.Print "Filing URL: " & filingUrl
    
    ' Create the XML HTTP request to get filing history
    Set request = CreateObject("MSXML2.ServerXMLHTTP")
    request.Open "GET", filingUrl, False
    request.setRequestHeader "Authorization", "Basic " & Base64Encode(apiKey & ":")
    request.setTimeouts 5000, 5000, 5000, 5000  ' Set timeout to 5 seconds for each stage
    request.send
    
    ' Check and display HTTP status code for filing history request
    Debug.Print "Filing history request status for company " & companyNumber & ": " & request.Status
    If request.Status <> 200 Then
        MsgBox "Filing history request failed for company " & companyNumber & " with status: " & request.Status, vbExclamation
        row = row + 1
        GoTo NextCompany
    End If

    ' Print raw JSON response to Immediate Window to examine structure
    Debug.Print "Raw JSON response for company " & companyNumber & ": " & request.responseText

    ' Parse JSON response
    Set json = JsonConverter.ParseJson(request.responseText)
    
    ' Initialize flag
    documentFound = False
    
    ' Loop through the filing history to find documents with various matching descriptions
    For Each item In json("items")
        descriptionText = LCase(item("description"))
        
        ' Debug: Print each filing's description
        Debug.Print "Filing description for company " & companyNumber & ": " & descriptionText
        
        ' Check for "Statement of Affairs" variants
        If InStr(1, descriptionText, "liquidation-voluntary-statement-of-affairs", vbTextCompare) > 0 Or _
           InStr(1, descriptionText, "liquidation-in-administration-statement-of-affairs-with-form-attached", vbTextCompare) > 0 Then
           
            documentID = Replace(item("links")("document_metadata"), "https://document-api.company-information.service.gov.uk", "")
            downloadUrl = "https://document-api.company-information.service.gov.uk" & documentID & "/content"
            
            ' Attempt to download "Statement of Affairs" document
            Call DownloadDocument(downloadUrl, "Statement_of_Affairs_" & companyName, apiKey)
            
            documentFound = True
        End If
        
        ' Check for "Administrator's Proposal" variant
        If InStr(1, descriptionText, "liquidation-in-administration-proposals", vbTextCompare) > 0 Then
            documentID = Replace(item("links")("document_metadata"), "https://document-api.company-information.service.gov.uk", "")
            downloadUrl = "https://document-api.company-information.service.gov.uk" & documentID & "/content"
            
            ' Attempt to download "Administrator's Proposal" document
            Call DownloadDocument(downloadUrl, "Administrators_Proposal_" & companyName, apiKey)
            
            documentFound = True
        End If
    Next item

    ' If no documents were found, print debug message
    If Not documentFound Then
        Debug.Print "No relevant documents found for company: " & companyNumber
        MsgBox "Neither 'Statement of Affairs' nor 'Administrator's Proposal' found for company " & companyNumber, vbExclamation
    End If

NextCompany:
’ Move to the next row
row = row + 1
Loop
End Sub

’ Helper function to download a document given its URL and save it with a specified filename
Sub DownloadDocument(downloadUrl As String, filenamePrefix As String, apiKey As String)
Dim downloadRequest As Object
Dim downloadStream As Object
Dim filename As String

' Create the XML HTTP request to download the document
Set downloadRequest = CreateObject("MSXML2.ServerXMLHTTP")
downloadRequest.Open "GET", downloadUrl, False
downloadRequest.setRequestHeader "Authorization", "Basic " & Base64Encode(apiKey & ":")
downloadRequest.setTimeouts 5000, 5000, 5000, 5000  ' Set timeout for the download request

' Debugging: Print Authorization Header and Download URL
Debug.Print "Authorization Header: Basic " & Base64Encode(apiKey & ":")
Debug.Print "Download URL: " & downloadUrl

' Error handling for the send request
On Error Resume Next
downloadRequest.send
On Error GoTo 0

' Check if the document was found and downloaded
If downloadRequest.Status = 200 Then
    ' Save the document as a PDF with the filename prefix
    filename = ThisWorkbook.Path & "\" & filenamePrefix & ".pdf"
    Set downloadStream = CreateObject("ADODB.Stream")
    downloadStream.Type = 1 ' Binary
    downloadStream.Open
    downloadStream.Write downloadRequest.responseBody
    downloadStream.SaveToFile filename, 2 ' Overwrite
    downloadStream.Close
    
    MsgBox "Document downloaded successfully: " & filename, vbInformation
Else
    MsgBox "Failed to download document: " & filenamePrefix & " with status " & downloadRequest.Status, vbExclamation
End If

End Sub

’ Helper function to encode API key in Base64
Function Base64Encode(text As String) As String
Dim arr() As Byte
arr = StrConv(text, vbFromUnicode)
Dim objXML As Object
Dim objNode As Object
Set objXML = CreateObject(“MSXML2.DOMDocument”)
Set objNode = objXML.createElement(“b64”)
objNode.DataType = “bin.base64”
objNode.nodeTypedValue = arr
Base64Encode = Replace(objNode.text, vbLf, “”)
End Function

Thank you!

I actually managed to get this working and your response did help achieve this :slight_smile: