> ## Documentation Index
> Fetch the complete documentation index at: https://sudomock.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The base URL is https://api.sudomock.com.
> Authenticate every request with the x-api-key header. Keys begin with sm_.
> A render returns the finished image at data.print_files[0].export_path. A request sent with is_async true returns a job_id to poll at GET /api/v1/jobs/{job_id}.
> Prefer the official SDKs over hand-written HTTP calls: npm install sudomock for Node, pip install sudomock for Python.

# Render product mockups with Next.js

> Render a PSD mockup from a Next.js route handler.

<Prompt description="Use this pre-built prompt to get started faster." icon="microchip" iconType="solid" actions={["copy", "cursor"]}>
  # Render mockups with the SudoMock Node SDK

  **Purpose:** Enforce only the **current** and **correct** instructions for
  rendering product mockups using the [SudoMock](https://sudomock.com) Node
  SDK.
  **Scope:** All AI-generated advice or code that renders a mockup with
  SudoMock from Node.js, Next.js, Express or Cloudflare Workers must follow
  these guardrails.

  ## **1. Official SudoMock Node setup**

  ### **Prerequisites**

  Human must first create an API key at
  [https://sudomock.com/dashboard/api-keys](https://sudomock.com/dashboard/api-keys).
  Keys start with `sm_`.

  The API key must be stored in an environment variable called
  `SUDOMOCK_API_KEY`. The SDK needs Node 20 or later.

  ### **Install the SDK**

  Use the project's existing package manager to install the SudoMock Node SDK.

  ```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
  npm install sudomock
  # or: yarn add sudomock / pnpm add sudomock / bun add sudomock
  ```

  ### **Initialize the client**

  ```typescript theme={"theme":{"light":"github-light","dark":"vesper"}}
  import SudoMock from 'sudomock'

  const sudomock = new SudoMock()
  ```

  Constructing the client with no argument reads `SUDOMOCK_API_KEY` from the
  environment. The base URL is `https://api.sudomock.com` and the client
  already points there, so do not set one. The examples are ESM. A CommonJS
  project reaches the same client with
  `const { SudoMock } = require('sudomock')`.

  ### **Upload a template once**

  A Photoshop file becomes a reusable template on the account. This belongs in
  setup, not in the path that serves requests.

  ```typescript theme={"theme":{"light":"github-light","dark":"vesper"}}
  const mockup = await sudomock.uploads.create({
    psdFileUrl: 'https://example.com/heavyweight-tee.psd',
    psdName: 'Heavyweight tee front',
  })

  mockup.uuid                  // pass as mockupId when rendering
  mockup.smartObjects[0].uuid  // the layer artwork goes into
  mockup.textLayers            // the copy you can replace later
  ```

  Keep those UUIDs. A template uploaded last month renders today without
  being sent again.

  ### **Render a mockup**

  ```typescript theme={"theme":{"light":"github-light","dark":"vesper"}}
  const render = await sudomock.renders.create({
    mockupId: mockup.uuid,
    smartObjects: [
      {
        uuid: mockup.smartObjects[0].uuid,
        asset: { url: 'https://example.com/artwork.png' },
      },
    ],
    exportOptions: { imageFormat: 'webp', imageSize: 1600 },
  })

  render.url // the finished image
  ```

  ## **2. Complete `renders.create()` parameter reference**

  ### **Required parameters**

  | Parameter      | Type       | Description                                 |
  | -------------- | ---------- | ------------------------------------------- |
  | `mockupId`     | `string`   | Mockup UUID returned by `uploads.create()`. |
  | `smartObjects` | `object[]` | Layers to fill. At least one entry.         |

  ### **Smart object entry**

  | Field       | Type     | Description                                 |
  | ----------- | -------- | ------------------------------------------- |
  | `uuid`      | `string` | Smart object UUID from the upload response. |
  | `asset.url` | `string` | Public HTTPS URL of the artwork to place.   |
  | `asset.fit` | `string` | How the artwork meets the print area.       |

  The accepted `fit` values are listed at
  [https://sudomock.com/docs/concepts/fit-and-blend-modes](https://sudomock.com/docs/concepts/fit-and-blend-modes).

  ### **Optional parameters**

  | Parameter                   | Type       | Description                       |
  | --------------------------- | ---------- | --------------------------------- |
  | `textLayers`                | `object[]` | Entries of `uuid` and `text`.     |
  | `exportOptions.imageFormat` | `string`   | Output format, such as `webp`.    |
  | `exportOptions.imageSize`   | `number`   | Output width in px, 100 to 10000. |
  | `exportOptions.quality`     | `number`   | Compression quality.              |
  | `isAsync`                   | `boolean`  | Render in the background.         |

  An account still in trial renders up to 1024 px wide. An `imageSize` above
  that answers `OUTPUT_RESOLUTION_LIMIT` rather than quietly shrinking the
  image, so the width you asked for is the width you get.

  ### **Response**

  A successful call resolves with:

  ```typescript theme={"theme":{"light":"github-light","dark":"vesper"}}
  {
    url: string,        // the finished image
    printFiles: [ ... ] // one entry per filled layer
  }
  ```

  Each `printFiles` entry carries an `exportPath` and the smart object it was
  placed into, so a render that fills several layers hands back all of them.

  With `isAsync` set to true the call resolves with a job instead. Await
  `sudomock.jobs.waitForJob(job.jobId)`, or let a webhook call you back.

  A failed call throws `SudoMockError`, carrying `status` and `code`.

  ## **3. Critical instructions for AI models**

  ### **3.1 - ALWAYS do the following**

  1. **Store the API key in an environment variable**
     (`SUDOMOCK_API_KEY`). Never hardcode API keys.
  2. **Import from `sudomock`.** The package name is `sudomock` and the
     default export is `SudoMock`.
  3. **Call from the server.** A route handler, a server action or a
     background job. Never from code that ships to a browser.
  4. **Upload the template once** and reuse its UUIDs for every later render.
  5. **Use `await`.** Every client method returns a Promise.
  6. **Catch `SudoMockError`** and branch on `status` and `code`. The
     subclasses `AuthenticationError`, `CreditError`, `ValidationError` and
     `RateLimitError` let you branch without reading message text.
  7. **Use camelCase for SDK parameters** (`mockupId`, `smartObjects`,
     `exportOptions`) and snake\_case when calling the REST API directly
     (`mockup_uuid`, `smart_objects`, `export_options`).
  8. **Send the key in the `x-api-key` header** on a direct REST call.
  9. **Retry `429`, `500` and `502` with backoff.** `RateLimitError` carries
     `retryAfter` in seconds. Never retry `400`, `401`, `402`, `404` or `422`.

  ### **3.2 - NEVER do the following**

  1. **Do not** name the variable `NEXT_PUBLIC_SUDOMOCK_API_KEY` in a Next.js
     project. That prefix inlines the value into the browser bundle, and a key
     in a bundle is a key anyone can copy.
  2. **Do not** send an `Authorization: Bearer` header. This API reads the key
     from `x-api-key`.
  3. **Do not** upload the Photoshop file again on every render. The template
     is stored on the account.
  4. **Do not** import from any package name other than `sudomock`.
  5. **Do not** invent a field name. The published contract is at
     [https://sudomock.com/docs/api-reference/introduction](https://sudomock.com/docs/api-reference/introduction).
  6. **Do not** treat a connection failure as an API rejection. A network
     failure or a client side timeout reports `status` as `0`, so give the
     handler a fallback status such as `502`.

  ## **4. Common patterns**

  ### **Replacing copy instead of artwork**

  ```typescript theme={"theme":{"light":"github-light","dark":"vesper"}}
  const render = await sudomock.renders.create({
    mockupId: mockup.uuid,
    textLayers: [{ uuid: textLayerUuid, text: 'Limited run' }],
  })
  ```

  ### **Long renders**

  ```typescript theme={"theme":{"light":"github-light","dark":"vesper"}}
  const job = await sudomock.renders.create({
    mockupId: mockup.uuid,
    smartObjects: [{ uuid, asset: { url: artworkUrl } }],
    isAsync: true,
  })

  const render = await sudomock.jobs.waitForJob(job.jobId)
  ```

  ### **Retrying a rate limit**

  ```typescript theme={"theme":{"light":"github-light","dark":"vesper"}}
  import { setTimeout as sleep } from 'node:timers/promises'
  import { RateLimitError } from 'sudomock'

  if (error instanceof RateLimitError) {
    await sleep((error.retryAfter ?? 1) * 1000)
    // send the same render again
  }
  ```

  ## **5. AI model verification steps**

  Before returning any SudoMock-related solution, you **must** verify:

  1. **Import**: is `SudoMock` imported from `sudomock`?
  2. **API Key**: is the key read from the environment, on the server?
  3. **Header**: is a direct REST call sending `x-api-key`?
  4. **UUIDs**: do `mockupId` and the smart object UUID come from an upload
     response rather than from a guess?
  5. **Await**: is every client call awaited?
  6. **Errors**: is `SudoMockError` caught, with `status` and `code` surfaced?

  If any check **fails**, **stop** and revise until compliance is achieved.

  The agent-facing summary of this API is at
  [https://sudomock.com/docs/skill.md](https://sudomock.com/docs/skill.md).
</Prompt>

## Prerequisites

Before you start, you will need:

* A SudoMock [API key](/docs/dashboard/api-keys)
* An [uploaded PSD mockup](/docs/api-reference/psd-mockups/create-a-mockup-from-a-psd)

## Guide

<Steps>
  <Step title="Install">
    Get the [SudoMock Node SDK](/docs/sdks).

    <CodeGroup>
      ```bash npm theme={"theme":{"light":"github-light","dark":"vesper"}}
      npm install sudomock
      ```

      ```bash pnpm theme={"theme":{"light":"github-light","dark":"vesper"}}
      pnpm add sudomock
      ```

      ```bash yarn theme={"theme":{"light":"github-light","dark":"vesper"}}
      yarn add sudomock
      ```

      ```bash bun theme={"theme":{"light":"github-light","dark":"vesper"}}
      bun add sudomock
      ```
    </CodeGroup>
  </Step>

  <Step title="Add your key">
    Put the key in `.env.local`, which Next.js loads for you. Leave the
    `NEXT_PUBLIC_` prefix off, since it inlines the value into the browser
    bundle.

    ```bash .env.local theme={"theme":{"light":"github-light","dark":"vesper"}}
    SUDOMOCK_API_KEY=sm_your_api_key
    ```
  </Step>

  <Step title="Render from a route handler">
    Create a route file under `app/api/render/route.ts`, or
    `pages/api/render.ts` if you are using the Pages Router. Both UUIDs come
    from your [upload](/docs/api-reference/psd-mockups/create-a-mockup-from-a-psd).

    <CodeGroup>
      ```ts app/api/render/route.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
      import SudoMock, { SudoMockError } from 'sudomock'

      const sudomock = new SudoMock()

      const MOCKUP = '8f2c1d90-6b4e-4a37-9e55-0d1c7a3f21b8'
      const SMART_OBJECT = 'b41a7e52-93c8-4d61-8f07-2ae5c9d04713'

      export async function POST(request: Request) {
        const { artworkUrl } = await request.json()

        try {
          const render = await sudomock.renders.create({
            mockupId: MOCKUP,
            smartObjects: [
              { uuid: SMART_OBJECT, asset: { url: artworkUrl } },
            ],
            exportOptions: { imageFormat: 'webp', imageSize: 1600 },
          })

          return Response.json({ url: render.url })
        } catch (error) {
          if (error instanceof SudoMockError) {
            return Response.json(
              { error: error.code },
              { status: error.status || 502 },
            )
          }
          throw error
        }
      }
      ```

      ```ts pages/api/render.ts theme={"theme":{"light":"github-light","dark":"vesper"}}
      import type { NextApiRequest, NextApiResponse } from 'next'
      import SudoMock, { SudoMockError } from 'sudomock'

      const sudomock = new SudoMock()

      const MOCKUP = '8f2c1d90-6b4e-4a37-9e55-0d1c7a3f21b8'
      const SMART_OBJECT = 'b41a7e52-93c8-4d61-8f07-2ae5c9d04713'

      export default async function handler(
        req: NextApiRequest,
        res: NextApiResponse,
      ) {
        const { artworkUrl } = req.body

        try {
          const render = await sudomock.renders.create({
            mockupId: MOCKUP,
            smartObjects: [
              { uuid: SMART_OBJECT, asset: { url: artworkUrl } },
            ],
            exportOptions: { imageFormat: 'webp', imageSize: 1600 },
          })

          res.status(200).json({ url: render.url })
        } catch (error) {
          if (error instanceof SudoMockError) {
            res.status(error.status || 502).json({ error: error.code })
            return
          }
          throw error
        }
      }
      ```
    </CodeGroup>

    <Note>
      A connection failure or a client side timeout reports `status` as `0`,
      which is why the handler falls back to `502`. A synchronous render holds
      the response open until the image is ready, so a route with a short
      maximum duration is better off passing `isAsync: true` and taking the
      finished image from a [webhook](/docs/webhooks/overview).
    </Note>
  </Step>
</Steps>

## Next steps

<CardGroup cols={3}>
  <Card title="Render a PSD mockup" icon="image" href="/docs/api-reference/psd-mockups/render-a-psd-mockup">
    Every field on the render call
  </Card>

  <Card title="Create a mockup from a PSD" icon="upload" href="/docs/api-reference/psd-mockups/create-a-mockup-from-a-psd">
    Upload a template and read its UUIDs
  </Card>

  <Card title="Fit and blend modes" icon="crop" href="/docs/concepts/fit-and-blend-modes">
    How artwork meets the print area
  </Card>

  <Card title="Text layers" icon="type" href="/docs/text/text-layers">
    Swap copy on the same template
  </Card>

  <Card title="Webhooks" icon="bell" href="/docs/webhooks/overview">
    Get called back when a background render finishes
  </Card>

  <Card title="Errors" icon="triangle-alert" href="/docs/errors">
    Every status and error code, and which ones retry
  </Card>
</CardGroup>
