SudoMock
Mockups
DocsPricing
Request a demo
Get Started

Start rendering mockups in minutes

From PSD to product mockup in one API call.

Get Free API Key→Browse Templates
Public status pagePublished API pricingProduct mockup API500 one-time API credits

Site Navigation

SudoMock

Turn reusable PSD templates, artwork, and editable text into production-ready product images through REST API, no-code workflows, native storefront apps, or AI.

SudoMock is a registered DBA
1209 Mountain Rd Pl NE, Ste N
Albuquerque, NM 87110, USA

Product

  • Pricing
  • Dashboard
  • PSD Mockups
  • Changelog
  • Start Free

Developers

  • API Overview
  • MCP Server
  • Quick Start
  • Render API
  • Upload PSD
  • Authentication
  • Error Handling
  • PSD Preparation
  • Integrations

Mockups & Tools

  • All Mockups
  • Apparel Mockups
  • Drinkware Mockups
  • Wall Art Mockups
  • Home & Living
  • By Platform
  • Search Mockups
  • Background Remover
  • Free Tools

Resources

  • All Use Cases
  • T-Shirt Mockup Generator for Print on Demand
  • Tote Bag Mockup Generator for Sustainable Brands
  • Automate Mug at Scale with SudoMock
  • Blog
  • Roadmap
  • All Alternatives
  • vs Placeit
  • vs DynamicMockups
  • vs Adobe Photoshop API
  • vs Static Mockup Generators
  • vs AI Image Editors

Company

  • About
  • Contact
  • Brand Assets
  • System Status (opens in new tab)
  • Feature Requests (opens in new tab)
Blog

Latest Articles

View all →
  • 01Packaging Mockups for Every SKU: One Bag Photo, One Label per Flavor
  • 02Photoshop Variables and Data Sets for Mockups, Run by API
  • 03Etsy Personalization Orders: Render Each Buyer's Name on Your Own PSD
  • 04Wallpaper and Mural Mockups on Your Own Room Photo, by API

© 2026 SudoMock. All rights reserved.

PrivacyTermsRefundsCookies[email protected]
  1. Blog
  2. /
  3. Tutorial

Packaging Mockups for Every SKU: One Bag Photo, One Label per Flavor

Save one photo of the blank bag as a photo mockup with a label area, then send one render request per SKU with that SKU's label file. The label lands with its own colors on the bag, and the same request can recolor the bag for a variant, so a 30 flavor line is 30 calls against one mockup.

Published Oct 29, 2026·6 min read·Beck

A photo mockup render request next to three coffee pouches SudoMock rendered with it, one request per SKU label: Ethiopia Guji and Colombia Huila on kraft, Night Shift Espresso on a black pouch

Key takeaways

  • Draw the label area once in photo pixels; every SKU render reuses its print_area_id.
  • For a long SKU list, add is_async true to each render and collect every result from its job.
  • Name each file by its SKU row, and send jpg when the images go to a marketplace.

On this page

  • What one SKU needs
  • Save the bag photo with its label area
  • Render one label per SKU
  • Change the bag color for a variant
  • Run the whole SKU list
  • Flavor names and weights as text layers
  • Next step
  • Frequently asked questions
  • Sources

What one SKU needs

Every SKU of a line that shares one bag differs only in its label, so the inputs are one photo of the blank bag and one label file per SKU. Keep them in a list with the SKU, the label's public HTTPS link (or its bytes as base64) and the bag color, and that list drives every render.

SKULabel fileBag color
HAL-ETH-250label-hal-eth-250.pngkraft
HAL-COL-250label-hal-col-250.pngkraft
HAL-ESP-340label-hal-esp-340.pngblack

Each label is one SKU's print file of a sample coffee line, exported as a PNG (JPEG and WebP work too): 900 by 1200 pixels for a 3 by 4 inch front label at 300 DPI, with the flavor name, the roast and the net weight already set by the designer. A new flavor adds a row and a label file, and the bag photo stays the same.

Save the bag photo with its label area

POST /api/v1/photo-mockups turns one product photo into a reusable mockup, and its optional print_areas list saves the label area in the photo's own pixels. Draw the area where the printed label sits on that bag, at the label's shape, with the corners from the top left clockwise.

curl -X POST https://api.sudomock.com/api/v1/photo-mockups \
-H "x-api-key: $SUDOMOCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_url": "https://cdn.example.com/kraft-pouch-front.png",
"name": "Kraft pouch, front label",
"print_areas": [
{ "points": [[450, 310], [700, 310], [700, 640], [450, 640]], "name": "Front label" }
]
}'
const response = await fetch('https://api.sudomock.com/api/v1/photo-mockups', {
method: 'POST',
headers: {
'x-api-key': process.env.SUDOMOCK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"source_url": "https://cdn.example.com/kraft-pouch-front.png",
"name": "Kraft pouch, front label",
"print_areas": [
{
"points": [
[
450,
310
],
[
700,
310
],
[
700,
640
],
[
450,
640
]
],
"name": "Front label"
}
]
}),
});
const data = await response.json();
console.log(data);
import os
import requests
response = requests.post(
"https://api.sudomock.com/api/v1/photo-mockups",
headers={
"x-api-key": os.environ["SUDOMOCK_API_KEY"],
"Content-Type": "application/json",
},
json={
"source_url": "https://cdn.example.com/kraft-pouch-front.png",
"name": "Kraft pouch, front label",
"print_areas": [
{
"points": [
[
450,
310,
],
[
700,
310,
],
[
700,
640,
],
[
450,
640,
],
],
"name": "Front label",
},
],
},
)
print(response.json())
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
payload := []byte(`{
"source_url": "https://cdn.example.com/kraft-pouch-front.png",
"name": "Kraft pouch, front label",
"print_areas": [
{
"points": [
[
450,
310
],
[
700,
310
],
[
700,
640
],
[
450,
640
]
],
"name": "Front label"
}
]
}`)
req, err := http.NewRequest("POST", "https://api.sudomock.com/api/v1/photo-mockups", bytes.NewBuffer(payload))
if err != nil {
panic(err)
}
req.Header.Set("x-api-key", os.Getenv("SUDOMOCK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
<?php
$payload = <<<'JSON'
{
"source_url": "https://cdn.example.com/kraft-pouch-front.png",
"name": "Kraft pouch, front label",
"print_areas": [
{
"points": [
[
450,
310
],
[
700,
310
],
[
700,
640
],
[
450,
640
]
],
"name": "Front label"
}
]
}
JSON;
$ch = curl_init('https://api.sudomock.com/api/v1/photo-mockups');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-api-key: ' . getenv('SUDOMOCK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $payload,
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;

The call answers with the finished mockup, so there is nothing to poll before the first render. Keep data.mockup_id, the label's data.quads[0].print_area_id and the bag's data.surfaces[0].surface_uuid; the print areas and surfaces guide reads both lists field by field. On 29 October 2026 this call, sent with the studio photo of the kraft paper coffee bag template and a 250 by 330 pixel area on the front of the pouch, came back with the area saved as Front label and the pouch as one surface labelled kraft paper pouch.

Without a photo of your own bag, the coffee bag templates carry photos of blank kraft coffee bags, and their Use This Mockup button opens the chosen photo in Photo Mockups, where Edit areas draws the label area and the Code tab prints the mockup_id and print_area_id for the requests below.

Render one label per SKU

POST /api/v1/photo-mockups/{mockup_id}/render places one SKU's label file on the saved area. Set blend_mode to normal so the label keeps the exact colors of its file on a kraft, white or black bag, and fit to crop, which covers the area and matches fit when the area has the label's shape.

curl -X POST https://api.sudomock.com/api/v1/photo-mockups/MOCKUP_ID/render \
-H "x-api-key: $SUDOMOCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"print_areas": [
{
"uuid": "FRONT_LABEL_PRINT_AREA_ID",
"artwork_url": "https://cdn.example.com/labels/label-hal-col-250.png",
"placement": { "fit": "crop" },
"adjustments": { "blend_mode": "normal" }
}
],
"export_options": { "image_format": "webp", "image_size": 1024 }
}'
const response = await fetch('https://api.sudomock.com/api/v1/photo-mockups/MOCKUP_ID/render', {
method: 'POST',
headers: {
'x-api-key': process.env.SUDOMOCK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"print_areas": [
{
"uuid": "FRONT_LABEL_PRINT_AREA_ID",
"artwork_url": "https://cdn.example.com/labels/label-hal-col-250.png",
"placement": {
"fit": "crop"
},
"adjustments": {
"blend_mode": "normal"
}
}
],
"export_options": {
"image_format": "webp",
"image_size": 1024
}
}),
});
const data = await response.json();
console.log(data);
import os
import requests
response = requests.post(
"https://api.sudomock.com/api/v1/photo-mockups/MOCKUP_ID/render",
headers={
"x-api-key": os.environ["SUDOMOCK_API_KEY"],
"Content-Type": "application/json",
},
json={
"print_areas": [
{
"uuid": "FRONT_LABEL_PRINT_AREA_ID",
"artwork_url": "https://cdn.example.com/labels/label-hal-col-250.png",
"placement": {
"fit": "crop",
},
"adjustments": {
"blend_mode": "normal",
},
},
],
"export_options": {
"image_format": "webp",
"image_size": 1024,
},
},
)
print(response.json())
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
payload := []byte(`{
"print_areas": [
{
"uuid": "FRONT_LABEL_PRINT_AREA_ID",
"artwork_url": "https://cdn.example.com/labels/label-hal-col-250.png",
"placement": {
"fit": "crop"
},
"adjustments": {
"blend_mode": "normal"
}
}
],
"export_options": {
"image_format": "webp",
"image_size": 1024
}
}`)
req, err := http.NewRequest("POST", "https://api.sudomock.com/api/v1/photo-mockups/MOCKUP_ID/render", bytes.NewBuffer(payload))
if err != nil {
panic(err)
}
req.Header.Set("x-api-key", os.Getenv("SUDOMOCK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
<?php
$payload = <<<'JSON'
{
"print_areas": [
{
"uuid": "FRONT_LABEL_PRINT_AREA_ID",
"artwork_url": "https://cdn.example.com/labels/label-hal-col-250.png",
"placement": {
"fit": "crop"
},
"adjustments": {
"blend_mode": "normal"
}
}
],
"export_options": {
"image_format": "webp",
"image_size": 1024
}
}
JSON;
$ch = curl_init('https://api.sudomock.com/api/v1/photo-mockups/MOCKUP_ID/render');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-api-key: ' . getenv('SUDOMOCK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $payload,
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;

The finished image is at data.print_files[0].export_path. Rendered on 29 October 2026 with the three label files of the table, one request each, the pouch came back as three WebP files with every label square on the front of the bag and the folds and studio light of the photo kept.

A kraft stand-up coffee pouch with a terracotta Colombia Huila medium roast label, rendered by API from one label file on the kraft pouch photoRendered with SudoMock, SKU HAL-COL-250: the label file went to the saved front label area with blend_mode normal, so its terracotta and cream print as designed on the kraft.

The default blend mode, multiply, reads like ink printed into the material, which suits a design printed on the bag itself. A label is a printed sticker on top of the bag, and normal reproduces its colors whatever the bag color; the fit and blend modes guide describes each mode.

Change the bag color for a variant

A variant on another bag color is the same request with one more entry: the bag's surface_uuid with a color. That entry recolors the whole pouch, and the label entry still lands on top with its own colors.

curl -X POST https://api.sudomock.com/api/v1/photo-mockups/MOCKUP_ID/render \
-H "x-api-key: $SUDOMOCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"print_areas": [
{ "surface_uuid": "POUCH_SURFACE_UUID", "color": "#232326" },
{
"uuid": "FRONT_LABEL_PRINT_AREA_ID",
"artwork_url": "https://cdn.example.com/labels/label-hal-esp-340.png",
"placement": { "fit": "crop" },
"adjustments": { "blend_mode": "normal" }
}
],
"export_options": { "image_format": "webp", "image_size": 1024 }
}'
const response = await fetch('https://api.sudomock.com/api/v1/photo-mockups/MOCKUP_ID/render', {
method: 'POST',
headers: {
'x-api-key': process.env.SUDOMOCK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"print_areas": [
{
"surface_uuid": "POUCH_SURFACE_UUID",
"color": "#232326"
},
{
"uuid": "FRONT_LABEL_PRINT_AREA_ID",
"artwork_url": "https://cdn.example.com/labels/label-hal-esp-340.png",
"placement": {
"fit": "crop"
},
"adjustments": {
"blend_mode": "normal"
}
}
],
"export_options": {
"image_format": "webp",
"image_size": 1024
}
}),
});
const data = await response.json();
console.log(data);
import os
import requests
response = requests.post(
"https://api.sudomock.com/api/v1/photo-mockups/MOCKUP_ID/render",
headers={
"x-api-key": os.environ["SUDOMOCK_API_KEY"],
"Content-Type": "application/json",
},
json={
"print_areas": [
{
"surface_uuid": "POUCH_SURFACE_UUID",
"color": "#232326",
},
{
"uuid": "FRONT_LABEL_PRINT_AREA_ID",
"artwork_url": "https://cdn.example.com/labels/label-hal-esp-340.png",
"placement": {
"fit": "crop",
},
"adjustments": {
"blend_mode": "normal",
},
},
],
"export_options": {
"image_format": "webp",
"image_size": 1024,
},
},
)
print(response.json())
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
payload := []byte(`{
"print_areas": [
{
"surface_uuid": "POUCH_SURFACE_UUID",
"color": "#232326"
},
{
"uuid": "FRONT_LABEL_PRINT_AREA_ID",
"artwork_url": "https://cdn.example.com/labels/label-hal-esp-340.png",
"placement": {
"fit": "crop"
},
"adjustments": {
"blend_mode": "normal"
}
}
],
"export_options": {
"image_format": "webp",
"image_size": 1024
}
}`)
req, err := http.NewRequest("POST", "https://api.sudomock.com/api/v1/photo-mockups/MOCKUP_ID/render", bytes.NewBuffer(payload))
if err != nil {
panic(err)
}
req.Header.Set("x-api-key", os.Getenv("SUDOMOCK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
<?php
$payload = <<<'JSON'
{
"print_areas": [
{
"surface_uuid": "POUCH_SURFACE_UUID",
"color": "#232326"
},
{
"uuid": "FRONT_LABEL_PRINT_AREA_ID",
"artwork_url": "https://cdn.example.com/labels/label-hal-esp-340.png",
"placement": {
"fit": "crop"
},
"adjustments": {
"blend_mode": "normal"
}
}
],
"export_options": {
"image_format": "webp",
"image_size": 1024
}
}
JSON;
$ch = curl_init('https://api.sudomock.com/api/v1/photo-mockups/MOCKUP_ID/render');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-api-key: ' . getenv('SUDOMOCK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $payload,
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;

Rendered on 29 October 2026, the espresso label came back on a black pouch that keeps the zip, the creases and the light of the kraft photo.

A black stand-up coffee pouch with a navy Night Shift Espresso dark roast label, the bag recolored and the label placed in one API requestRendered with SudoMock, SKU HAL-ESP-340 on a black pouch: a surface entry with color #232326 recolored the bag, and the label entry kept the label's navy as designed.

color takes a hex code or the name of a color saved on the mockup, so a line that comes back to the same bag colors can save them by name once.

Run the whole SKU list

The script below takes the SKU list, fires one request per row in parallel and saves each image under its SKU. For a large set, the render guide adds "is_async": true to each call and collects every result from its job.

csv
sku,label_url,bag_color
HAL-ETH-250,https://cdn.example.com/labels/label-hal-eth-250.png,
HAL-COL-250,https://cdn.example.com/labels/label-hal-col-250.png,
HAL-ESP-340,https://cdn.example.com/labels/label-hal-esp-340.png,#232326

Fill in the three IDs from the create response, set SUDOMOCK_API_KEY, and run node skus.mjs skus.csv.

JavaScript
// skus.mjs
import { readFileSync, writeFileSync } from "node:fs";
const MOCKUP = "MOCKUP_ID";
const LABEL_AREA = "FRONT_LABEL_PRINT_AREA_ID";
const BAG = "POUCH_SURFACE_UUID";
const [header, ...lines] = readFileSync(process.argv[2], "utf8").trim().split(/\r?\n/);
const keys = header.split(",");
const rows = lines.map((line) => Object.fromEntries(line.split(",").map((v, i) => [keys[i], v.trim()])));
await Promise.all(rows.map(async (row) => {
const print_areas = [
{ uuid: LABEL_AREA, artwork_url: row.label_url, placement: { fit: "crop" }, adjustments: { blend_mode: "normal" } },
];
if (row.bag_color) print_areas.unshift({ surface_uuid: BAG, color: row.bag_color });
const send = () => fetch(`https://api.sudomock.com/api/v1/photo-mockups/${MOCKUP}/render`, {
method: "POST",
headers: { "x-api-key": process.env.SUDOMOCK_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ print_areas, export_options: { image_format: "webp", image_size: 1024 } }),
});
let res = await send();
if (res.status === 429) {
const wait = Number(res.headers.get("retry-after")) || 5;
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
res = await send();
}
const json = await res.json();
if (!res.ok) return console.error(row.sku, res.status, json);
const image = await fetch(json.data.print_files[0].export_path);
writeFileSync(`${row.sku}.webp`, Buffer.from(await image.arrayBuffer()));
console.log(row.sku, "saved");
}));

A row answered 429 waits for its Retry-After and is sent once more. Each SKU lands next to the CSV as its own file, such as HAL-COL-250.webp, ready for the store, a marketplace listing or a wholesale catalog. For marketplace uploads set image_format to jpg: the render guide notes that some marketplace upload pipelines read WebP colors with reduced contrast.

Without code, Photo Mockup: Render in the SudoMock n8n node takes a color and a blend mode per entry, so each Google Sheets row can carry its SKU's label link and bag color.

Flavor names and weights as text layers

When the packaging design lives in a PSD with the flavor name and the weight as type layers, the label art goes to the smart object and each SKU's words go to text_layers, with fit set to shrink for long names. Photoshop variables and data sets for mockups maps a spreadsheet row to those fields, and a color smart object takes the bag color per SKU through smart_objects[].color, as the smart objects reference shows. In the dashboard, PSD uploads are capped at 300 MB. API uploads support files up to Adobe's official PSD file size limit.

Next step

Start with three SKUs, one photo mockup, three label files and three requests, then send the whole list. The photo mockups overview walks through the create and render calls, the render reference holds the surface color and blend mode fields, and the packaging templates hold bags, boxes and pouches to start from. For boxes, labels and pouches across a whole brand, the packaging mockups use case covers rendering them from your own PSD templates.

Frequently asked questions

How do I make a packaging mockup for every flavor from one template?

Save one photo of the blank bag as a photo mockup with a label area, export one label PNG per flavor, and send one render request per flavor with that file's URL. The bag photo and the label area stay the same for the whole line, so a new flavor is one more request.

Will the label keep its exact colors on a kraft or black bag?

Yes, when the label entry sets blend_mode to normal. Normal reproduces the colors of the label file whatever the bag color, while the default multiply reads like ink printed into the material. A sticker label belongs on normal.

How do I change the bag color for one SKU?

Add an entry with the bag's surface_uuid and a color to the same render request. The surface entry recolors the whole bag and the label entry sits on top with its own colors. The color can be a hex code or the name of a color saved on the mockup.

How do I name each SKU's image?

The render answers with the image URL, and your script saves the file under the SKU of its row. A CSV with one row per SKU then gives one file per SKU, such as HAL-COL-250.webp. For marketplace uploads, set the image format to jpg.

Do I need a packaging PSD?

No. A photo of the blank bag is enough, because a photo mockup places the label on a saved area of the photo. When the design already lives in a PSD with text layers for the flavor and weight, those layers can be filled per SKU instead.

Sources

  1. Photo mockups (opens in new tab), SudoMock Docs
  2. Print areas and surfaces (opens in new tab), SudoMock Docs
  3. Render artwork onto a photo mockup (opens in new tab), SudoMock Docs
  4. Create a mockup from a product photo (opens in new tab), SudoMock Docs
  5. Render a photo mockup (opens in new tab), SudoMock Docs
  6. Fit and blend modes (opens in new tab), SudoMock Docs
  7. Render mockups with n8n and SudoMock (opens in new tab), SudoMock Docs
B

Beck

CTO

View all posts

On this page

  • What one SKU needs
  • Save the bag photo with its label area
  • Render one label per SKU
  • Change the bag color for a variant
  • Run the whole SKU list
  • Flavor names and weights as text layers
  • Next step
  • Frequently asked questions
  • Sources

Related articles

A three-row CSV of export labels and design URLs above the three framed poster mockups SudoMock rendered from it, each labeled with its row's export_label
Tutorial

Photoshop Variables and Data Sets for Mockups, Run by API

Map Photoshop's Text Replacement, Pixel Replacement and Visibility variables to render fields and run every CSV row against your mockup PSD.

Oct 28, 2026 · 8 min read

Six colorways of one t-shirt PSD rendered by API, one color hex per request
Tutorial

Every T-Shirt Color Variant Mockup from One PSD by API

One PSD with a color smart object renders every t-shirt colorway: one request per color, shown with six real renders.

Oct 1, 2026 · 4 min read

A blank white pillow photo, the two SudoMock tool calls an assistant makes with it, and the same pillow rendered with a lemon branch design and a sleeping cat badge
Tutorial

Product Mockups With ChatGPT or Claude From One Product Photo

Make product mockups in ChatGPT or Claude without a PSD: one product photo becomes a reusable mockup, and each design is one render on it.

Oct 7, 2026 · 7 min read

Explore by use case

See SudoMock in action for your specific product category

Automate Packaging Mockups at Scale with Your TemplatesT-Shirt Mockup Generator API for Print on DemandHoodie Mockup Generator APIAutomate Mug Mockups at Scale with SudoMock APIPoster Mockup Generator API for Wall Art SellersPhone Case Mockup Generator API for Print on Demand

Your own PSD, rendered via API

Render your own PSD through the API. Start with 500 one-time credits, no card.

Start Free View Docs

Render your own PSD via API with 500 one-time credits.

Start Free · 500 credits, no card