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:
-
NTLM
-
Basic Authentication (Username/password)
-
API Key, e.g. https://partner.web.service.com/rest/api/companies?hapikey=66da89a5-097a-3250-8cfd-05c83e704a56
-
OAUTH (request/access token dance - available for Netsuite)
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.
If Aimsio is querying data from an external adapter, we support pulling for the following entities.
Billable Items
Adapter Endpoint
Method | Path | Produces |
|---|---|---|
GET |
| 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:
- billableItemList can be replaced with keyword
valueto work with the OData standard.
Price Books
Adapter Endpoint
Method | Path | Produces |
|---|---|---|
GET |
| 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:
-
priceBookList can be replaced with keyword
valueto work with the OData standard. -
priceBookName or priceBookGuid is a unique/required field with which a price book is identified.
-
priceItemList is an array of billable items with their rates in this price book.
-
Each billable item is referenced by its code, e.g
WELD_TRKin the above example -
This array is immutable, i.e. previous items under this price book will be deleted if they don’t exist in the array.
-
-
priceItemAction determines whether priceItemList is all inclusive or not.
-
DEFAULT: If the value is
ADD_OR_UPDATE, any items in the price book that is not in priceItemList will be deleted. -
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 |
| 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:
-
companyList can be replaced with keyword
valueto work with the OData standard. -
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:
-
employeeList can be replaced with keyword
valueto work with the OData standard. -
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 |
| 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:
-
assetList can be replaced with keyword
valueto work with the OData standard. -
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 |
| 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
-
jobList can be replaced with keyword
valueto work with the OData standard. -
jobWBSSchedule is optional
-
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.
-
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.
-
In the worst case, this field can be the phase + “.” + description but it will be prone to renaming mismatches
-
-
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 |
| 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:
-
nrbList can be replaced with keyword
valueto work with the OData standard. -
nrbJobOrderNumber is a required field. You cannot change the job on an existing NRB once it is created.
-
nrbWBSSharedIdentifier is an optional field. Its value (if any) must exist within the Job WBS Schedule.
-
nrbOriginReference is a unique/required field with which a non-recurring item is identified.
-
nrbTypeUname is a required field.
Custom Entities
Adapter Endpoint
Method | Path | Produces |
|---|---|---|
GET |
| 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:
-
customerEntityList can be replaced with keyword
valueto work with the OData standard. -
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 |
| application/json |
POST | Raw data and PDF (Base64) in Json |
| application/json |
POST | Raw data and PDF as files |
| multipart/form-data |
POST | Raw data as Json and Pdf upload to a FTP server |
| 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 |
| 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 |
| 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 }