Use "Transfer files into destination application" as the import type with your HTTP connection to Portatour.
- HTTP method:
POST
- Relative URI:
/a/api/ImportCustomers?mode=UpdateOrInsert
The request body needs two inline parts for the multipart form:
[
{
"name": "file",
"value": "{{csvData}}",
"type": "inline",
"mime-headers": {
"Content-Disposition": "form-data",
"content-type": "text/csv",
"filename": "customers.csv"
}
},
{
"name": "importConfig",
"value": "{{importConfigJson}}",
"type": "inline",
"mime-headers": {
"Content-Disposition": "form-data",
"content-type": "application/json",
"filename": "customers-template.importcfg"
}
}
]
A few things worth noting:
type: "inline" means you're providing content directly as a string — works perfectly here since both the CSV and importConfig are text-based
type: "attachment" with "value": "{{blob}}" is for when you have a blobKey from a prior blob export, which isn't needed here
- The docs mention only one file reference (blob type) per import, but since both parts are inline this shouldn't be an issue
- Don't include the boundary — Celigo adds it automatically
- Make sure to set the header
Content-Type: multipart/form-data
Reference: Celigo docs on multipart/form-data
For generating the CSV from the NetSuite saved search data, I'm using a preSavePage hook on the export. One thing to keep in mind — preSavePage runs per page, not across all pages. So either set the export page size high enough to capture all customers in one go, or accept that each page becomes a separate CSV upload (which should be fine since Portatour's UpdateOrInsert mode is idempotent).
function preSavePage(options) {
const records = options.data;
if (!records || !records.length) {
return {
data: [],
errors: options.errors,
abort: false,
newErrorsAndRetryData: []
};
}
const columns = ['ExternalId', 'CompanyName', 'Street', 'ZipCode', 'City', 'Country'];
let csv = columns.join(',') + '\n';
for (const rec of records) {
const row = columns.map(col => {
let val = (rec[col] || '').toString().replace(/"/g, '""');
return `"${val}"`;
});
csv += row.join(',') + '\n';
}
return {
data: [{
csvData: csv,
importConfigJson: JSON.stringify({ /* your importcfg content */ })
}],
errors: options.errors,
abort: false,
newErrorsAndRetryData: []
};
}
If the importConfig is always the same (just a field mapping template), you can hardcode it directly in the request body template instead of passing it dynamically.
Overall flow ends up being:
NetSuite Saved Search Export (JSON)
→ preSavePage hook (convert records to CSV string)
→ HTTP Transfer to Portatour (multipart/form-data, 2 inline parts)