logo

Integrate with Node.js

GuideUpdated 2026-06-20

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 install axios for 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

POST
https://multicartapi.com/api/v1/schedules/add_schedule/
API Key or Session Token
ParameterTypeRequiredDescription
namestringRequiredHuman-readable collection name.
request_typestringRequiredSupplier type: amazon or officeworks.com.au.
schedulestringRequiredRun frequency: Every X Minutes, Daily, Weekly, Monthly, or Manual.
prioritystringRequiredQueue priority: Highest, High, Normal, Low, or Lowest.
notification_emailstringRequiredEmail address for run-completion notifications.
fetch_modestringOptionalfull returns product details + availability; availability_only returns stock status only.
Response
{
"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

POST
https://multicartapi.com/api/v1/schedules/amazon/asin/create/multiple/
API Key or Session Token
ParameterTypeRequiredDescription
collectionintegerRequiredCollection ID returned in step 1.
domainstringRequiredDomain name by value, e.g. amazon.com.au.
customer_postcodestringRequiredPostcode number as a string, e.g. 4500.
asinsstringRequiredComma-separated ASIN codes, e.g. B001,B002,B003.
including_raw_htmlbooleanOptionalInclude raw HTML in scrape output.
Response
{
"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

POST
https://multicartapi.com/api/v1/schedules/run/result/
API Key or Session Token
ParameterTypeRequiredDescription
collection_idintegerRequiredCollection ID to run.
Response
{
"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

POST
https://multicartapi.com/api/v1/schedules/collections/results/download/
API Key or Session Token
ParameterTypeRequiredDescription
collection_idintegerRequiredCollection ID to poll.
skipintegerOptionalPagination offset.
limitintegerOptionalNumber of runner rows to return.
Response
{
"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

GET
https://multicartapi.com/api/v1/results/{collection_id}/{runner_id}/{file_name}
API Key or Session Token

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 === 1 for success, not the HTTP status code.
  • x-api-key header is the auth mechanism throughout. Never embed it in query strings.
  • code: 402 inside 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.pages is an array — large collections are split across multiple page files. Iterate and fetch each one, or use download_links.all_pages for the ZIP.