Integrate with Node.js
This guide walks through a complete Node.js integration: authenticating with your API key, creating a collection, loading ASINs, triggering a scrape run, and downloading results once they are ready. All network calls use the native fetch API (Node 18+); an Axios variant is shown in the code tabs where it differs.
Prerequisites
- Node.js 18 or later (for native
fetch), or installaxiosfor older runtimes. - Your API key — find it under Settings → API Key in the dashboard, or call
POST /users/account-details. - A valid domain ID and an active postcode ID. See List Domains and Add a Postcode if you have not created these yet.
The complete script
The example below is self-contained. Copy it, replace the four constants at the top, and run it with node integrate.js.
// integrate.js — MultiCartAPI end-to-end example (Node.js)
// Requires Node 18+ for native fetch, or install axios and swap post() below.
const BASE_URL = "https://multicartapi.com/api/v1";
const API_KEY = "YOUR_API_KEY"; // from Settings → API Key
const DOMAIN_ID = 1; // e.g. 1 = amazon.com.au (/settings/domains/)
const POSTCODE_ID = 15; // Zipcode row ID (/zipcodes/get-zipcodes/)
// ─── helpers ────────────────────────────────────────────────────────────────
/** POST JSON to a MultiCartAPI endpoint; throws on network error.
* Branches on body.status: returns body.data on success, throws on failure. */
async function post(path, payload = {}) {
const res = await fetch(BASE_URL + path, {
method: "POST",
headers: {
"x-api-key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const body = await res.json();
if (body.status !== 1) {
throw new Error(
`API error on ${path}: code=${body.code} message=${JSON.stringify(body.data)}`
);
}
return body.data;
}
/** Poll until is_download_generated is true (or timeout after maxWaitMs). */
async function pollForResults(collectionId, intervalMs = 10_000, maxWaitMs = 600_000) {
const deadline = Date.now() + maxWaitMs;
while (Date.now() < deadline) {
const runs = await post("/schedules/collections/results/download/", {
collection_id: collectionId,
skip: 0,
limit: 1,
});
const latest = runs[0];
if (latest && latest.is_download_generated) {
return latest; // contains download_links
}
console.log(" still processing — waiting", intervalMs / 1000, "s …");
await new Promise((r) => setTimeout(r, intervalMs));
}
throw new Error("Timed out waiting for results");
}
// ─── main flow ──────────────────────────────────────────────────────────────
(async () => {
// 1. Create a collection
console.log("Creating collection …");
const collection = await post("/schedules/add_schedule/", {
name: "Node.js Example Collection",
request_type: "amazon",
schedule: "Manual",
priority: "Normal",
notification_email: "[email protected]",
fetch_mode: "full",
});
const collectionId = collection.id;
console.log(" Created collection id =", collectionId);
// 2. Add ASINs (bulk string form — domain + postcode by value)
console.log("Adding ASINs …");
const added = await post("/schedules/amazon/asin/create/multiple/", {
collection: collectionId,
domain: "amazon.com.au", // string value, not ID
customer_postcode: "4500", // postcode number as string
asins: "B0DJQQ38TG,B0ABC12345,B0XYZ99999",
including_raw_html: false,
});
console.log(
` Inserted ${added.success_count} ASINs; ${added.error_rows.length} skipped`
);
// 3. Trigger the run
console.log("Triggering run …");
const runMsg = await post("/schedules/run/result/", {
collection_id: collectionId,
});
console.log(" ", runMsg); // "Scraping Processing Queued!"
// 4. Poll until results are ready
console.log("Polling for results …");
const runner = await pollForResults(collectionId);
console.log(
` Run id=${runner.id} complete — ${runner.total_results} results`
);
// 5. Fetch the first result page
const pages = runner.download_links.pages;
if (!pages || pages.length === 0) {
throw new Error("No result pages in download_links");
}
const fileUrl = pages[0];
console.log("Fetching result file:", fileUrl);
const fileRes = await fetch(fileUrl, {
headers: { "x-api-key": API_KEY },
});
if (!fileRes.ok) {
throw new Error(`File download failed: HTTP ${fileRes.status}`);
}
const results = await fileRes.json();
console.log(
`Downloaded ${Array.isArray(results) ? results.length : "?"} product records.`
);
console.log("First record sample:", JSON.stringify(results[0], null, 2));
})();Axios users
Replace the post() helper's fetch call with:
import axios from "axios";
async function post(path, payload = {}) {
const { data: body } = await axios.post(BASE_URL + path, payload, {
headers: { "x-api-key": API_KEY },
});
if (body.status !== 1) {
throw new Error(`API error on ${path}: ${JSON.stringify(body.data)}`);
}
return body.data;
}
Everything else in the script is identical.
Step-by-step breakdown
1. The post() helper
Every MultiCartAPI response uses HTTP 200 regardless of business-logic outcome. The real success signal is body.status === 1. The helper centralises that check and throws a descriptive error when status is 0 or missing, so the rest of the script can use straightforward await calls without per-call error handling.
The x-api-key header is set once inside the helper — you never need to repeat it.
2. Create a collection
POST /schedules/add_schedule/ returns a draft collection. Status auto-promotes to enable the moment the first ASIN is added in the next step.
Create Collection
https://multicartapi.com/api/v1/schedules/add_schedule/| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Required | Human-readable collection name. |
request_type | string | Required | Supplier type: amazon or officeworks.com.au. |
schedule | string | Required | Run frequency: Every X Minutes, Daily, Weekly, Monthly, or Manual. |
priority | string | Required | Queue priority: Highest, High, Normal, Low, or Lowest. |
notification_email | string | Required | Email address for run-completion notifications. |
fetch_mode | string | Optional | full returns product details + availability; availability_only returns stock status only. |
{
"code": 200,
"data": {
"id": 101,
"name": "Node.js Example Collection",
"status": "draft",
"schedule_status": "Pending",
"request_type": "amazon",
"fetch_mode": "full",
"created_at": "2026-06-20T10:00:00Z",
"updated_at": "2026-06-20T10:00:00Z"
},
"status": 1
}2. Add ASINs
The multiple-ASIN endpoint accepts domain and postcode by string value (not by numeric ID), which is convenient when you already know them. Pass a comma-separated list in asins.
Create Multiple ASINs
https://multicartapi.com/api/v1/schedules/amazon/asin/create/multiple/| Parameter | Type | Required | Description |
|---|---|---|---|
collection | integer | Required | Collection ID returned in step 1. |
domain | string | Required | Domain name by value, e.g. amazon.com.au. |
customer_postcode | string | Required | Postcode number as a string, e.g. 4500. |
asins | string | Required | Comma-separated ASIN codes, e.g. B001,B002,B003. |
including_raw_html | boolean | Optional | Include raw HTML in scrape output. |
{
"code": 200,
"data": {
"success_count": 2,
"error_rows": [
{ "asin": "B0XYZ99999", "error": "Duplicate entry" }
]
},
"status": 1
}Field name difference vs. single-ASIN create
This endpoint uses including_raw_html (note the -ing). The single-ASIN endpoint /schedules/amazon/asin/create/ uses include_raw_html. They are different field names; mixing them up silently drops the value.
3. Trigger the run
POST /schedules/run/result/ enqueues all ASINs in the collection and returns immediately. Scraping happens asynchronously.
Run Collection
https://multicartapi.com/api/v1/schedules/run/result/| Parameter | Type | Required | Description |
|---|---|---|---|
collection_id | integer | Required | Collection ID to run. |
{
"code": 200,
"data": "Scraping Processing Queued!",
"status": 1
}Credit check
If your account has fewer credits than the number of ASINs in the collection, the envelope returns code: 402 with status: 0. The HTTP status is still 200. The post() helper will throw on status !== 1, so this surfaces as an error automatically.
4. Poll for results
POST /schedules/collections/results/download/ returns a list of run records. Poll until is_download_generated is true on the latest entry. The pollForResults() helper in the script checks every 10 seconds for up to 10 minutes.
List Run Results
https://multicartapi.com/api/v1/schedules/collections/results/download/| Parameter | Type | Required | Description |
|---|---|---|---|
collection_id | integer | Required | Collection ID to poll. |
skip | integer | Optional | Pagination offset. |
limit | integer | Optional | Number of runner rows to return. |
{
"code": 200,
"data": [
{
"id": 88,
"collection": 101,
"is_download_generated": true,
"download_links": {
"pages": [
"https://multicartapi.com/api/v1/results/101/88/Node.js Example Collection_88_Results_Page_1.json"
],
"all_pages": "https://multicartapi.com/api/v1/results/101/88/Node.js Example Collection_88_Results_All_Pages.zip"
},
"total_exacted_result": 250,
"total_results": 250,
"created_at": "2026-06-20T12:00:00Z",
"updated_at": "2026-06-20T12:05:00Z"
}
],
"status": 1
}5. Download a result file
Each URL in download_links.pages is a direct link to a JSON file. Send your x-api-key header — the server validates ownership before streaming the file.
Download Result File
https://multicartapi.com/api/v1/results/{collection_id}/{runner_id}/{file_name}The response is raw file bytes (no JSON envelope). Parse it with fileRes.json() for JSON pages, or pipe fileRes.body to a WriteStream if you want to save the ZIP to disk:
import { createWriteStream } from "fs";
import { Readable } from "stream";
const zipUrl = runner.download_links.all_pages;
const zipRes = await fetch(zipUrl, { headers: { "x-api-key": API_KEY } });
Readable.fromWeb(zipRes.body).pipe(createWriteStream("results.zip"));
Key API conventions to remember
- All responses use HTTP 200. Check
body.status === 1for success, not the HTTP status code. x-api-keyheader is the auth mechanism throughout. Never embed it in query strings.code: 402inside the envelope means a credit shortfall — not an HTTP 402.- Scraping is async.
POST /schedules/run/result/queues the job; poll/schedules/collections/results/download/for completion. download_links.pagesis an array — large collections are split across multiple page files. Iterate and fetch each one, or usedownload_links.all_pagesfor the ZIP.
Next steps
