Understanding Mockup API Data Models: A Technical Guide
Deep dive into mockup API response structures. Learn how smart object data is organized, what each field means, and how to build robust integrations.
The De-Facto Standard: Rect + Transform
Every major design tool and mockup API uses the same fundamental data model: a rectangle with position coordinates and optional transforms. This pattern comes from decades of graphics programming, from Photoshop to Canvas2D to WebGL.
This model is universal because it's deterministic, minimal, and fast. Whether you're working with Photoshop layers, Figma frames, or mockup APIs, the core concept remains the same: define a bounding box and optionally transform it.
Anatomy of a Smart Object Response
When you upload a PSD to SudoMock, each smart object layer returns detailed metadata. Let's break down what each field means and how to use it:
1{2 "uuid": "73957ff7-82c4-4c14-947a-a6f841944d1c",3 "name": "print_area",4 "size": {5 "width": 3951,6 "height": 48007 },8 "position": {9 "x": 771,10 "y": 886,11 "width": 507,12 "height": 62013 },14 "quad": [15 [771, 886],16 [1278, 886],17 [1278, 1506],18 [771, 1506]19 ],20 "blend_mode": "BlendMode.NORMAL",21 "layer_name": "print_area"22}
Understanding Each Field
size vs position: The Key Distinction
The most important concept to understand is the difference between size and position. They represent two different things:
| Field | Represents | Use Case |
|---|---|---|
| size.width | Embedded content width (3951px) | Design canvas resolution |
| size.height | Embedded content height (4800px) | Design canvas resolution |
| position.x | Left edge on mockup (771px) | Placement coordinate |
| position.y | Top edge on mockup (886px) | Placement coordinate |
| position.width | Display width (507px) | Rendered size after transform |
| position.height | Display height (620px) | Rendered size after transform |
Think of it this way
position = "Your design will appear here on the mockup"
In the example above, a 3951x4800 design gets scaled down to 507x620 when rendered on the mockup canvas. The scale factor is approximately 0.128x (507/3951).
Working with Quad Coordinates
The quad array contains four [x, y] coordinate pairs representing the corners of the smart object's bounding box. This is especially useful for:
1// Draw smart object bounds on canvas2function drawSmartObjectBounds(ctx, smartObject) {3 const [topLeft, topRight, bottomRight, bottomLeft] = smartObject.quad;45 ctx.beginPath();6 ctx.moveTo(topLeft[0], topLeft[1]);7 ctx.lineTo(topRight[0], topRight[1]);8 ctx.lineTo(bottomRight[0], bottomRight[1]);9 ctx.lineTo(bottomLeft[0], bottomLeft[1]);10 ctx.closePath();11 ctx.strokeStyle = '#00ff00';12 ctx.stroke();13}1415// Check if a point is inside the smart object16function isPointInQuad(point, quad) {17 const [tl, tr, br, bl] = quad;18 // Simple bounding box check (for rectangular quads)19 return point.x >= tl[0] && point.x <= tr[0] &&20 point.y >= tl[1] && point.y <= bl[1];21}
Integration Patterns
Pattern 1: Basic Render Request
The most common pattern is uploading a PSD, storing the smart object metadata, then using it to render variations:
1// Step 1: Upload PSD and store metadata2const uploadResponse = await fetch('https://api.sudomock.com/api/v1/psd/upload', {3 method: 'POST',4 headers: {5 'X-API-KEY': API_KEY,6 'Content-Type': 'application/json'7 },8 body: JSON.stringify({9 psd_file_url: 'https://storage.example.com/tshirt-mockup.psd',10 psd_name: 'T-Shirt Front'11 })12});1314const { data: mockup } = await uploadResponse.json();1516// Store the mockup metadata for later use17const mockupId = mockup.uuid;18const smartObjects = mockup.smart_objects;1920// Find the main design area21const designArea = smartObjects.find(so => so.name === 'print_area');22console.log('Design canvas size:', designArea.size); // { width: 3951, height: 4800 }23console.log('Rendered position:', designArea.position); // { x: 771, y: 886, width: 507, height: 620 }2425// Step 2: Render with a design26const renderResponse = await fetch('https://api.sudomock.com/api/v1/renders', {27 method: 'POST',28 headers: {29 'X-API-KEY': API_KEY,30 'Content-Type': 'application/json'31 },32 body: JSON.stringify({33 mockup_uuid: mockupId,34 smart_objects: [{35 uuid: designArea.uuid,36 asset: {37 url: 'https://storage.example.com/my-design.png',38 fit: 'cover' // cover, contain, or fill39 }40 }],41 export_options: {42 image_format: 'webp',43 image_size: 1920,44 quality: 9545 }46 })47});4849const { data: render } = await renderResponse.json();50console.log('Rendered mockup:', render.print_files[0].export_path);
Pattern 2: Using Position Data for Custom Rendering
If you're building a custom preview or need to render locally, the position data tells you exactly where to place the design:
1async function createPreview(mockupImage, designImage, smartObject) {2 const canvas = document.createElement('canvas');3 const ctx = canvas.getContext('2d');45 // Set canvas to mockup dimensions6 canvas.width = mockupImage.width;7 canvas.height = mockupImage.height;89 // Draw the mockup background10 ctx.drawImage(mockupImage, 0, 0);1112 // Get position data from API response13 const { x, y, width, height } = smartObject.position;1415 // Draw the design at the correct position and size16 ctx.drawImage(designImage, x, y, width, height);1718 return canvas.toDataURL('image/png');19}2021// Usage22const preview = await createPreview(23 await loadImage(mockupThumbnail),24 await loadImage(myDesign),25 designArea26);
Design Resolution
size dimensions (e.g., 3951x4800), not the position dimensions. The API handles scaling automatically.Working with Blend Modes
The blend_mode field indicates how the smart object layer blends with layers below it in the PSD. Common modes include:
| Blend Mode | Effect | Common Use |
|---|---|---|
| NORMAL | Standard opacity blending | Regular design placement |
| MULTIPLY | Darkens, whites become transparent | Color overlays, fabric textures |
| SCREEN | Lightens, blacks become transparent | Light effects, glows |
| OVERLAY | Combines multiply and screen | Contrast enhancement |
For color smart objects (used to change product colors), MULTIPLY is typically used so the design texture shows through the color overlay.
Next.js Integration Examples
Here are practical examples for integrating SudoMock into your Next.js application. These examples show how to use the smart object metadata in real frontend code.
SudoMock Client Utility
First, create a reusable client for making API calls to SudoMock:
1// SudoMock API Client for Next.js2// Store your API key in .env.local as SUDOMOCK_API_KEY34const API_KEY = process.env.SUDOMOCK_API_KEY!5const BASE_URL = 'https://api.sudomock.com/api/v1'67export interface SmartObject {8 uuid: string9 name: string10 size: { width: number; height: number }11 position: { x: number; y: number; width: number; height: number }12 quad: [number, number][]13 blend_mode: string14}1516export interface MockupData {17 uuid: string18 name: string19 thumbnail: string20 smart_objects: SmartObject[]21}2223// Upload a PSD and get smart object metadata24export async function uploadPsd(psdUrl: string, name: string): Promise<MockupData> {25 const response = await fetch(`${BASE_URL}/psd/upload`, {26 method: 'POST',27 headers: {28 'X-API-KEY': API_KEY,29 'Content-Type': 'application/json',30 },31 body: JSON.stringify({32 psd_file_url: psdUrl,33 psd_name: name,34 }),35 })3637 const data = await response.json()38 if (!data.success) throw new Error(data.detail)39 return data.data40}4142// Render a mockup with a design43export async function renderMockup(44 mockupId: string,45 smartObjectId: string,46 designUrl: string47): Promise<string> {48 const response = await fetch(`${BASE_URL}/renders`, {49 method: 'POST',50 headers: {51 'X-API-KEY': API_KEY,52 'Content-Type': 'application/json',53 },54 body: JSON.stringify({55 mockup_uuid: mockupId,56 smart_objects: [{57 uuid: smartObjectId,58 asset: { url: designUrl, fit: 'cover' }59 }],60 export_options: {61 image_format: 'webp',62 image_size: 1920,63 quality: 9564 }65 }),66 })6768 const data = await response.json()69 if (!data.success) throw new Error(data.detail)70 return data.data.print_files[0].export_path71}
React Hook for Managing Mockups
A custom hook that manages mockup state and provides useful computed values from the smart object metadata:
1'use client'23import { useState, useMemo } from 'react'4import type { MockupData, SmartObject } from '@/lib/sudomock'56export function useMockupEditor(initialMockup?: MockupData) {7 const [mockup, setMockup] = useState<MockupData | null>(initialMockup ?? null)8 const [selectedDesign, setSelectedDesign] = useState<string | null>(null)910 // Find the main print area smart object11 const printArea = useMemo(() => {12 return mockup?.smart_objects.find(13 so => so.name.toLowerCase().includes('print') ||14 so.name.toLowerCase().includes('design')15 )16 }, [mockup])1718 // Calculate design requirements from size field19 const designRequirements = useMemo(() => {20 if (!printArea) return null2122 const { width, height } = printArea.size23 return {24 width,25 height,26 aspectRatio: width / height,27 megapixels: (width * height) / 1000000,28 recommendation: width >= 300029 ? 'High resolution - perfect for print'30 : 'Medium resolution - good for web'31 }32 }, [printArea])3334 // Get render position info from position field35 const renderPosition = useMemo(() => {36 if (!printArea) return null3738 const { x, y, width, height } = printArea.position39 const originalSize = printArea.size4041 return {42 x,43 y,44 width,45 height,46 scaleFactor: width / originalSize.width,47 // Useful for canvas preview48 bounds: { left: x, top: y, right: x + width, bottom: y + height }49 }50 }, [printArea])5152 return {53 mockup,54 setMockup,55 printArea,56 designRequirements,57 renderPosition,58 selectedDesign,59 setSelectedDesign,60 }61}
Live Preview with Canvas
Use the position data to create a real-time preview before rendering:
1'use client'23import { useRef, useEffect, useState } from 'react'45interface PreviewProps {6 mockupThumbnailUrl: string7 designImageUrl: string | null8 // From smart_object.position in API response9 position: { x: number; y: number; width: number; height: number }10}1112export function MockupPreview({ mockupThumbnailUrl, designImageUrl, position }: PreviewProps) {13 const canvasRef = useRef<HTMLCanvasElement>(null)14 const [previewDataUrl, setPreviewDataUrl] = useState<string | null>(null)1516 useEffect(() => {17 if (!designImageUrl || !canvasRef.current) return1819 const canvas = canvasRef.current20 const ctx = canvas.getContext('2d')21 if (!ctx) return2223 // Load both images24 const mockupImg = new Image()25 const designImg = new Image()26 mockupImg.crossOrigin = 'anonymous'27 designImg.crossOrigin = 'anonymous'2829 Promise.all([30 new Promise<void>(resolve => { mockupImg.onload = () => resolve(); mockupImg.src = mockupThumbnailUrl }),31 new Promise<void>(resolve => { designImg.onload = () => resolve(); designImg.src = designImageUrl }),32 ]).then(() => {33 // Set canvas to mockup size34 canvas.width = mockupImg.width35 canvas.height = mockupImg.height3637 // Draw mockup background38 ctx.drawImage(mockupImg, 0, 0)3940 // Draw design at the exact position from API41 // The position object tells us exactly where to place it!42 ctx.drawImage(43 designImg,44 position.x, // x coordinate from API45 position.y, // y coordinate from API46 position.width, // rendered width from API47 position.height // rendered height from API48 )4950 setPreviewDataUrl(canvas.toDataURL('image/png'))51 })52 }, [mockupThumbnailUrl, designImageUrl, position])5354 return (55 <div className="relative aspect-square bg-zinc-900 rounded-lg overflow-hidden">56 <canvas ref={canvasRef} className="hidden" />57 {previewDataUrl && (58 <img src={previewDataUrl} alt="Preview" className="w-full h-full object-contain" />59 )}60 </div>61 )62}
Server Action for Batch Rendering
Process multiple designs in parallel using Next.js Server Actions:
1'use server'23import { renderMockup } from '@/lib/sudomock'45interface BatchResult {6 designUrl: string7 renderUrl: string | null8 success: boolean9 error?: string10}1112export async function batchRenderMockups(13 mockupId: string,14 smartObjectId: string,15 designUrls: string[]16): Promise<BatchResult[]> {17 // Process 5 at a time to respect rate limits18 const CONCURRENCY = 519 const results: BatchResult[] = []2021 for (let i = 0; i < designUrls.length; i += CONCURRENCY) {22 const batch = designUrls.slice(i, i + CONCURRENCY)2324 const batchResults = await Promise.all(25 batch.map(async (designUrl): Promise<BatchResult> => {26 try {27 const renderUrl = await renderMockup(mockupId, smartObjectId, designUrl)28 return { designUrl, renderUrl, success: true }29 } catch (error) {30 return {31 designUrl,32 renderUrl: null,33 success: false,34 error: error instanceof Error ? error.message : 'Unknown error'35 }36 }37 })38 )3940 results.push(...batchResults)41 }4243 return results44}
Environment Variables
.env.local as SUDOMOCK_API_KEY. Never expose it in client-side code — always call SudoMock from Server Components, Server Actions, or API routes.Key Takeaways
Related Resources
Ready to Try SudoMock?
Start automating your mockups with 500 free API credits.