> ## 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 Laravel

> Render a PSD mockup from Laravel with the Http client.

<Prompt description="Use this pre-built prompt to get an agent writing SudoMock HTTP calls correctly." icon="sparkles" actions={["copy", "cursor"]}>
  # Render mockups with the SudoMock HTTP API

  **Purpose:** Enforce only the current and correct instructions for rendering
  mockups with the [SudoMock](https://sudomock.com/) HTTP API from a language
  that has no official SDK.
  **Scope:** All AI-generated advice or code that calls SudoMock over HTTP
  must follow these guardrails.

  ***

  ## **1. Official SudoMock HTTP setup**

  ### **Prerequisites**

  The human must first create an API key at
  [https://sudomock.com/docs/dashboard/api-keys](https://sudomock.com/docs/dashboard/api-keys)
  and have a PSD or PSB reachable over HTTPS.

  Keys begin with `sm_` and are stored in an environment variable called
  `SUDOMOCK_API_KEY`.

  ### **Authenticate**

  The base URL is `https://api.sudomock.com`. Every request carries the key in
  the `x-api-key` header and sends JSON.

  ```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
  curl https://api.sudomock.com/api/v1/me \
    -H "x-api-key: $SUDOMOCK_API_KEY"
  ```

  ### **Upload a template once**

  ```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
  curl https://api.sudomock.com/api/v1/psd/upload \
    -H "x-api-key: $SUDOMOCK_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "psd_file_url": "https://example.com/heavyweight-tee.psd",
      "psd_name": "Heavyweight tee front"
    }'
  ```

  Store `data.uuid` and the `uuid` of every entry in `data.smart_objects` and
  `data.text_layers`. Upload once per template, never once per render.

  ### **Render it**

  ```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
  curl https://api.sudomock.com/api/v1/renders \
    -H "x-api-key: $SUDOMOCK_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "mockup_uuid": "MOCKUP_UUID",
      "smart_objects": [
        {
          "uuid": "SMART_OBJECT_UUID",
          "asset": { "url": "https://example.com/artwork.png" }
        }
      ],
      "export_options": {
        "image_format": "webp",
        "image_size": 2048
      }
    }'
  ```

  The finished image is `data.print_files[0].export_path`.

  ***

  ## **2. Complete parameter reference**

  ### **`POST /api/v1/psd/upload`**

  | Parameter      | Type      | Description                                            |
  | -------------- | --------- | ------------------------------------------------------ |
  | `psd_file_url` | `string`  | Required. HTTPS URL of the PSD or PSB.                 |
  | `psd_name`     | `string`  | Optional name for the template.                        |
  | `is_async`     | `boolean` | Read the file in the background and answer with a job. |

  ### **`POST /api/v1/renders`**

  | Parameter        | Type       | Description                                     |
  | ---------------- | ---------- | ----------------------------------------------- |
  | `mockup_uuid`    | `string`   | Required. Template UUID returned by the upload. |
  | `smart_objects`  | `object[]` | Artwork and colour, one entry per smart object. |
  | `text_layers`    | `object[]` | Replacement copy, one entry per text layer.     |
  | `group_layers`   | `object[]` | Outline colour for a listed group layer.        |
  | `export_options` | `object`   | Format, width and quality of the output.        |
  | `export_label`   | `string`   | Optional label for the export file.             |
  | `is_async`       | `boolean`  | Enqueue the render and answer with a job.       |

  At least one of `smart_objects` or `text_layers` is required.

  ### **`smart_objects[]`**

  | Field               | Type     | Description                                                            |
  | ------------------- | -------- | ---------------------------------------------------------------------- |
  | `uuid`              | `string` | Required. Smart object UUID from the upload.                           |
  | `asset`             | `object` | The artwork to place.                                                  |
  | `color`             | `object` | `hex` or a saved `label`, plus an optional `blending_mode`.            |
  | `adjustment_layers` | `object` | `brightness`, `contrast`, `opacity`, `saturation`, `vibrance`, `blur`. |

  ### **`smart_objects[].asset`**

  | Field                               | Type      | Description                                                        |
  | ----------------------------------- | --------- | ------------------------------------------------------------------ |
  | `url`                               | `string`  | HTTPS URL of the artwork.                                          |
  | `base64`                            | `string`  | Artwork bytes. Takes priority over `url`.                          |
  | `content_type`                      | `string`  | Media type of the bytes sent as `base64`.                          |
  | `fit`                               | `string`  | How the artwork meets the area. The default never distorts.        |
  | `rotate`                            | `number`  | Rotation in degrees.                                               |
  | `flip_horizontal` / `flip_vertical` | `boolean` | Mirror the artwork.                                                |
  | `size` / `position`                 | `object`  | Place the artwork by hand instead of by fit mode.                  |
  | `remove_background`                 | `boolean` | Isolate the subject before placing it. Charged per unique artwork. |

  ### **`text_layers[]`**

  | Field       | Type     | Description                                           |
  | ----------- | -------- | ----------------------------------------------------- |
  | `uuid`      | `string` | Required. Text layer UUID from the upload.            |
  | `text`      | `string` | Replacement copy, 1 to 500 characters.                |
  | `segments`  | `array`  | Per-segment copy for a layer that carries two styles. |
  | `font`      | `string` | Font UUID or PostScript name.                         |
  | `font_size` | `number` | Size at the template's native resolution.             |
  | `color`     | `string` | Six-digit hex value.                                  |
  | `fit`       | `string` | `shrink`, `clip` or `overflow`. Default `overflow`.   |

  ### **`export_options`**

  | Field          | Type                                                             | Default |
  | -------------- | ---------------------------------------------------------------- | ------- |
  | `image_format` | `png`, `jpg` or `webp`                                           | `webp`  |
  | `image_size`   | `number`, 100 to 10000 px wide                                   | `2048`  |
  | `quality`      | `number`, 1 to 100, PNG ignores it                               | `90`    |
  | `dpi`          | `number`, 72 to 2400, a metadata tag that does not change pixels | none    |

  ### **Response**

  ```json theme={"theme":{"light":"github-light","dark":"vesper"}}
  {
    "data": {
      "render_uuid": "c315f78f-d2c7-4541-b240-a9372842de94",
      "print_files": [
        {
          "export_path": "https://cdn.sudomock.com/renders/tee.webp"
        }
      ]
    },
    "success": true
  }
  ```

  Each print file also carries `smart_object_uuid`, the layer its artwork was
  placed into.

  ***

  ## **3. Long renders**

  Send `is_async` as `true` and the call answers `202` at once with a `job_id`
  instead of an image.

  ```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
  curl https://api.sudomock.com/api/v1/jobs/JOB_ID \
    -H "x-api-key: $SUDOMOCK_API_KEY"
  ```

  The job carries `status`, which is `succeeded`, `failed`, `ready` or
  `rejected`, and `result_url` once it succeeds. A registered webhook endpoint
  delivers the same outcome without polling.

  ***

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

  ### **4.1 - ALWAYS DO THE FOLLOWING**

  1. **Keep the key in the environment** and on the server side only.
  2. **Send it in the `x-api-key` header** on every request.
  3. **Read the HTTP status** before reading the body.
  4. **Branch on `error_code`** and keep a default case that surfaces
     `message` and `details.suggestion`.
  5. **Upload a template once** and store the UUIDs it returns.
  6. **Use the framework's own HTTP client** rather than adding a dependency.

  ### **4.2 - NEVER DO THE FOLLOWING**

  1. **Do not** hardcode an `sm_` key in source, in a bundle, or in any code
     that ships to a browser.
  2. **Do not** send an `Authorization` header. This API does not accept one.
  3. **Do not** invent a field name. If it is not in the documentation, it does
     not exist.
  4. **Do not** retry `400`, `401`, `402`, `403`, `404` or `422`. Fix the
     request or the billing state first.
  5. **Do not** upload a PSD that is already a template. Render against the
     stored UUID.

  ***

  ## **5. Common patterns**

  ### **Errors**

  Every failure answers with JSON. Upload and render failures add a
  machine-readable `error_code` and a `details.suggestion`.

  | Status       | Meaning                                             | What to do                                      |
  | ------------ | --------------------------------------------------- | ----------------------------------------------- |
  | `400`        | Malformed request, or a PSD that could not be read. | Fix the input.                                  |
  | `401`        | The key is missing, malformed or revoked.           | Check the header.                               |
  | `402`        | The request cannot be paid for.                     | Add credit or a payment method.                 |
  | `404`        | No such template, layer or job.                     | Re-read the UUIDs.                              |
  | `422`        | Body validation failed.                             | Fix the body. Repeating it repeats the failure. |
  | `429`        | Rate limit or concurrency limit.                    | Wait the seconds given in `Retry-After`.        |
  | `500`, `502` | Server side, or an upstream image source.           | Retry with backoff.                             |

  ### **Retry on a rate limit**

  Retry `429`, `500`, `502`, `503`, `504` and network errors with backoff. A
  `429` carries `Retry-After` in the response headers, and that is the exact
  number of seconds to wait.

  ### **Replace copy instead of artwork**

  ```json theme={"theme":{"light":"github-light","dark":"vesper"}}
  {
    "mockup_uuid": "MOCKUP_UUID",
    "text_layers": [
      { "uuid": "TEXT_LAYER_UUID", "text": "Limited edition" }
    ]
  }
  ```

  ### **Read the account before promising a size**

  An account still in trial renders up to 1024 px wide. An `image_size` above
  that answers `OUTPUT_RESOLUTION_LIMIT` rather than quietly shrinking the
  image, so the width you asked for is the width you get. `GET /api/v1/me`
  reports the account.

  ***

  ## **6. AI model verification steps**

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

  1. **Header**: is `x-api-key` sent on every request, read from the
     environment?
  2. **UUIDs**: does the render use UUIDs an upload returned, not invented
     ones?
  3. **Status**: is the HTTP status checked before the body is read?
  4. **Errors**: does the code branch on `error_code` and keep a default case?
  5. **Retries**: are only `429`, `500`, `502`, `503` and `504` repeated?
  6. **Result**: does the code read `data.print_files[0].export_path`?

  If any check **fails**, **stop** and revise until compliance is achieved.
  Then confirm against the account: `GET /api/v1/me` answers `200`, and one
  render answers with a path that loads an image.

  Every error code and its retry rule: [https://sudomock.com/docs/errors](https://sudomock.com/docs/errors)

  For the entire docs for SudoMock, see [https://sudomock.com/docs/llms-full.txt](https://sudomock.com/docs/llms-full.txt)
</Prompt>

## Prerequisites

Before you start, you'll need:

* A SudoMock [API key](/docs/dashboard/api-keys)
* A [PSD reachable over HTTPS](/docs/psd-mockups/preparing-a-psd)

## Guide

<Steps>
  <Step title="Set your API key">
    The Http client ships with the framework, so there is nothing to install.
    Put the key in the environment and read it through a config entry. The two
    UUIDs stay empty until the next step fills them.

    <CodeGroup>
      ```bash .env theme={"theme":{"light":"github-light","dark":"vesper"}}
      SUDOMOCK_API_KEY=sm_your_api_key
      SUDOMOCK_MOCKUP_UUID=
      SUDOMOCK_SMART_OBJECT_UUID=
      ```

      ```php config/services.php theme={"theme":{"light":"github-light","dark":"vesper"}}
      'sudomock' => [
          'key' => env('SUDOMOCK_API_KEY'),
          'mockup' => env('SUDOMOCK_MOCKUP_UUID'),
          'smart_object' => env('SUDOMOCK_SMART_OBJECT_UUID'),
      ],
      ```
    </CodeGroup>

    One macro gives every call the same base URL, header and timeout.

    ```php app/Providers/AppServiceProvider.php theme={"theme":{"light":"github-light","dark":"vesper"}}
    use Illuminate\Support\Facades\Http;

    public function boot(): void
    {
        Http::macro('sudomock', function () {
            return Http::baseUrl('https://api.sudomock.com')
                ->withHeaders([
                    'x-api-key' => config('services.sudomock.key'),
                ])
                ->acceptJson()
                ->timeout(120);
        });
    }
    ```

    <Note>
      Read the key with `config()` rather than `env()`. Once the config is
      cached in production, `env()` answers `null` and every request comes
      back `401`.
    </Note>
  </Step>

  <Step title="Upload the template once">
    The upload returns every layer you can address later, each with its own
    UUID. An Artisan command keeps that out of the request path.

    ```php app/Console/Commands/ImportMockup.php theme={"theme":{"light":"github-light","dark":"vesper"}}
    <?php

    namespace App\Console\Commands;

    use Illuminate\Console\Command;
    use Illuminate\Support\Facades\Http;

    class ImportMockup extends Command
    {
        protected $signature = 'sudomock:import {url} {name}';

        public function handle(): int
        {
            $response = Http::sudomock()
                ->post('/api/v1/psd/upload', [
                    'psd_file_url' => $this->argument('url'),
                    'psd_name' => $this->argument('name'),
                ])
                ->throw();

            $data = $response->json('data');

            $this->line('mockup: '.$data['uuid']);

            foreach ($data['smart_objects'] as $object) {
                $this->line($object['name'].': '.$object['uuid']);
            }

            return self::SUCCESS;
        }
    }
    ```

    ```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
    php artisan sudomock:import \
      https://example.com/heavyweight-tee.psd "Heavyweight tee"
    ```

    Copy the mockup UUID and the UUID of the smart object you want to fill
    into `.env`. A template is uploaded once and rendered as often as you
    like.
  </Step>

  <Step title="Render from a route">
    The route takes an artwork URL and answers with the finished image. On
    Laravel 11 and newer, `php artisan install:api` creates `routes/api.php`
    if the application does not have one yet.

    ```php routes/api.php theme={"theme":{"light":"github-light","dark":"vesper"}}
    use App\Http\Controllers\RenderMockupController;

    Route::post('/mockups', RenderMockupController::class);
    ```

    ```php app/Http/Controllers/RenderMockupController.php theme={"theme":{"light":"github-light","dark":"vesper"}}
    <?php

    namespace App\Http\Controllers;

    use Illuminate\Http\JsonResponse;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Http;

    class RenderMockupController extends Controller
    {
        public function __invoke(Request $request): JsonResponse
        {
            $input = $request->validate([
                'artwork_url' => ['required', 'url'],
            ]);

            $mockup = config('services.sudomock.mockup');
            $layer = config('services.sudomock.smart_object');

            $response = Http::sudomock()
                ->post('/api/v1/renders', [
                    'mockup_uuid' => $mockup,
                    'smart_objects' => [[
                        'uuid' => $layer,
                        'asset' => ['url' => $input['artwork_url']],
                    ]],
                    'export_options' => [
                        'image_format' => 'webp',
                        'image_size' => 2048,
                    ],
                ])
                ->throw();

            return response()->json([
                'image' => $response->json(
                    'data.print_files.0.export_path'
                ),
            ]);
        }
    }
    ```
  </Step>
</Steps>

## Examples

<CardGroup cols={3}>
  <Card title="Create a mockup from a PSD" icon="square-arrow-out-up-right" href="/docs/api-reference/psd-mockups/create-a-mockup-from-a-psd">
    Every field of the upload call
  </Card>

  <Card title="Render a PSD mockup" icon="square-arrow-out-up-right" href="/docs/api-reference/psd-mockups/render-a-psd-mockup">
    Every field of the render call
  </Card>

  <Card title="Retrieve a single job" icon="square-arrow-out-up-right" href="/docs/api-reference/jobs/retrieve-a-single-job">
    Collect an async render
  </Card>

  <Card title="Create a webhook endpoint" icon="square-arrow-out-up-right" href="/docs/api-reference/webhook-endpoints/create-a-new-webhook-endpoint">
    Get called back instead of polling
  </Card>

  <Card title="Fit and blend modes" icon="square-arrow-out-up-right" href="/docs/concepts/fit-and-blend-modes">
    How artwork meets a print area
  </Card>

  <Card title="Errors" icon="square-arrow-out-up-right" href="/docs/errors">
    Every code, and which ones are worth retrying
  </Card>
</CardGroup>
