API & Developer Guides

How to Build a Node.js Script That Pulls a Set via API and Generates Every Mockup in It With a Single Command

By Mockupanda10 min read
How to Build a Node.js Script That Pulls a Set via API and Generates Every Mockup in It With a Single Command

If you sell print-on-demand products, you already know the drill. You finish a new design, then you spend the next hour opening templates one by one, dropping in your artwork, and downloading each result individually. It works, but it does not scale. The moment you have dozens of designs and want each one rendered across a curated set of templates, the manual approach becomes the bottleneck.

The Mockupanda API gives you a cleaner path. You can save a named group of templates as a Set inside your account, then reference that Set by a single ID in your API calls. Pair that with a small Node.js script and you have a single terminal command that pulls the Set, generates every mockup, and saves the images to disk, without you touching anything after you press Enter.

This guide walks through every line of that script. By the end you will have something reusable you can plug into a CI pipeline, a Shopify app, an Etsy automation, or just a cron job on your laptop.


What You Need Before Writing Any Code

A Mockupanda Account and an API Key

Head to mockupanda.com and sign up if you have not already. Once you are inside the dashboard, navigate to Dashboard, then API Keys, and click "Create API Key". Give it a descriptive name like "Local Dev" or "POD Automation", then copy the key immediately. The dashboard will not show it again after you leave that screen.

Store the key in a .env file at the root of your project:

MOCKUPANDA_API_KEY=your_key_here

Never hardcode the key in your script. If you push it to GitHub you will have to revoke and rotate it.

A Set Already Created in Your Account

Sets are named, ordered groups of templates you save inside Mockupanda. You build them in the web app by picking the templates you want, saving them as a group, and giving the group a name. Each Set gets a unique ID that looks something like 2bed1943-2880-4d40-b3d6-3296aa71e11e.

You can retrieve a Set's details with a quick GET request:

curl -X GET "https://www.mockupanda.com/api/v1/sets/YOUR_SET_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"

The response tells you everything you need:

{
  "id": "2bed1943-2880-4d40-b3d6-3296aa71e11e",
  "name": "Warm bedrooms",
  "template_ids": [
    "bedroom-poster-01",
    "wall-diptych-01",
    "living-room-03"
  ],
  "template_count": 3,
  "created_at": "2026-03-04T09:07:11.495Z",
  "updated_at": "2026-03-19T16:22:40.118Z"
}

Note that template_ids is an ordered array. The API respects that order when it renders, so the files you download will come back in the sequence you arranged in the app.

If you request a Set ID that does not exist or belongs to a different account, the API returns a 404 with the code SET_NOT_FOUND. Keep that in mind for your error handling.

Actionable takeaway: Create your Set in the Mockupanda web app first, grab the Set ID from a GET request, and store it somewhere handy. You will pass it as a command-line argument when you run your script.


Project Setup

Directory Structure and Dependencies

Create a new folder for your project and initialize it:

mkdir mockup-generator && cd mockup-generator
npm init -y
npm install dotenv

That is the only dependency you need. Node.js 18 and later ships with the native fetch API, so you do not need node-fetch or axios. If you are on an older Node version, run npm install node-fetch and adjust the import accordingly.

Your folder structure will look like this when you are done:

mockup-generator/
  .env
  generate-set.js
  output/

Create the output/ folder manually or let the script create it at runtime. Your downloaded mockups will land there.

Reading Configuration From the Environment

At the top of generate-set.js, load your environment variables and define the base URL:

import 'dotenv/config';
import fs from 'fs';
import path from 'path';

const API_KEY = process.env.MOCKUPANDA_API_KEY;
const BASE_URL = 'https://www.mockupanda.com/api/v1';
const OUTPUT_DIR = './output';

if (!API_KEY) {
  console.error('Missing MOCKUPANDA_API_KEY in .env');
  process.exit(1);
}

if (!fs.existsSync(OUTPUT_DIR)) {
  fs.mkdirSync(OUTPUT_DIR);
}

This exits early with a clear message if the key is missing, so you are never left wondering why requests are failing.

Actionable takeaway: Keep your dotenv loading at the very top of the file, before any other imports that might need environment variables. Guard against a missing key immediately so the script fails loudly instead of silently.


Fetching the Set

The GET /sets/{id} Request

The first thing your script does at runtime is fetch the Set to get its list of template_ids. Write a small async function for this:

async function fetchSet(setId) {
  const res = await fetch(`${BASE_URL}/sets/${setId}`, {
    headers: { 'Authorization': `Bearer ${API_KEY}` }
  });

  if (res.status === 404) {
    console.error(`Set not found: ${setId}. Check the ID or verify it belongs to this account.`);
    process.exit(1);
  }

  if (!res.ok) {
    const err = await res.json();
    console.error(`Failed to fetch set: ${err.code} - ${err.error}`);
    process.exit(1);
  }

  return res.json();
}

This returns the full Set object, including the template_ids array and the human-readable name. You will use both.

Validating What You Got Back

Before you fire off any generation requests, it is worth logging what the script found. It makes debugging much easier:

const set = await fetchSet(setId);
console.log(`Found set: "${set.name}" with ${set.template_count} templates`);
console.log('Template IDs:', set.template_ids);

If template_count is 0 or template_ids is empty, the Set exists but has no templates assigned. Exit early with a helpful message rather than sending a generate request with nothing in it.

Actionable takeaway: Always log the Set name and template count after fetching. It confirms you are working against the right Set before spending any credits.


Generating Mockups in Batches

Why You Need Batching

The POST /mockups/generate endpoint accepts up to 12 template IDs per request when you use template_ids in batch mode. If your Set has 30 templates, you cannot send them all at once. You need to split the array into chunks of 12 and send multiple requests.

This is also a smart approach for rate limiting. The API allows 100 requests per minute per key. Batching keeps your request count low while still processing everything quickly. For the relationship between batch mode and credits, each rendered mockup costs 1 credit regardless of whether it came from a single request or a batch, so a 12-template batch costs 12 credits.

Here is a simple chunk helper:

function chunkArray(arr, size) {
  const chunks = [];
  for (let i = 0; i < arr.length; i += size) {
    chunks.push(arr.slice(i, i + size));
  }
  return chunks;
}

The Generate Function

For each chunk, you send a POST request with template_ids, artwork_url, and response_format: "json". The JSON response format returns each mockup as a base64-encoded image inside a results array, which is easier to handle in a script than raw binary streams.

async function generateBatch(templateIds, artworkUrl) {
  const res = await fetch(`${BASE_URL}/mockups/generate`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      template_ids: templateIds,
      artwork_url: artworkUrl,
      response_format: 'json',
      width: 2000,
      format: 'jpeg'
    })
  });

  if (res.status === 429) {
    const err = await res.json();
    const retryAfter = err.details?.retry_after ?? 60;
    console.warn(`Rate limited. Waiting ${retryAfter}s before retrying...`);
    await new Promise(r => setTimeout(r, retryAfter * 1000));
    return generateBatch(templateIds, artworkUrl);
  }

  if (!res.ok) {
    const err = await res.json();
    console.error(`Generation failed: ${err.code} - ${err.error}`);
    return null;
  }

  return res.json();
}

Notice the rate limit handler. When the API returns 429 Too Many Requests, it includes a retry_after value in seconds. The function reads that value and waits before recursively retrying the same batch. This keeps your script self-healing rather than crashing mid-run.

This pattern is worth internalizing if you plan to batch large numbers of mockup variations, since rate limits become more relevant at scale.

Art print rendered into the Soft light wall art template by the Mockupanda API
Art print rendered into the Soft light wall art template by the Mockupanda API

Saving Images to Disk

Each item in the results array contains a template_id and an image field holding a base64 string. Decode it and write it as a JPEG:

function saveResult(result, outputDir) {
  const filename = `${result.template_id}.jpg`;
  const filepath = path.join(outputDir, filename);
  const buffer = Buffer.from(result.image, 'base64');
  fs.writeFileSync(filepath, buffer);
  console.log(`Saved: ${filename} (${(result.size_bytes / 1024).toFixed(1)} KB)`);
}

Using the template_id as the filename makes the output folder self-documenting. You immediately know which file corresponds to which template without having to rename anything.

Actionable takeaway: Always use response_format: "json" when batching so you get a structured results array. Binary responses work for single templates but become awkward when you are processing multiple results in a loop.


Putting It All Together

The Main Function

Now wire everything into a main function that reads the Set ID and artwork URL from command-line arguments:

async function main() {
  const [,, setId, artworkUrl] = process.argv;

  if (!setId || !artworkUrl) {
    console.error('Usage: node generate-set.js <set_id> <artwork_url>');
    process.exit(1);
  }

  const set = await fetchSet(setId);
  console.log(`\nGenerating mockups for set: "${set.name}"`);
  console.log(`Templates: ${set.template_count}`);
  console.log(`Artwork: ${artworkUrl}\n`);

  const chunks = chunkArray(set.template_ids, 12);
  let totalSaved = 0;

  for (let i = 0; i < chunks.length; i++) {
    console.log(`Sending batch ${i + 1} of ${chunks.length}...`);
    const data = await generateBatch(chunks[i], artworkUrl);
    if (data && data.results) {
      for (const result of data.results) {
        saveResult(result, OUTPUT_DIR);
        totalSaved++;
      }
    }
  }

  console.log(`\nDone. ${totalSaved} mockups saved to ${OUTPUT_DIR}/`);
}

main().catch(err => {
  console.error('Unexpected error:', err.message);
  process.exit(1);
});

Running the Script

With the script saved as generate-set.js, run it like this:

node generate-set.js 2bed1943-2880-4d40-b3d6-3296aa71e11e https://example.com/your-artwork.jpg

That one command fetches the Set, chunks the templates, generates every mockup, and writes each JPEG to your output/ folder. Failed requests cost zero credits, so if something goes wrong mid-run you are not penalized for the error.

If you want to wire this into a package.json script, add:

"scripts": {
  "generate": "node generate-set.js"
}

Then run it as npm run generate -- YOUR_SET_ID YOUR_ARTWORK_URL.

Actionable takeaway: Accept the Set ID and artwork URL as command-line arguments rather than hardcoding them. That one decision makes the same script reusable across every design and every Set in your account.


Handling Edge Cases and Understanding Costs

What Happens With Multi-Frame Templates

Some templates in your Set might be multi-frame, meaning diptychs or triptychs that expect more than one artwork URL. The batch approach using artwork_url (singular) works fine for single-frame templates but may not give you the result you want for split layouts.

If your Set contains multi-frame templates and you want different artwork in each frame, you would need to handle those templates separately using the artwork_urls parameter with frame indices like "0", "1", and "2". You could look up template details via GET /templates to identify them for special handling. The multi-frame template guide covers that parameter in detail.

For most Sets built around single-frame wall art and poster templates, the script above works exactly as written.

Credit Usage and Cost Estimation

Before you run the script on a large Set, it is worth knowing what you will spend. Each rendered mockup costs 1 credit, and 1 credit equals $0.01. So a Set with 30 templates costs $0.30 per run. If you have 50 designs and run all of them against the same Set, that is $15.00 total, which covers 1,500 mockups at $10 per 1,000 credits.

Failed requests cost 0 credits, so you are not charged for any batch that errors out. Credits never expire, so any wallet balance you top up stays usable indefinitely.

You can check your current credit balance with:

curl -X GET https://www.mockupanda.com/api/v1/billing/wallet \
  -H "Authorization: Bearer YOUR_API_KEY"

Add a quick balance check at the start of your main() function if you want the script to warn you before running when credits are low.

Output Width and Quality

The script above uses the default width: 2000 and format: "jpeg". If you need higher resolution, you can push width up to 4000 pixels, though render times increase to roughly 3 to 5 seconds per mockup at that size. The default JPEG quality is 85, which strikes a good balance between file size and listing image sharpness. You can pass quality as a number from 1 to 100 if you need to adjust it.

Actionable takeaway: Run a quick cost estimate before you fire off a large batch by multiplying the number of templates in your Set by the number of designs you plan to process. Top up your API wallet in advance if needed. Minimum top-up is $1.00 and payments go through Stripe.


Where to Take This Next

Once you have this script working, the natural next step is to loop it over a folder of artwork files. Add a fs.readdirSync call to read every PNG or JPEG from a local directory, then call main() for each file. Combined with Mockupanda's pixel-perfect rendering that preserves artwork quality without AI distortion, you can go from a folder of raw designs to a full library of listing-ready images in a single terminal session.

You can also extend the script to upload results directly to your Shopify or Etsy draft listings using those platforms' own APIs, making the whole pipeline from design file to published listing a single automated workflow.

Sets are the organizing principle that makes all of this manageable. When your preferred templates change, you update the Set in the Mockupanda web app and every future script run picks up the change automatically. No code edits required.