API and File Attachments

Hi Guys,

We have a few automation systems running and wondering how I can insert an attachment to a purchase invoice or a sales invoice when saving via API. And is it base64 Encoded or binary.

1 Like

You can’t as far as I know because I don’t think attachment files are accessible.

But I would definitely like to know.

This would definitely be a useful feature to consider as clients could attach their purchase orders for example.

Hi @bdallen, so yes it is possible to add an attachment to a Sales invoice via Automation (notice I didn’t use the “api” word)

The real issue is that once it’s added it not that easy to get it back again by simply requesting a pdf version of the invoice for example. (attachments are not added to pdf)

Some assumptions made and info required:
a. assumption is you have an Admin user setup for All access (e.g. adminusr:adminPassword)
b. You have HTTPS access to your ServerManager
c. My internal test site is: https://dev.miotest.local/ behind an nginX config.

Here is a basic overview on how you achieve it.

  1. Find the UUID of the Sales Invoice to add attachments to. (e.g. 260e4d4a-d83d-444a-a86d-4d42b569b720)
  2. Make file to upload available locally.
  3. Using Curl with the following command set works:

curl -v --user "adminusr:adminPassword" -H "Content-Type: application/octet-stream" -H "X-File-Name: MyTestFile.jpg" -H "X-File-Type: image/jpeg" --data-binary @simage.jpg "https://dev.miotest.local/new-attachment?Source=260e4d4a-d83d-444a-a86d-4d42b569b720&FileID=Tm9ydGh3aW5kQVBJ"

The response will be some JSON with the UUID of the attachment:

{"key":"362a78f3-61fa-466f-b122-612328aa27b3","view":"/view-attachment?Key=362a78f3-61fa-466f-b122-612328aa27b3&FileID=Tm9ydGh3aW5kQVBJ","delete":"/remove-attachment?Key=362a78f3-61fa-466f-b122-612328aa27b3&FileID=Tm9ydGh3aW5kQVBJ"}

To find “details” about the object, you can use the "/api/FileID/2e541a82-94d7-42fc-a388-26bdc0803455/index.json" fixed Attachments endpoint or directly via: "/api/FileID/362a78f3-61fa-466f-b122-612328aa27b3.json" - the object endpoint.

2 Likes

Thanks Mark, I shall give this a shot.

1 Like

Works like a charm. Using Python.

# Upload Attachment
def UploadAttachment(self, attachment_name, attachment_data):
    if '.pdf' in attachment_name:
        bheaders = {'Content-Type': 'application/octet-stream', 'X-File-Name': attachment_name, 'X-File-Type': 'application/pdf'}
    if '.jpg' in attachment_name:
        bheaders = {'Content-Type': 'application/octet-stream', 'X-File-Name': attachment_name, 'X-File-Type': 'image/jpeg'}
    else:
        raise('Unknown File Type for Upload please use jpg or pdf')

    payload_bytes = attachment_data.encode('ascii')
    payload = base64.decodebytes(payload_bytes)
    resp = requests.post(self.__application.Service_URL + '/new-attachment?Source=' + self.id + '&FileID=' + self.bus_id, data=payload, headers=bheaders, auth=(self.__application.API_Username, self.__application.API_Password))
    reply = resp.status_code
    if reply != 200:
        raise('Error Uploading Attachment to Manager')
3 Likes

Thanks for the contribution Brendon!

Just for anyone that may be leaning towards php, here is a PHP version that runs using the Laravel Framework Artisan Command that can be added to the command.php routes file. (e.g. php artisan api:attach)

<?php
use GuzzleHttp\Client;

Artisan::command('api:attach  {fileId : The unique ID of the Company File}  {uuid : UUID for the Invoice, Quote etc that you want the attachment added to}  {file : Full path to the locally hosted resource to be uploaded}', function($fileId, $uuid, $file) {

$this->info('Wait... Attempting to upload an attachment...');
// Make sure file exists...
if (!file_exists($file)) {
    $this->error('Error! Unable to find the file specified ('.$file.')');
    return;
}
$config   = config('managerapi');
$host     =  $config['host']         ?? null;
$port     =  $config['http_port']    ?? null;
$user     =  $config['username']     ?? null;
$password =  $config['password']     ?? null;
$hostName = $host . ($port ? ':'.$port : '');
$this->info('Using Host Name ' . $hostName);

// open the file read only
$fHandle = fopen($file, "r");
// Create Guzzle Client options
$opts = [
    'auth' => [$user, $password],
    'body' => $fHandle,
    'headers' => [
        'Content-Type' => 'application/octet-stream',
        'X-File-Name' => basename($file),
        'X-File-Type' => mime_content_type($file),
]];

// Create a new Client
$client = new Client();
$response = $client->request('POST',$hostName . '/new-attachment?Source='.$uuid.'&FileID='. $fileId, $opts);
// Make sure it worked ok
if (200 == $response->getStatusCode()) {
    $result = json_decode((string) $response->getBody(),true);
    // Output results in a table
    $this->table(['Name','Data'], [['key', $result['key']],['view', $result['view']],['delete', $result['delete']]]);
    return;
} else {
    $this->error('Unknown error occured');
    dd($response);
}
})->describe('Upload a file attachment, pdf, doc, image etc. to an Invoice, Quote and other forms');

If successful the UUID and other links are output as in…
php artisan api:attach Tm9ydGh3aW5kQVBJ "d26b982b-8a51-4218-af06-f1d5080255fe" \Images\metoo.jpg

Should produce the following results:

Wait... Attempting to upload an attachment...
Using Host Name https://xxx.xxx.local
+--------+-------------------------------------------------------------------------------------+
| Name   | Data                                                                                |
+--------+-------------------------------------------------------------------------------------+
| key    | 83c1910b-4f83-43f4-8182-b4e5b887b621                                                |
| view   | /view-attachment?Key=83c1910b-4f83-43f4-8182-b4e5b887b621&FileID=Tm9ydGh3aW5kQVBJ   |
| delete | /remove-attachment?Key=83c1910b-4f83-43f4-8182-b4e5b887b621&FileID=Tm9ydGh3aW5kQVBJ |
+--------+-------------------------------------------------------------------------------------+
1 Like