Integrations (Developer overview)

Last updated: June 10, 2025

This document outlines the options for vendors seeking to either communicate data from their platform to Aimsio (Inbound Data) or receive data from Aimsio (Outbound Data).

Table of Contents

Using Aimsio API

If you want to utilize our in-house API, refer to our documentation at api.aimsio.com

Develop External adapters

Push vs Pull

There are generally two ways to communicate a change from ERP to Aimsio. Some modern ERPs have the ability to push a change immediately to other systems (in this case, Aimsio), known as Push/Post. On the other hand, Aimsio can pull the data at known intervals once a day or whenever the end-user triggers an import by going to the Integration menu in Aimsio. This is known as Pull. Both strategies are being used in different systems.

When considering pushing data to Aimsio, one needs to take into account error handling. For example, how can a user know with certainty if their change has gone through from their ERP to Aimsio without going to Aimsio, opening the changed record and double-checking manually? Does the ERP have a log of errors to review?

In contrast, when you trigger a pull in Aimsio, we expect the other system to return all the data (e.g. all the employees). If there is an error in some records, there is a place in Aimsio where you can check all the errors, then you can fix them in the ERP and import again to verify all the errors are gone.

Adapter specification

Authentication

An authentication method will need to be established with the integration adapter. 

Currently, we support Basic Authentication and SSL to secure web service calls. Other options are:

Date encoding

Dates are encoded as UTC epoch milliseconds https://en.wikipedia.org/wiki/Unix_time

Pagination

We currently do not support pagination of results.

Errors

Errors can be returned through a metadata field in the JSON response.

{ “meta”: { “error”: { “errorCode”: "", //ivw.response.ok, ivw.request.validation.exception, ivw.request.parse.exception, ivw.request.application_error “errorMessage”: "", “errorReference”: "", } }, “data”: {} }

Enriched data

The adapter may need to provide custom data that Aimsio should process during the import, rather than mapping directly to data dimensions in the Aimsio system.

Inbound Data

The chart below shows the communication architecture between Aimsio and another system in case of pulling data. An Integration Adapter should implement the specified API web-service endpoints, which Aimsio will call. Modern ERPs (e.g Microsoft Dynamics NAV or NetSuite) already allow the end-users to publish a set of web services with any custom mapping. For other ERPs without the inherent web-service functionality (e.g. ViewPoint), we normally ask the client’s ERP consultant to implement the web-service endpoints, as they are the most familiar with the ERP and configuration specific to the client.

On the Aimsio side, we have implemented the flexibility to allow further modifying the data returned by the Integration Adapter, in case more complex data mapping is required. This way, the two systems (Aimsio and ERP) can meet in the middle by converting data back and forth through the Integration Adapter.

Untitled-20250214-235510.jpg

If Aimsio is querying data from an external adapter, we support pulling for the following entities.

Billable Items

Adapter Endpoint

Method

Path

Produces

GET

/jobs

application/json

Sample Response

{ “billableItemList”: [ { “billableItemIsEquipment”: true, “billableItemTypeUname”: “equipment”, “billableItemDescription”: "", “billableItemCode”: “WELD_TRK”, “billableItemIsTrade”: false, “billableItemName”: “Welder Truck”, “billableItemDefaultRateTypeUname”: “hourly”, “billableItemDefaultRateTypeUname2”: “daily”, “billableItemDefaultRateTypeUname3”: null, “billableItemDefaultRate”: “80.00”, “billableItemDefaultRate2”: “900.00”, “billableItemDefaultRate3”: null, “billableItemCategory”: null, “customFieldDataList”: [ // Omitted ], “extraDataList”: [ // Omitted ] } ] }

Important Notes:

  1. billableItemList can be replaced with keyword value to work with the OData standard.

Price Books

Adapter Endpoint

Method

Path

Produces

GET

/price_books

application/json

Sample Response

{ “priceBookList”: [ { “priceBookName”: “ABC Price Book”, “priceBookIsDeleted”: false, “priceBookOriginReference”: null, “priceItemList”: [ { “priceItemBillableItemCode”: “WELD_TRK”, “priceItemRateTypeUname”: “hourly”, “priceItemRateTypeUname2”: “daily”, “priceItemRate”: “95.00”, “priceItemRate2”: “1100.00”, “priceItemRate3”: null, “priceItemRateTypeUname3”: null, “customFieldDataList”: [ // Omitted ], “extraDataList”: [ // Omitted ] } ] } ] }

Important Notes:

  1. priceBookList can be replaced with keyword value to work with the OData standard.

  2. priceBookName or priceBookGuid is a unique/required field with which a price book is identified.

  3. priceItemList is an array of billable items with their rates in this price book.

    1. Each billable item is referenced by its code, e.g WELD_TRK in the above example

    2. This array is immutable, i.e. previous items under this price book will be deleted if they don’t exist in the array.

  4. priceItemAction determines whether priceItemList is all inclusive or not.

    1. DEFAULT: If the value is ADD_OR_UPDATE, any items in the price book that is not in priceItemList will be deleted.

    2. If the value is REPLACE, any items in the price book that is not in priceItemList will be deleted.

Companies

Adapter Endpoint

Method

Path

Produces

GET

/companies

application/json

Sample Response

{ “companyList”: [ { “companyAddress”: “12345 - 123 9th Ave SW, Calgary, AB, T2P 0Z1”, “companyCode”: “AIMSI”, “companyName”: “Aimsio Inc.”, “companyPhone”: “403-123-4567”, “companyTypeUname”: “client”, “extraDataList”: [ ] } ] }

Important Notes:

  1. companyList can be replaced with keyword value to work with the OData standard.

  2. Aimsio can be configured to mark an existing company as deleted if it doesn’t show up in the next import list.

Employees - Sample Response

{ “employeeList”: [ {

        "employeeAddress": "1 Calgary Tower Way, Calgary, Alberta",
        "employeeArea": "",
        "employeeCode": "E73209",
        "employeeEmail": "employee@company.com",
        "employeeFirstName": "John",
        "employeeLastName": "Doe",
        "employeePhone": "123-4567",
        "employeeStatusUname": "active\_employee\_status",
        "employeeTradeCode": "73209",
        "employeeNickName": "Johnny",
        "employeeNotes": "",
        "employmentType": "DIRECT\_HIRE",
        "employeeAverageDayPay": "50.00",
        "employeePayrollUnionName": "Default",
        "employmentHireDate": 1266883200000,
        "employmentReHireDate": null,
        "employmentTerminationDate": null,
        "employeePayrollHolidayEligibility": "Union Rule",
        "extraDataList": \[
        \],
        "customFieldDataList": \[
            {
                "customFieldDataDtype": "NumberFieldData",
                "customFieldDataStringValue": "100.00",
                "customFieldDataUname": "individual\_pay\_rate"
            },
            {
                "customFieldDataDtype": "BooleanFieldData",
                "customFieldDataBooleanValue": true,
                "customFieldDataUname": "union"
            }
        \]
    }
\]

}

Important Notes:

  1. employeeList can be replaced with keyword value to work with the OData standard.

  2. Aimsio can be configured to mark an existing employee inactive if it doesn’t appear in the next import list.

Employees

Adapter Endpoint

Method

Path

Produces

GET

/employees

application/json

Sample Response

{ “assetList”: [ { “assetCategoryCode”: “542”, “assetCode”: “AC43189”, “assetLocation”: “YYC”, “assetModel”: “Excavator”, “assetNotes”: “Excavator”, “assetSerialNumber”: “58051”, “assetStatusUname”: “asset_active”, “assetTypeUname”: “asset_type_truck”, “assetOperatorEmployeeCode”: “E73209”, “customFieldDataList”: [ // Omitted ], “extraDataList”: [ // Omitted ] } ] }

Important Notes:

  1. assetList can be replaced with keyword value to work with the OData standard.

  2. Aimsio can be configured to mark an existing asset as inactive if it doesn’t show up in the next import list.

Job Orders

Adapter Endpoint

Method

Path

Produces

GET

/jobs

application/json

Sample Response

{ “jobList”: [ { “jobStatus”: “Active”, “jobTypeUname”: “service_job”, “jobStartDate”: 1530658353000, “jobPriceBookName”: “The Aimsio Job Price Book”, “jobNumber”: “304-72262”, “jobLocationCode”: “YYC”, “jobEndDate”: 1530658353000, “jobDescription”: “A Job Description”, “jobDirection”: “A Job Description”, “jobLocationCode”: “YYC”, “jobClientCode”: “ACMECLIENTCODE”, “jobAddress”: “The Aimsio Job Location”, “jobAssigneeEmployeeNumber”: “E73209”, “extraDataList”: [ // omitted ], “customFieldDataList”: [ // omitted ], “jobWBSSchedule”: { “jobScheduleBudgetComparisonMethod”: “NonBillableAndBillableAgainstBudget”, // other values: BillableAgainstBudget, NonBillableAgainstBudget, CostAgainstBudget, BillableAgainstContractValue “jobScheduleBudgetEntryOption”: “PerDetailedBreakDown”, // other value: PerWBS “jobScheduleRootActivity”: { “wbsActivitySharedIdentifier”: “74763465 or 1. 304-72262”, “wbsActivitySubCode”: “1. ”, “wbsActivityName”: “304-72262”, “wbsActivityPlannedStartDate”: 1561482136326, “wbsActivityPlannedEndDate”: 1561914136326, “wbsActivityWeight”: 50, “wbsActivityUoM”: “KM”, “wbsActivityTotal”: 120, “wbsActivityCurrent”: 10, “wbsActivityCostCode”: “C0”, “wbsActivitySubCostCode”: “C0”, “wbsActivityChildren”: [ // omitted, other children activities, no limit on the hierarchy and how many levels it can go down ] } } } ] }

Important Notes

  1. jobList can be replaced with keyword value to work with the OData standard.

  2. jobWBSSchedule is optional 

    1. wbsActivitySharedIdentifier: it might be an id or guid or any type of identifier in the ERP. Since phase code and description can be always renamed, this is a CRITICAL field to have to account for such renames.

      1. After an import, Aimsio will delete all the phase/code that didn’t get imported and that affects the Payroll Report. That is why we need to find a shared identifier field.

      2. In the worst case, this field can be the phase + “.” + description but it will be prone to renaming mismatches

    2. There should be only one root node at the top-level (jobScheduleRootActivity). Every other node should be under wbsActivityChildren of the root node.

Non-recurring Billables

Adapter Endpoint

Method

Path

Produces

GET

/nrbs

application/json

Sample Response

{ “nrbList”: [ { “nrbJobOrderNumber”: “Job1234”, “nrbWBSSharedIdentifier”: “1.1. ROW”, “nrbOriginReference”: “INV#20-500-1234”, “nrbName”: “BC Permit - INV#20-500-1234”, “nrbQuantity”: “1.000”, “nrbRate”: “100.000”, “nrbIsBillable”: true, “nrbTypeUname”: “type_po”, “nrbNotes”: “HT0009 // $100.00 + $0.00 PST + 8%”, “nrbUpcharge”: “8.000” } ] }

Important Notes:

  1. nrbList can be replaced with keyword value to work with the OData standard.

  2. nrbJobOrderNumber is a required field. You cannot change the job on an existing NRB once it is created.

  3. nrbWBSSharedIdentifier is an optional field. Its value (if any) must exist within the Job WBS Schedule.

  4. nrbOriginReference is a unique/required field with which a non-recurring item is identified.

  5. nrbTypeUname is a required field.

Custom Entities

Adapter Endpoint

Method

Path

Produces

GET

/customer_entities

application/json

Sample Response

{ “customerEntityList”: [ { “customerEntityName”: “NMEDDYC”, “customerEntityVar1”: “Basic Property UDF 1”, “customerEntityVar2”: “Basic Property UDF 2”, “customerEntityVar3”: “Basic Property UDF 3”, “customerEntityVar4”: “Basic Property UDF 4”, “customerEntityVar5”: “Basic Property UDF 5”, “extraDataList”: [ // Omitted ], “customFieldDataList”: [ { “customFieldDataUname”: “tax_code”, “customFieldDataDtype”: “FreeTextFieldData”, “customFieldDataStringValue”: “NMEDDYC” }, { “customFieldDataUname”: “description”, “customFieldDataDtype”: “FreeTextFieldData”, “customFieldDataStringValue”: “NM Eddy County” }, { “customFieldDataUname”: “new_rate”, “customFieldDataDtype”: “NumberFieldData”, “customFieldDataStringValue”: “0.059583” }, { “customFieldDataUname”: “old_rate”, “customFieldDataDtype”: “NumberFieldData”, “customFieldDataStringValue”: “0” }, { “customFieldDataUname”: “effective_date”, “customFieldDataDtype”: “NumberFieldData”, “customFieldDataStringValue”: “1514790000” // EPOCH equivalent of Jan 1, 2018 } ] } ] }

Important Notes:

  1. customerEntityList can be replaced with keyword value to work with the OData standard.

  2. customerEntityName is a unique/required field with which an item is identified.

Outbound Data

If Aimsio is pushing data to an external adapter , we support the following entities.

Invoices

We can push invoice raw data as JSON and/or the rendered pdf in 5 different ways

Adapter Endpoints

Method

Description

Path

Content Type

POST

Just Raw data as Json

/revenue/invoices

application/json

POST

Raw data and PDF (Base64) in Json

/revenue/invoices

application/json

POST

Raw data and PDF as files

/revenue/invoices

multipart/form-data

POST

Raw data as Json and Pdf upload to a FTP server

/revenue/invoices

application/json

Just upload PDF to a FTP server

Sample Request (1)

{ “invoiceBilledTo”: “Aimsio Client\n315 7 Ave SW, Calgary, AB T2P 4K1”, “invoiceClientCode”: null, “invoiceClientName”: “Aimsio Client”, “invoiceDate”: 1528513454000, “invoiceMessage”: null, “invoiceNumber”: “1002”, “invoiceTax”: “410.50”, “invoiceTotal”: “8620.50”, “invoicableRecords”: [ { “invoicableDate”: 1528243200000, // Typically the start date of line item “invoicableDate2”: 1528243200000, // Typically the end date of line item “invoicableDate3”: null, “invoicableDesc”: “Some line item”, “invoicableGLAccount”: null, “invoicableRecordCostCode”: “1.2”, “invoicableRecordCostType”: "", “invoicableRecordDType”: “FieldTicketInvoicableRecord”, “invoicableRecordJobNo”: “83-070750”, “invoicableRecordNotes”: “Line Item Notes”, “invoicableRecordOriginReference”: null, “invoicableRecordQuantity”: “2.000”, “invoicableRecordRate”: “2000.000”, “invoicableRecordStr1”: “Customer account dependent string1”, “invoicableRecordStr2”: “Customer account dependent string2”, “invoicableRecordStr3”: “Customer account dependent string3”, “invoicableRecordStr4”: “Customer account dependent string4”, “invoicableRecordStr5”: “Customer account dependent string5”, “invoicableRecordStr6”: “Customer account dependent string6”, “invoicableRecordTotal”: “4000.00”, “invoicableRecordUofM”: null } ] }

Sample Request (2)

{ “pdf_base64”: “base64ofpdfbinaryhere…” “raw_data”: { “invoicableRecords”: [ ], “invoiceBilledTo”: “Canadian Pacific Railway\n”, “invoiceClientCode”: “CPR”, “invoiceClientName”: “Canadian Pacific Railway”, “invoiceCreatedAt”: 1582161482606, “invoiceCustomStatus”: null, “invoiceDate”: 1582161442000, “invoiceFormData”: null “invoiceId”: 2149, “invoiceMessage”: "", “invoiceNumber”: “1045”, “invoiceStatus”: “Pending”, “invoiceSubTotal”: “1510.00”, “invoiceTax”: “0.00”, “invoiceTotal”: “1510.00”, “invoiceUpdatedAt”: 1605032190969 } }

Sample Request (3) (where raw_data.json and Inv1234.pdfwill be two attached files to the request)

curl —request POST \ —url http://end-point-url/post \ —header ‘Authorization: Basic ABCDE’ \ —header ‘content-type: multipart/form-data; boundary=---011000010111000001101001’ \ —form raw_data.json= \ —form Inv1234.pdf=

Sample Response

{     “meta”: {         “error”: {             “errorCode”: "", //ivw.response.ok, ivw.request.validation.exception, ivw.request.parse.exception, ivw.request.application_error             “errorMessage”: "",             “errorReference”: "",         }     },     “data”: {} }

(Legacy) Field Tickets

Our clients use different configurations to enter the hours worked on a job. 

Aimsio will send a generic representation of the custom form data used by the client. 

The adapter will be responsible for extracting data on hours and equipment from this field.

Adapter Endpoint

Method

Path

Accepts

Produces

POST

/timeCards/fieldTickets

application/json

application/json

Sample Request

{ “fieldTickets”: [ { “fieldTicketNumber”: “FT180706-000-01”, “fieldTicketCostSheets”: [], “fieldTicketMainFormData”: {}, “fieldTicketCrewFormData”: {}, “fieldTicketAdditionalFormData”: {}, “fieldTicketLocation” : "", “fieldTicketTotal” : 0.00, “fieldTicketTotalNonBillable” : 0.00, “jobNumber”: “83-070750” } ] }

Sample Response

{ “meta”: { “error”: { “errorCode”: "", //ivw.response.ok, ivw.request.validation.exception, ivw.request.parse.exception, ivw.request.application_error “errorMessage”: "", “errorReference”: "" } }, “data”: {} }

Timecards

Aimsio can pre-process field ticket/timesheet data into time cards that are sent to the client, but this requires Aimsio to do data mapping work before sending them.

Aimsio will also track what has been sent to the integration adapter and will send appropriate cancel time cards when times are updated based on previously sent data.

Refer to Aimsio Standard API to view available properties of the Timecard JSON.

Adapter Endpoint

Method

Path

Accepts

Produces

POST

/timeCards/timeCards

application/json

application/json

Sample Request

{ “timeCards”: [ { “timeCardEmployeeOverTimeHours”: “0”, “timeCardDescription”: “AIM-0596”, “timeCardEmployeeCode”: “E01297”, “timeCardCompanyCode”: null, “timeCardIsBillable”: false, “timeCardEquipmentCode”: null, “timeCardEmployeeRegularHours”: “3.000000”, “timeCardRecordDate”: 1527609036000, “jobNumber”: “83-070750”, “timeCardUnionCode”: “AB”, “timeCardPayType”: “Hours”, “timeCardEmployeeTravelTimeHours”: “0”, “timeCardRate”: “0”, “timeCardEmployeeDoubleTimeHours”: “0”, “timeCardWorkTradeCode”: “WORKTRADECODE”, “timeCardPhaseCode”: “1.2”, “timeCardPayCode”: null, “extraDataList”: [ // Custom values specific to this integration { “extraDataDtype”: “FreeTextFieldData” “extraDataStringValue”: "", “extraDataName”: “TAX_JURISDICTION” } ] } ] }

Sample Response

{ “meta”: { “error”: { “errorCode”: "", //ivw.response.ok, ivw.request.validation.exception, ivw.request.parse.exception, ivw.request.application_error “errorMessage”: "", “errorReference”: "", } }, “data”: {} }

Forms

We can enable an action to send one form at a time (or in bulk) through a POST call to the adapter. The structure of the form depends on the fields in the form; here’s an example.

Sample Request

{ “formDataUpdatedAt”: 1617137846000, “formDataFormStatus”: “CLOSED_PROCESSED”, “formDataFieldDataList”: [ { “fieldDataFieldTypeLabel”: “Client”, “fieldDataFieldTypeDataType”: “LookupFieldData”, “fieldDataReferenceValue”: “eea9746e-35d0-4b19-a211-2df453e75510”, “fieldDataStringValue”: “ABC Resources Ltd.”, “fieldDataFieldTypeUname”: “client” }, { “fieldDataFieldTypeLabel”: “Date”, “fieldDataDateValue”: 1617137726000, “fieldDataFieldTypeDataType”: “DateFieldData”, “fieldDataFieldTypeUname”: “date” }, { “fieldDataFieldTypeLabel”: “Job No.”, “fieldDataFieldTypeDataType”: “FreeTextFieldData”, “fieldDataStringValue”: “LS-2021-002”, “fieldDataFieldTypeUname”: “job_no” }, { “fieldDataFieldTypeLabel”: “Hearing Protection”, “fieldDataFieldTypeDataType”: “BooleanFieldData”, “fieldDataBooleanValue”: true, “fieldDataFieldTypeUname”: “hearing_protection” }, { “fieldDataFieldTypeLabel”: “Attendance”, “fieldDataFieldTypeDataType”: “TableFieldData”, “fieldDataTableRows”: [ { “formDataFormTypeUname”: “attendance_table”, “formDataFieldDataList”: [ { “fieldDataFieldTypeLabel”: “Print”, “fieldDataFieldTypeDataType”: “FreeTextFieldData”, “fieldDataStringValue”: “Albert”, “fieldDataFieldTypeUname”: “employee_printed_name” } ], “formDataInternalStatus”: null, “formDataGuid”: “CD540F66-1CEF-4AAD-8692-E85EB4E1B44E” } ], “fieldDataFieldTypeUname”: “attendance_table” }, { “fieldDataFieldTypeLabel”: “Supervisor”, “fieldDataFieldTypeDataType”: “SignatureFieldData”, “fieldDataBooleanValue”: true, “fieldDataFieldTypeUname”: “supervisor_signature” } ], “formDataReviewedByFullName”: null, “formDataInternalStatus”: null, “formDataTimeZoneOffset”: -25200000, “formDataGuid”: “05EF1EA6-7CAD-474B-A7DE-BBD5FA47A09E”, “formTypeGuid”: “11a49c96-f7df-4d8e-9781-461c3662d9a7”, “orderInMasterForm”: null, “formDataReviewedByUserName”: null, “formDataCreatedByUserName”: “dennis@project-sandbox”, “formDataTimeZoneStr”: “America/Edmonton”, “formDataDeletionReason”: null, “masterFormDataGuid”: null, “formDataReviewedAt”: null, “formDataFormNo”: “STM210330-056-1”, “formDataIsDeleted”: false, “formDataCreatedAt”: 1617137726000, “formDataSubStatus”: null, “formDataFormTypeUname”: “safety_tailgate_meeting”, “formDataCreatedByFullName”: “Dennis Hale”, “formDataReviewComment”: "", “formDataStatus”: “Closed”, “formDataLocationLat”: 51.12591711361115, “formDataLocationLong”: -114.24591998542213, “formDataJobGuid”: “a3b6f3dd-780b-420e-8c4f-adb12414f83a”, “formDataReviewOk”: null }