# Personalized orders > Send different artwork with every order — names, photos or generated designs — by URL or uploaded file, without creating a product first. Source: https://swaglists.com/docs/personalized-orders For products that are different for every customer (a name on the back of a jersey, a pet photo on a mug, a generated poster), order a **catalog variant with inline designs** instead of a SKU. You do not need to create a product first. ```json { "catalog_variant_id": "cv_01k8w1a2s7d9f1g3h5j7k9m1n3", "quantity": 1, "designs": [ { "placement": "front", "layers": [{ "file_id": "file_01k8x3y1q3w5e7r9t1y3u5i7o9" }] }, { "placement": "back", "layers": [{ "url": "https://cdn.example.com/personalized/order-1042-back.png" }] } ] } ``` - `catalog_variant_id` — the exact color/size from `GET /catalog/products/{id}` (`variants[].id`). - `designs` — the same format as product [designs](https://swaglists.com/docs/designs.md): one entry per placement, layers with `file_id` **or** `url`, optional `position` and `opacity`. Every `required` placement must have artwork. Inline items are validated like products (artwork type, size, resolution) and priced like products — the estimate includes extra placements. ## Two ways to send the artwork | | Upload first (`file_id`) | Send a URL (`url`) | | --- | --- | --- | | How | `POST /files` (multipart or `{ "url": … }`) → use the `file_…` id | Put the public image URL in the layer | | When it is downloaded | During the upload call | While we create the order | | Errors surface | On upload (`file_invalid`, `file_too_large`, `download_failed`, `download_not_an_image`, …) | On order creation: a `400` (`download_failed`, `download_not_an_image`, `file_invalid`, `file_download_failed`, …) with `param` = `items[i].designs[j].layers[k].url` | | Best for | Retries, large files, reusing artwork | Simplicity — one request | URLs must be publicly reachable over http(s) (no login, no short-lived signed URL that might expire before we fetch it) and return the image itself. A link to a web page — a file host's share page, for example — fails with `download_not_an_image`: use the host's direct download link instead. - We follow up to 5 redirects, and retry once when the host answers with a temporary error (a dropped connection or HTTP 429, 502, 503, 504) — so a single CDN hiccup doesn't fail your order. - Images are checked before they are decoded: at most 30,000 px on a side and 250 megapixels in total (`file_too_large`). - Photos with an EXIF rotation are stored upright, and CMYK images are converted to RGB, so what you see in the preview is what is printed. - Downloading the same image again (for example when you retry an order) reuses the file we already stored instead of keeping a copy. ## Example: a name on the back Generate the personalized image in your backend (any image library), host it or upload it, then order: ```bash title="cURL" curl -X POST https://swaglists.com/api/v1/orders \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: store-order-2087" \ -d '{ "external_id": "2087", "recipient": { "name": "Sam Lee", "address1": "9 Elm St", "city": "Austin", "state_code": "TX", "postal_code": "78701", "country_code": "US" }, "items": [{ "catalog_variant_id": "cv_01k8w1a2s7d9f1g3h5j7k9m1n3", "quantity": 1, "external_line_id": "line-1", "designs": [ { "placement": "front", "layers": [{ "file_id": "file_01k8x3y1q3w5e7r9t1y3u5i7o9" }] }, { "placement": "back", "layers": [{ "url": "https://cdn.example.com/names/sam-lee-23.png", "position": { "x": 0.1, "y": 0.1, "width": 0.8, "height": 0.3 } }] } ] }] }' ``` ```js title="Node.js" const res = await fetch("https://swaglists.com/api/v1/orders", { method: "POST", headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": `store-order-${storeOrder.id}`, }, body: JSON.stringify({ external_id: String(storeOrder.id), recipient, items: [ { catalog_variant_id: "cv_01k8w1a2s7d9f1g3h5j7k9m1n3", quantity: 1, designs: [ { placement: "front", layers: [{ file_id: LOGO_FILE_ID }] }, { placement: "back", layers: [{ url: personalizedImageUrl }] }, // centered & fitted when no position ], }, ], }), }); ``` ```python title="Python" order = requests.post( "https://swaglists.com/api/v1/orders", headers={"Authorization": f"Bearer {os.environ['API_KEY']}", "Idempotency-Key": f"store-order-{store_order_id}"}, json={ "external_id": str(store_order_id), "recipient": recipient, "items": [{ "catalog_variant_id": "cv_01k8w1a2s7d9f1g3h5j7k9m1n3", "quantity": 1, "designs": [ {"placement": "front", "layers": [{"file_id": LOGO_FILE_ID}]}, {"placement": "back", "layers": [{"url": personalized_image_url}]}, ], }], }, ).json() ``` The position above makes the name box 80% of the area wide and 30% high, near the top — make the image's aspect ratio match the box (see [keeping the aspect ratio](https://swaglists.com/docs/designs.md#position)) or omit `position` to fit it automatically. ## Mixing personalized and regular items One order can contain both SKU items and inline items: ```json "items": [ { "sku": "CA-TEE-BLK-M", "quantity": 2 }, { "catalog_variant_id": "cv_01k8w1a2s7d9f1g3h5j7k9m1n3", "quantity": 1, "designs": [ … ] } ] ``` ## Tips - **Price check:** send the same body to `POST /orders/estimate` first, or show your customers a fixed price per product and placement. - **Resolution:** generate images at the placement's `recommended_width_px` (300 DPI); low-resolution artwork is rejected below 75 DPI. - **Storefront personalization fields:** Shopify line item `properties` and WooCommerce item meta carry the customer's input — render the image from them in your webhook handler, then send the order. See the [Shopify recipe](https://swaglists.com/docs/shopify.md). - Inline items show `product_id: null` and `sku: null` on the order, and `designs` is exactly what will be printed. - **Previews:** a few seconds after the order is created, `mockup_url` shows your design on the product, rendered from the order's own `designs` (it never changes when you later edit a product). Until it is rendered — and in the `order.created` webhook, which is built when the order is created — `mockup_url` is the catalog photo of the variant. Fetch the order again (`GET /orders/{id}`) to get the rendered image; `mockup_url` links don't expire.