Developer docs

API reference

Everything in the product is available over REST: renders, templates, assets, bulk jobs, print files, presets, and the embedded editor. An interactive OpenAPI explorer with try-it-out lives at /api/docs.

Quickstart

Three steps from zero to a rendered mockup. You need an account and an API key from the API keys page.

1 · Upload a PSD template (or pick a library one)

curl -H "Authorization: Bearer $KEY" \
  -F "file=@mockup.psd" -F "name=Tee front" \
  https://your-domain.com/api/v1/templates

The response lists every smart-object slot with its id, name, source size, and warp info. Templates from the public library can be rendered directly by their id — no upload needed.

2 · Render

curl -H "Authorization: Bearer $KEY" \
  -F "template_id=$TEMPLATE" \
  -F "Front Design=@art.png" \
  https://your-domain.com/api/v1/renders -o mockup.png

Design files are multipart parts whose field name is the slot key (layer name, layer id, or smart-object uuid). The image comes back directly — typically in about a second.

3 · Automate

Use async jobs with webhooks from code, or plug the same request into Zapier, Make, or n8n with zero code.

Authentication

Create keys on the API keys page — multiple named keys per account, revocable individually. Send the key as a bearer token on every request:

Authorization: Bearer soe_xxxxxxxxxxxxxxxx

Keys are shown once at creation. The same page holds your webhook signing secret (for verifying webhook deliveries) and your website key (for the embedded editor — safe to expose in client-side code, unlike API keys).

No-code automation — Zapier, Make & n8n

Every render is one HTTP request, so any tool with an HTTP module can generate mockups. Reference your artwork with design_url (a public URL — e.g. from Google Drive, Dropbox, Airtable, or your store) and there is no file handling at all.

The one request every tool makes

Method   POST
URL      https://your-domain.com/api/v1/renders
Headers  Authorization: Bearer soe_YOUR_KEY
Body     multipart/form-data
  template_id   tee-front-001
  options       {"slots":{"Front Design":{"design_url":"https://…/art.png"}}}

Response: the finished PNG (binary). Save it, attach it, upload it onward.

Zapier

Add a "Webhooks by Zapier" action → Custom Request or POST. Set the URL and Authorization header as above; in Data, add template_id and options as form fields, mapping the design URL from your trigger (new Drive file, new sheet row, new order) into design_url. Zapier receives the image as the response body — feed it to any later step (upload to Drive, attach to email, send to your store).

Make

Add an HTTP → "Make a request" module. Method POST, body type multipart/form-data, fields template_id and options. Make exposes the response as binary data you can pass straight to Google Drive, Dropbox, or an email module. For long renders, use the async flow below and a Custom webhook trigger to resume the scenario when the render finishes.

n8n

Drop in an HTTP Request node — method POST, body Form-Data, response format File. For async, set webhook_url to the URL of an n8n Webhook node; your workflow sleeps while we render and resumes the moment the render.completed event arrives — no polling loop.

Async recipe (Make & n8n)

POST /api/v1/render-jobs            ← same fields as /renders, plus:
  webhook_url   https://your-n8n.com/webhook/abcd1234

→ 202 {"id":"rj_...","status":"queued"}

… we render, then POST to your webhook_url:
  X-MGW-Event: render.completed
  X-MGW-Signature: <HMAC-SHA256 of the raw body, hex>
  {"id":"rj_...","status":"done","result_url":"https://…","duration_ms":1043}

Verify X-MGW-Signature with your webhook secret (see async jobs for code samples), then download result_url. The same pattern works for bulk jobs — the webhook delivers a link to the finished zip.

Templates

POST/api/v1/templates

Multipart: file (the PSD) and optional name. We parse the PSD once at upload — every smart object becomes a render slot. Returns 201 with the template, including per-slot editor geometry (warped outline, placement quad, source size).

GET/api/v1/templates
GET/api/v1/templates/{id}
DELETE/api/v1/templates/{id}

List, inspect, and delete your templates. Slot keys accepted in render calls: the layer name (e.g. Front Design), the layer id, or the smart-object uuid.

GET/api/public/library
GET/api/public/library/{id}

The public template library (no auth required). Filter with ?category=. Library template ids work in every render endpoint exactly like your own.

Design assets

Upload artwork once, reference it everywhere by id — no re-uploading the same design on every render call.

POST/api/v1/assets

Multipart: file (PNG, JPEG, or WebP, up to 30 MB). Returns 201 with id, name, dimensions, and a thumbnail URL.

GET/api/v1/assets
DELETE/api/v1/assets/{id}

Use an asset in any render via the asset_id slot option:

-F 'options={"slots":{"Front Design":{"asset_id":"ast_8f3k2"}}}'

Renders (sync)

POST/api/v1/renders

The workhorse. Multipart form: template_id, optional options JSON, and designs supplied any of three ways — an uploaded file part named after the slot, a design_url slot option, or an asset_id slot option. A slot can also be just a color fill with no design at all.

curl -H "Authorization: Bearer $KEY" \
  -F "template_id=$TEMPLATE" \
  -F "Front Design=@art.png" \
  -F 'options={
        "format": "png", "image_size": 2048, "mode": "full",
        "slots": {
          "Front Design": {"fit":"contain","scale":0.8,"rotation":-4},
          "Shirt Color":  {"color":"#111827"}
        }
      }' \
  https://your-domain.com/api/v1/renders -o out.png

The response is the image itself, with X-Render-Id and X-Render-Ms headers. Add store=true to also keep a copy in your workspace (retrievable later via /api/v1/render-jobs/{id}/result). Rendered files never expire.

Render options

Top-level keys of the options JSON object (simple ones may also be sent as plain form fields):

OptionValuesDescription
formatpng · jpeg · webp (default png)Output encoding. PNG keeps transparency.
image_size16 – 8192Output width in pixels. Default: the template's native canvas width.
backgroundtemplate · transparent"template" composes onto the product photo; "transparent" renders the warped placement alone with real alpha — ideal for layering. Requires mode: fast.
modefast · fullRendering pipeline — see quality modes.
quality1 – 100 (default 90)JPEG/WebP encode quality.
supersample1 – 4 (default 2)Anti-aliasing factor for the warp resampling.
fitcover · contain · stretchDefault fit for slots that don't set their own.
slotsobjectPer-slot placement and styling — see slot options.

Slot options

Keys of options.slots.<slot>. The slot key is the layer name, layer id, or smart-object uuid (listed in the template detail response). All options compose — a design can be tiled, recolored, adjusted, and repositioned in one call.

Content

OptionValuesDescription
design_urlhttps URLFetch the design from a public URL — the no-code favorite.
asset_idstringUse a design from your asset library.
colorhex or CSS nameFill the slot with a flat color — alone (product color variants) or under a design.

Placement

OptionValuesDescription
fitcover · contain · stretchHow the design fills the slot.
offset_x / offset_ypixels (source space)Nudge the design inside the slot.
scalenumber (1 = fitted size)Resize around the slot center.
rotationdegreesRotate around the slot center.

Adjustments (applied to the design before warping)

OptionRangeDescription
opacity0 – 100 (default 100)Design transparency.
brightness-150 – 150Lighten / darken.
contrast-100 – 100Flatten / punch up.
saturation-100 – 100Desaturate / saturate.
vibrance-100 – 100Boosts muted colors more than vivid ones.
blur0 – 100Gaussian blur (0–10 px at design resolution).

Pattern tiling

OptionValuesDescription
patterntrue · falseTile the design seamlessly across the whole slot (all-over prints, wrapping paper, fabric).
pattern_scale2 – 400 (default 50)Tile width as a percentage of the slot width. offset_x/offset_y shift the tile origin.

Quality modes

ModeWhat it doesWhen to use
fastComposites your warped design over the flattened template. ~1 s typical. Supports transparent backgrounds.Interactive editors, previews, most production renders.
fullRe-composites the original PSD layer stack with your design injected — shadow, lighting, and texture layers above the slot modulate the artwork exactly as in Photoshop.Hero shots and templates with overlay effects. Slower; template background only.
-F 'options={"mode":"full"}'

Async jobs + webhooks

POST/api/v1/render-jobs

Identical fields to /renders, but returns 202 immediately with a job id. Add webhook_url and we POST the result to you — no polling.

curl -H "Authorization: Bearer $KEY" \
  -F "template_id=$TEMPLATE" -F "Front Design=@art.png" \
  -F "webhook_url=https://yourapp.com/hooks/mockups" \
  https://your-domain.com/api/v1/render-jobs

→ 202 {"id":"rj_7h2k","status":"queued","mode":"async", ...}
GET/api/v1/render-jobs
GET/api/v1/render-jobs/{id}
GET/api/v1/render-jobs/{id}/result

List recent jobs (?limit=), poll status, or fetch the finished image. Stored results never expire.

Webhook delivery

Events: render.completed / render.failed (and bulk.completed / bulk.cancelled for bulk jobs). Each delivery carries the event name in X-MGW-Event and an HMAC-SHA256 signature of the raw body (hex) in X-MGW-Signature, signed with the secret from your API keys page. Failed deliveries are retried once.

{
  "id": "rj_7h2k",
  "status": "done",
  "template_id": "tpl_x91",
  "error": null,
  "duration_ms": 1043,
  "result_url": "https://your-domain.com/api/v1/render-jobs/rj_7h2k/result"
}

Verifying signatures

# Python
import hmac, hashlib

def verify(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
// Node
import { createHmac, timingSafeEqual } from "crypto";

function verify(rawBody, signature, secret) {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Batch renders

POST/api/v1/renders/batch

Up to 50 variations in one call. Attach design files under any field names, plus a batch JSON array referencing them. Results are stored and returned as URLs; per-item errors don't fail the batch.

curl -H "Authorization: Bearer $KEY" \
  -F "artA=@design-a.png" -F "artB=@design-b.png" \
  -F 'batch=[
    {"template_id":"tee-front","designs":{"Front Design":"artA"},"label":"tee-a"},
    {"template_id":"tee-front","designs":{"Front Design":"artB"},"label":"tee-b"},
    {"template_id":"mug-right","designs":{"Wrap":"artA"},
     "options":{"slots":{"Wrap":{"scale":0.9}}},"label":"mug-a"}
  ]' \
  https://your-domain.com/api/v1/renders/batch

→ {"total":3,"successful":3,"failed":0,
   "renders":[{"label":"tee-a","status":"done","render_id":"rj_1","url":"…"}, …]}

Items may also use design_url / asset_id slot options instead of uploaded files. You're only billed for successful renders.

Bulk generation

POST/api/v1/bulk-jobs

The full matrix: designs × templates × colors rendered server-side into one zip (up to 1000 renders per job). Returns 202 — the job keeps running even if your client disconnects.

curl -H "Authorization: Bearer $KEY" \
  -F "design-1=@a.png" -F "design-2=@b.png" \
  -F 'spec={
    "templates": [
      {"id": "tee-front"},
      {"id": "hoodie-front", "preset_id": "pst_3k2"},
      {"id": "mug-right", "design_slot": "Wrap", "color_slot": "Mug Color"}
    ],
    "assets": ["ast_8f3k2"],
    "colors": ["#111827", "#f8fafc", "olive"],
    "options": {"format": "png", "image_size": 2000},
    "webhook_url": "https://yourapp.com/hooks/bulk"
  }' \
  https://your-domain.com/api/v1/bulk-jobs
Spec keyTypeDescription
templatesarray (required)Templates to render. Each entry: id; optional design_slot / color_slot to pin which slot gets the design / color (defaults to the largest slot); optional preset_id — a saved placement preset, snapshotted at submission so later edits don't change a running job.
assetsarray of asset idsDesigns from your library, combined with any uploaded files.
colorsarrayColor variants — the matrix multiplies by these.
optionsobjectSame render options as /renders, applied to every cell.
webhook_urlurlGet bulk.completed / bulk.cancelled with the zip's result_url.
GET/api/v1/bulk-jobs
GET/api/v1/bulk-jobs/{id}
POST/api/v1/bulk-jobs/{id}/cancel
GET/api/v1/bulk-jobs/{id}/result

Status responses include live completed/total progress and credits_used. Honest billing on cancel: stop a job at the 8th of 50 renders and you're charged exactly 8 — and the 8 finished images are still delivered as a partial zip via result_url.

Placement presets

Save a placement (fit, offsets, scale, rotation, …) per template once — usually from the visual editor — then reuse it in API renders and bulk jobs for consistent positioning across thousands of files.

POST/api/v1/presets
{"template_id":"tee-front","name":"chest small",
 "slots":{"Front Design":{"fit":"contain","scale":0.45,"offset_y":-380}}}
GET/api/v1/presets?template_id={id}
DELETE/api/v1/presets/{id}

Apply via preset_id in a bulk spec entry, or copy its slots object into any render's options.

Embedded editor

Let your customers personalize products on your site. Issue a website key (wk_…) — safe for client-side code, scoped to read-and-render only:

POST/api/v1/keys/website-key

Then drop in the iframe:

<iframe
  src="https://your-domain.com/embed/TEMPLATE_ID?wk=wk_xxxxxxxx"
  style="width:100%;height:640px;border:0"
></iframe>

<script>
  window.addEventListener("message", (e) => {
    if (e.data?.type === "mockup:rendered") {
      console.log("render ready:", e.data.url);
    }
  });
</script>

For headless integrations the same key works against the embed API:

GET/api/embed/templates/{id}
POST/api/embed/templates/{id}/render

Send the key as an X-Website-Key header or ?wk= query param. The render endpoint accepts the same multipart fields and slot options as /api/v1/renders.

Media URLs

URLReturns
/media/templates/{id}/preview.jpgTemplate preview (720 px JPEG).
/media/assets/{id}/thumbAsset thumbnail.
/media/assets/{id}Asset original.
/media/renders/{job_id}A stored render result.

Asset and render media require auth (or a presigned URL, which result payloads already contain when S3-compatible storage is configured).

Errors & limits

Errors are JSON: {"detail": "human-readable message"}.

StatusMeaning
401Missing or invalid API key / website key.
402Plan quota reached (monthly renders, templates, or assets). The message shows your usage.
404Resource not found — or owned by a different account.
413Design file exceeds 30 MB.
422Validation error: unknown slot, bad option value, malformed JSON, oversized bulk matrix.
500Render failed — the message includes the engine error.
LimitValue
Max output width8192 px
Max design upload30 MB
Batch size50 renders per call
Bulk matrix1000 renders per job
Webhook retries1 retry on connection failure

Full machine-readable schema: interactive OpenAPI explorer · openapi.json. Questions the docs don't answer? They should — tell us.