API, MCP & CLI

API: Presets

Read a ListingView preset section by section, look up print-on-demand products, attributes, shop profiles and mockup templates, then create, edit and copy print-on-demand, digital and physical presets through the API.

Updated

Connect Claude, ChatGPT or Cursor to ListingView to research what's selling on Etsy, update your Etsy shop, manage your listings.

A preset holds everything ListingView needs to turn a design into a listing: listing details, shop settings, print-on-demand products and prices, design slots, mockups, photos, digital files and Etsy variations. Through the API you can read a preset section by section, look up what presets are built from, and create, edit and copy print-on-demand, digital and physical presets.

Every write is checked the way the app editor saves, then refused whole if anything is wrong. Nothing is trimmed or dropped to make a write fit.

New to the API? Start with API: Getting started. For what presets do in the app, see Presets and POD Presets & Bulk Create.

What you need

  • Scopes. Reads need presets:read. Writes need presets:write. A Drafting Agent key holds both.

  • Plan. Reading presets and their building blocks works on every plan with API access. So do writes to digital and physical presets. Every write to a print-on-demand preset needs the Plus plan or higher: creates, edits, copies and renames. Below Plus they answer PLAN_INSUFFICIENT.

  • Ready files. Designs, photos and deliverables must be uploaded and show ready: true first. See API: Uploads.

  • Daily limits. An account can create or copy 100 presets a day through the API (DATA_BUDGET_EXCEEDED with the reason preset_writes_daily past it) and delete 20 (preset_deletes_daily). Edits don't count.

Read a preset

GET /presets/{preset_id} answers the preset's standard view: listing defaults, shop bindings, print-on-demand products and the design slots, each with its letter, its label, whether each listing brings its own design, and the garments and colors it prints on.

curl https://api.listingview.io/api/v1/presets/1667 \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY"
{
  "view": "preset.v1",
  "id": 1667,
  "name": "Halloween sweatshirt — light + dark",
  "type": "pod",
  "updated_at": "2026-10-14T18:22:05.123Z",
  "slots": [
    {
      "slot_id": "A",
      "position": 1,
      "label": "Light Designs Here",
      "enabled": true,
      "fill": "upload_required",
      "prints_on": [
        { "product": "Gildan 18000", "provider": "Monster Digital", "print_area": "front", "colors": ["Black", "Navy", "Forest Green"], "garment_tone": "dark" }
      ],
      "summary": "A · \"Light Designs Here\" · prints on dark garments (Black, Navy, Forest Green)"
    }
  ],
  "pod_products": [
    { "product": "Gildan 18000", "provider": "Monster Digital", "colors_selected": ["Black", "Navy", "Forest Green", "White", "Sand", "Ash"], "sizes_selected": ["S", "M", "L", "XL", "2XL"], "variant_count": 30, "price_range": { "min": 34.99, "max": 38.99 } }
  ],
  "mockups": { "count": 2, "slots": [{ "index": 0, "template_id": 77120, "design_slot": "A" }, { "index": 1, "template_id": 77121, "design_slot": "B" }] }
}

The answer is shortened here. Add check_shop_id to check the preset's shipping profile, return policy, section, production partners and processing profile against one of your shops. shop_check.bindings[] then marks each one valid, invalid (with that shop's target_options) or unverified.

A preset whose saved settings ListingView can't read still answers. Its sections are empty and warnings[] carries preset_data_unreadable: open it in ListingView and save it again.

More sections: include

Add include to get more, comma-separated:

include

What it adds

pod_variants

Every variant of each print-on-demand product: options, price, enabled, available and base_cost.

targets

Where each slot prints: product, print area with its size in inches, colors and placement.

mockups

Each mockup entry with design_slots_by_region and placement.

photos

The preset's static photos, by asset id, with thumbnails.

digital

Deliverables, design files, the destination folder and delivery-PDF settings.

variations

Etsy variations, the variation inventory and which variations set price, quantity and SKU.

properties

The category and the attributes Etsy shows buyers.

spec

The preset in the exact shape a create accepts. See below.

all

Everything above.

  • Entry refs. Entries carry an opaque ref, such as pp_… for a product or sl_… for a slot. Edits use these refs to name an entry.

  • etsy_type is physical, download or both.

  • A section ListingView can't read comes back null and is named in unreadable_sections[]. The rest of the answer still arrives.

  • Values the write model can't carry exactly are listed in unaddressable[], each with its effect: read_only, kept or changed_on_write.

  • File URLs. The only file URL in a preset read is a thumbnail_url. A link deliverable returns the address the seller typed. A link that points at ListingView or its storage reads url: null with redacted: "internal_url".

include=spec: when it is offered

spec is the preset in exactly the shape POST /presets takes, so a simple preset can be read, changed and posted back as a new one. It is returned only when ListingView can prove that writing it back recreates the preset exactly. Today that means:

  • Withheld for most presets. A preset with print-on-demand products, slots, mockups, photos, digital files, attributes or a category reads spec: null with spec_unavailable_reason: "spec_not_writable". unaddressable[] names each of those sections with family_unverified.

  • Presets made in the app carry their shop, which a create can't recreate. They read spec: null, and unaddressable[] says content_not_recreatable.

  • Other gaps. not_writable, round_trip_mismatch and spec_too_large (the spec would be over the preset body limit) also leave spec null. A two-variation preset whose app inventory prices some combinations from the per-variation cells reads per_axis_fallback. None of these refuse the read.

For any other preset, read the sections you need with the other includes and build the create body from them.

The building blocks

These reads list what presets are built from. They need presets:read, cost nothing, and work on every plan with API access.

Print-on-demand products

Search the print-on-demand catalog, then drill down to a product's providers, then to one provider's variants and print areas.

curl "https://api.listingview.io/api/v1/pod-products?q=comfort%20colors%201717" \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY"
{
  "products": [{
    "product_id": 706,
    "name": "Unisex Garment-Dyed T-shirt",
    "brand": "Comfort Colors",
    "model": "1717",
    "supplier": "printify",
    "category": { "id": "mens-t-shirts", "name": "T-Shirts" },
    "thumbnail": "https://images.printify.com/5853fec7ce46f30f8328200a/regular.jpg",
    "provider_count": 6,
    "min_cost": 12.29,
    "currency": "USD",
    "pod_catalog_as_of": "2026-10-25T05:12:44.000Z"
  }],
  "next_cursor": null,
  "hint": null,
  "warnings": []
}
  • Search (GET /pod-products): every word of q must match, up to 10 words. Filter by category, supplier (printify), min_cost and max_cost, and page with limit and cursor. product_id is the Printify blueprint id.

  • A product (GET /pod-products/{product_id}) lists its active providers[]: provider_id, name, recommended (Printify's default), location, in-stock colors[] (name, hex) and sizes[], min_cost, max_cost and shipping_summary[].

  • A provider (GET /pod-products/{product_id}/providers/{provider_id}) lists every variants[] row, out-of-stock ones with available: false, and its print_areas[]. A slot target names a print area by its id (such as pa_1) or its name:

{
  "product_id": 706,
  "product_name": "Comfort Colors 1717",
  "provider_id": 99,
  "provider_name": "Printify Choice",
  "currency": "USD",
  "variants": [
    { "options": { "color": "Black", "size": "S" }, "color_hex": "#1a1a1a", "base_cost": 12.29, "available": true },
    { "options": { "color": "Ivory", "size": "S" }, "color_hex": "#f2ecdf", "base_cost": 12.29, "available": false }
  ],
  "print_areas": [
    { "id": "pa_1", "name": "front", "position": "front", "decoration_method": "dtg", "width_in": 12, "height_in": 16, "width_px": 3600, "height_px": 4800, "dpi": 300 }
  ],
  "pod_catalog_as_of": "2026-10-25T05:12:44.000Z",
  "warnings": []
}

Answers come from ListingView's nightly copy of Printify's catalog, never a live Printify call. Every answer carries pod_catalog_as_of. When the copy is more than 8 days old, warnings[] says pod_catalog_stale: costs, colors or print areas may have changed since.

  • Not synced yet. A product ListingView hasn't synced answers NOT_FOUND with the reason pod_blueprint_unknown; one Printify dropped, pod_blueprint_unavailable. An unknown or inactive provider answers pod_provider_unknown or pod_provider_inactive, with candidates.

  • Still syncing. While the catalog or a provider's variants are still syncing, you get a retryable SERVICE_UNAVAILABLE with the reason pod_catalog_pending.

Category attributes

GET /etsy-taxonomy/{taxonomy_id}/properties lists the attributes Etsy offers for a category.

{
  "taxonomy_id": 482,
  "properties": [{
    "property_id": 200,
    "name": "Primary color",
    "required": false,
    "supports_variations": true,
    "supports_attributes": true,
    "scales": [],
    "possible_values": [
      { "value_id": 1, "name": "Black", "scale_id": null },
      { "value_id": 2, "name": "Blue", "scale_id": null }
    ],
    "max_values": 1
  }],
  "unverified": [],
  "unverified_reason": null
}

Properties that can only be a variation aren't listed. Answers come from ListingView's cache. On a miss a live key reads Etsy, which counts against your daily Etsy-call budget. A category Etsy doesn't know answers NOT_FOUND. A test key reads the cache only: a category that isn't cached answers properties: null with unverified_reason: "test_key_cache_miss".

Shop profiles

GET /my-shop/profiles?shop_id=38123456 lists the shop-specific choices a preset binds to, for one of your shops: shipping_profiles, return_policies, sections, production_partners and processing_profiles, each a list of {id, label, …}. With one connected shop you can leave shop_id out.

{
  "shop_id": 38123456,
  "shipping_profiles": null,
  "return_policies": [{ "id": 1234567890, "label": "Returns within 30 days", "accepts_returns": true, "accepts_exchanges": true, "return_deadline_days": 30 }],
  "sections": [{ "id": 45678901, "label": "Sweatshirts" }],
  "production_partners": [{ "id": 3456789, "label": "Monster Digital", "location": "US" }],
  "processing_profiles": [{ "id": 987654321, "label": "Made to order · 1-3 days", "readiness_state": "made_to_order", "min_processing_days": 1, "max_processing_days": 3 }],
  "unverified": ["shipping_profiles"],
  "unverified_reason": "etsy_unavailable",
  "retry_after": 60
}

A list ListingView couldn't read right now is null and named in unverified[]; the rest still answer. unverified_reason says why: etsy_unavailable, etsy_call_budget, test_key_cache_miss or reconnect_required. These are the same choices a refusal offers in target_options when a preset names another shop's setting. Answers come from ListingView's cache, and a live key reads missing lists from Etsy against your Etsy-call budget. A test key always reads processing_profiles: null.

Mockup templates

GET /mockup-templates lists the templates you can use: yours first, newest first, then ListingView's shared ones (owned: false). It pages with limit and cursor.

{
  "template_id": 77120,
  "name": "flatlay-black-tee.jpg",
  "owned": true,
  "thumbnail_url": "https://d111111abcdef8.cloudfront.net/assets-v2/1042/77120/thumb.webp?Expires=…",
  "preview_url": "https://d111111abcdef8.cloudfront.net/assets-v2/1042/77120/template-preview/9b1e4d7c2a6f8e03b5c1d9a7f4e2c680.jpg?Expires=…",
  "preview_pending": false,
  "width": 3000,
  "height": 2000,
  "has_regions": true,
  "provider": null,
  "revision": "5f0c2a9e1b7d4c3a8e6f1d2b9c0a7e41",
  "regions": [{
    "region_id": "3f1c9a52-6d1e-4b8a-9c3d-2e7f0a1b4c5d",
    "label": "Chest print",
    "fit": "contain",
    "h_align": "center",
    "v_align": "top",
    "blend_mode": "multiply",
    "opacity": 0.95,
    "quad": { "points": [[0.3625, 0.2], [0.6375, 0.2], [0.6375, 0.6125], [0.3625, 0.6125]] }
  }]
}
  • quad.points are the region's four corners relative to the photo. 0 to 1 spans the photo, and a region may overhang it, so a corner can read below 0 or above 1.

  • revision changes whenever the photo or a region is redrawn. Cache anything you build from a template on its revision.

Template previews: preview_url

preview_url is a copy of your own template photo that a tool can place designs on. For example, a script can paste each design into a region's quad to pick the best lead photo.

  • What it is. The whole photo, at most 1,600 px on the long edge. Never the original.

  • Short-lived. The link is signed for up to an hour. List the templates again for a fresh link.

  • Your own JPG and PNG templates only. PSD templates and ListingView's shared templates answer preview_url: null, and have no thumbnail_url either. That includes PSDs registered through SudoMock (provider: "sudomock").

  • Still being made. A new or redrawn template can answer preview_url: null with preview_pending: true. Read it again later. preview_pending: false with no preview means none can be made for that photo.

Prices: one unit, two entry shapes

Every price is a decimal amount in the shop's currency, with at most 2 decimals. No field anywhere is in cents. A price you read can be sent back as it is.

Print-on-demand prices come in two entry shapes. They follow the same rules:

Where

Entry shape

Example

A create's spec.pod_products[].prices, typed ops, a product swap, and listing writes

{options: {color?, size?}, price}

{"options": {"size": "L"}, "price": 26.99}

fields.pod_products[].prices on PATCH /presets/{preset_id}

{color?, size?, price}

{"color": "Maroon", "size": "S", "price": 32.99}

  • Color and size together price one variant.

  • A color alone prices that color in every size. A size alone prices that size in every color.

  • The exact entry wins. A color-and-size entry beats a color-only or size-only entry for the same variant.

  • Conflicts are refused. When a color price and a size price disagree for one variant, the call answers pod_price_ambiguous. Add a color-and-size entry for that variant.

  • Missing prices get a default. On a create, a variant with no price is priced at Printify cost × 1.2, rounded up to the next $0.50. On an edit, so is a variant you newly select that was stored switched off: it is switched on at that price. The answer lists each one in prices_defaulted[]. This is a warning, not a refusal. If the catalog has no cost for a variant, the write is refused.

  • Unpriced variants that are already on are refused (pod_variants_unpriced): send a price for them.

Create a preset

POST /presets takes fields and an optional spec:

  • fields: name (required, and not already used by one of your presets), listing_defaults (title, description, tags, price, quantity, who made it, when it was made, category, materials, SKU) and shop_bindings (shipping profile, return policy, section, production partners, processing profile).

  • spec: the preset's type (pod, digital or physical) and its typed sections: pod_products, slots, mockups, photos, digital, variations, variation_inventory, varies and properties. Without spec you get a physical preset with just the fields you sent.

The smallest create is a name. Fill in the rest later:

{ "fields": { "name": "Ceramic mug — draft" } }

The answer is the new preset, changed[] (every field the write set) and, for a typed write, prices_defaulted[], properties_cleared and warnings[]. Send an X-Idempotency-Key header so a retry can't make a second preset.

A print-on-demand preset from scratch

  1. Find the product and its provider with the print-on-demand product reads above.

  2. Read the provider's print areas, so each slot can target one by id or name.

  3. Pick mockup templates with GET /mockup-templates, and the shop's settings with GET /my-shop/profiles.

  4. Send POST /presets with spec.type: "pod".

This example makes a Comfort Colors 1717 tee from Printify Choice, in Black and Pepper, sizes S to L, priced per size, with one fixed design on the front:

curl -X POST https://api.listingview.io/api/v1/presets \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": {
      "name": "Desert sunset tee — front print",
      "listing_defaults": {
        "title": "Retro Desert Sunset Tee, Comfort Colors Vintage Shirt",
        "description": "Heavyweight ring-spun cotton tee, printed to order and shipped from the US.",
        "tags": ["desert tee", "sunset shirt", "retro graphic tee"],
        "who_made": "someone_else",
        "when_made": "made_to_order",
        "taxonomy_id": 482
      },
      "shop_bindings": {
        "shipping_profile_id": 209876543210,
        "return_policy_id": 1234567890,
        "production_partner_ids": [3456789],
        "readiness_state_id": 987654321
      }
    },
    "spec": {
      "type": "pod",
      "pod_products": [{
        "blueprint_id": 706,
        "print_provider_id": 99,
        "options": { "color": ["Black", "Pepper"], "size": ["S", "M", "L"] },
        "prices": [
          { "options": { "size": "S" }, "price": 24.99 },
          { "options": { "size": "M" }, "price": 24.99 },
          { "options": { "size": "L" }, "price": 26.99 }
        ]
      }],
      "slots": [{
        "label": "Front design",
        "fill": { "fixed": 88130 },
        "targets": [{ "product": 1, "print_area": "front" }]
      }]
    }
  }'

Mockups aren't rendered when you save. Bulk Create renders them when it makes listings.

The print-on-demand sections

  • type is required in spec and can never change. A section that doesn't fit the type, such as digital on a pod preset, is refused with section_not_for_type.

  • pod_products[] (up to 10): blueprint_id, print_provider_id, options (color, size), prices and size_label_prefix. print_provider_id is required: ListingView never picks a provider for you.

  • slots[] (up to 10): label, fill, enabled and targets[]. Slots are lettered A to J by their position among the enabled slots. fill is "upload_required" (each listing brings its design) or {"fixed": asset_id} (the same design every time).

  • targets[] (up to 20 per slot): product (the product's 1-based position), print_area (id or name), options ({"color": [...]} or "all") and placement.

  • placement: fit, alignment, angle, the size and offset percentages, the maximum inches and margins. Size percentages run from 0 to 100, offsets from −100 to 100 and angle from −360 to 360. Inch fields must fit the print area. Leave placement out to get the app's default placement.

  • mockups[]: template_id and design_slot ("A" to "J"), both required. Optional: index (its image slot, 0 to 19), design_slots_by_region ({region_id: slot}, to put another slot's design in some regions) and placement (fit is cover, contain or stretch).

  • photos[]: static photos by index and asset_id, such as a size chart.

What a print-on-demand create checks

  • The provider prints that product and offers every color and size you chose (pod_option_unknown otherwise).

  • At most 100 variants per product (Printify's limit), 70 options per Etsy variation and 400 combinations in all (Etsy's limits). Over a limit answers POD_VARIANT_LIMIT, and details.limit_kind names which.

  • Every color you sell is covered by at least one enabled slot target (slot_without_color otherwise).

  • Every target's print area exists on that provider (slot_print_area_unknown), and every placement fits it (placement_out_of_bounds).

  • Every mockup template is yours or a shared one, and ready. Its region ids are real, and its design_slot is an enabled slot.

  • At most 20 photos and mockups together, Etsy's image limit (images_over_limit).

  • Every fixed design is yours and ready.

Two things aren't offered yet. Writing an auto-converted (derived) slot answers derived_slots_unsupported. A product option other than color and size answers pod_axis_unsupported.

Digital presets

A digital preset delivers files to buyers. Upload the files first with purpose: "digital_file" (see API: Uploads), then create the preset. This example delivers a PDF and a folder of bonus files, saves into a destination folder, shows a cover design on a mockup and adds a size-chart photo:

{
  "fields": { "name": "Printable planner — PDF + bonus pack" },
  "spec": {
    "type": "digital",
    "digital": {
      "deliverables": [
        { "kind": "file", "asset_id": 88123 },
        { "kind": "collection", "collection_id": 4521 }
      ],
      "parent_collection_id": 4522,
      "design_files": [{ "asset_id": 88124, "label": "Planner cover" }]
    },
    "mockups": [{ "index": 0, "template_id": 77120, "design_slot": "A" }],
    "photos": [{ "index": 1, "asset_id": 88125 }]
  }
}
  • Deliverables. A file must be a ready deliverable. Every file in a collection, sub-folders included, must be yours, ready and scanned clean (collection_file_not_ready). An empty collection is refused (collection_empty). A preset needs at least one deliverable (digital_files_required).

  • How buyers get the files. Each listing delivers one PDF with a download button per deliverable. A preset holds up to 50 deliverables, and its collections can bring up to 200 files to one listing (too_many_collection_files).

  • Design files (up to 10) are ready PNG, JPEG or WebP images used on mockups (design_not_image otherwise).

  • Optional. parent_collection_id (where the files are saved), and pdf_settings for the delivery PDF.

  • Old presets. A digital preset that still carries legacy inline files answers legacy_preset_files when an Etsy draft uses it. Re-save its deliverables in ListingView.

Link deliverables

A preset can still hold a link deliverable: {"kind": "link", "url", "label"}. The link must pass ListingView's link screen, or it is refused with link_not_allowed and details.link_rules naming each rule it broke: an https:// URL of at most 2,048 characters, on the standard port, with no user name or password, no IP address and no reserved or internal host, and a plain-text label of at most 100 characters.

Etsy drafts made through the API never deliver a link: a digital draft whose preset or item carries one is refused with link_deliverables_unsupported. Use a file or collection deliverable for anything an API draft delivers. A deliverable that points at a link saved in your link library reads kind: "saved_link": an edit keeps it in place, and a create from spec doesn't carry it.

Physical presets: variations and attributes

Physical and digital presets can carry Etsy variations. Category attributes work on every preset type.

  • variations[] (up to 2): name and options[] (up to 70 each). ListingView sets property_id from the variation's name and position; leave it out or send it as read. An op that sets it is refused (op_not_supported).

  • variation_inventory is by_option or by_combination, never both: setting one replaces the other. Rows are {options, price, quantity, sku, visible}. Quantity runs from 0 to 999, and a SKU can be up to 32 characters. A new preset with two variations takes by_combination (by_option_two_axes otherwise).

  • varies says which variations set the price, quantity and sku.

  • properties[]: property_id, value_ids[] or values[], and an optional scale_id. Attributes need a category (listing_defaults.taxonomy_id) first.

Attributes are checked against the category's properties before anything is saved: the property must exist for the category, use allowed values and a valid scale, and stay within max_values. A property that only works as a variation is refused as an attribute (property_variation_only). If Etsy's category data can't be read, the write answers EXTERNAL_API_ERROR with the reason taxonomy_unavailable and saves nothing. Retry after retry_after.

Changing taxonomy_id clears the category's attributes, and the answer says properties_cleared: true.

Two variations are locked. On a preset with two variations, the API can't change the variations, their cells or varies. Send them exactly as read, or edit them in ListingView. Any other change answers op_not_supported with the reason two_axis_variations_locked. With two variations, an option can't contain ||| (variation_option_delimiter).

Edit a preset

PATCH /presets/{preset_id} changes only what you send. It requires expected_updated_at: the updated_at from your last read. If the preset changed since, nothing is saved and the answer is CONFLICT with the reason preset_changed.

You can send fields, typed ops[], or both. fields covers the name, listing defaults, shop bindings, slot labels and on/off, and, on a print-on-demand preset, each product's colors, sizes and prices among the variants it already has:

curl -X PATCH https://api.listingview.io/api/v1/presets/1831 \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "expected_updated_at": "2026-10-16T09:12:44.512Z",
    "fields": {
      "pod_products": [{
        "position": 1,
        "product": "Gildan 18000",
        "colors_selected": ["Black", "Navy", "Forest Green", "Maroon"],
        "prices": [
          { "size": "S", "price": 34.99 },
          { "size": "M", "price": 34.99 },
          { "size": "2XL", "price": 38.99 },
          { "color": "Maroon", "size": "S", "price": 32.99 }
        ]
      }]
    }
  }'

This sells two more colors and reprices three sizes in every color. Slot changes work the same way, by position:

{
  "expected_updated_at": "2026-10-16T09:12:44.512Z",
  "fields": {
    "slots": [
      { "position": 1, "label": "Light ink (dark garments)" },
      { "position": 3, "enabled": false }
    ]
  }
}

Add check_shop_id to require every shop-specific id to belong to that shop.

Typed ops

For anything else, send ops[]: up to 100 typed operations, each {op, section, ref?, field?, position?, value?, prices?, target?}. Read the preset with include=all first, so you know each entry's ref.

{
  "expected_updated_at": "2026-10-16T09:12:44.512Z",
  "ops": [{ "op": "replace", "section": "photos", "value": [] }]
}
  • add inserts value into a list at position (1-based; the end by default).

  • remove drops the entry ref names.

  • replace with a ref replaces that entry. Without a ref it replaces the whole list; an empty list clears it.

  • set sets one field: of the entry ref names, or of the section. set order puts the named entries first, in that order.

  • Refs are a 1-based position as read, a ref a read returned (pp_…, sl_…, mk_…, ph_… and so on), or, for slots, the letter or the exact label. Refs always mean the preset as read at expected_updated_at, never shifted by earlier ops in the same call. A label two slots share answers ref_ambiguous; an entry an earlier op removed, ref_removed.

  • Order. Ops run before fields. Edit each section once per call (section_edited_twice otherwise).

What ops cover on a print-on-demand preset:

  • Products. Add a product from the catalog, or remove one. A product a slot still targets can be removed only if the same call retargets or removes that slot (slot_target_dangling otherwise).

  • Provider. set print_provider_id switches a product to another provider. Print areas are matched by name. Percentage placements are kept and listed in placement_rescaled[]. An inch field that no longer fits answers placement_out_of_bounds. The product itself (its blueprint_id) can't change in an edit (pod_base_mismatch): to use another product, swap it on a copy.

  • Colors and sizes. set options changes them, including variants the provider offers that the preset didn't have yet. It is the only op that takes prices: pod.v1 entries for the new cells. Any you leave out are listed in prices_defaulted[]. A new color must be covered by a slot.

  • Slots. Add, remove, relabel, switch on or off, retarget or re-place a slot. keep_only keeps the named slots and removes the rest. Mockups follow their slot, not its letter.

  • Mockups and photos. Replace, add, remove, reorder or clear them.

Ops apply in order to one copy of the preset, and the result is checked once at the end, so a step in between may be invalid. The call saves everything or nothing.

The answer carries diff, the sections that changed, and for print-on-demand changes pod_diff: products added, removed or changed, variants added, removed or repriced, and the slots, mockups and photos that changed. To check every variant afterward, read include=pod_variants.

The exact shape of every op is in the API reference.

Print-on-demand edits are strict

A print-on-demand write must leave the whole print-on-demand setup valid, including problems the preset already had: those are listed as "Already on the preset — fix it in this request". If ListingView can't fully read a preset's print-on-demand data, every change to it is refused with pod_preset_unreadable; edit that preset in ListingView. Two known limits:

  • A slot target that points at a product the preset no longer has makes the preset unreadable this way.

  • A select on a preset made in the app overrides a cell the seller had switched off inside the new selection. A row that is already on but has no price is still refused as unpriced.

Copy a preset

POST /presets/{preset_id}/copy copies a preset, optionally with fields and ops[] edits on the copy, and optionally to another of your shops. Without a name the copy is called "Copy of" plus the original's name. A name one of your presets already has answers CONFLICT with the reason name_taken and existing_preset_id. expected_updated_at is optional on a copy.

{
  "preset_id": 2041,
  "fields": {
    "name": "Linen apron — gift edition",
    "listing_defaults": { "price": 48 }
  }
}

preset_id goes in the path. The rest is the body.

Copy to another shop

Send target_shop_id. Shop-specific ids are checked against that shop and never swapped. A mismatch is refused with that shop's options, so you can resend with a top-level shop_bindings chosen from them (not also inside fields; both answer duplicate). A print-on-demand preset copied to another shop needs pod_blanks: "keep" (PRESET_POD_BLANK_ACK_REQUIRED otherwise), and that shop must be linked to Printify (PRINTIFY_SHOP_NOT_MAPPED otherwise). Every print-on-demand copy lists its products in carried_blanks[]:

{
  "target_shop_id": 38123456,
  "fields": { "name": "Halloween sweatshirt — second shop" },
  "shop_bindings": {
    "shipping_profile_id": 209876543299,
    "return_policy_id": null,
    "shop_section_id": null,
    "readiness_state_id": 987654399
  },
  "pod_blanks": "keep"
}

Swap a product

pod_product_ops[] swaps a print-on-demand product of the copy for a product block from your other presets. This example copies a sweatshirt-and-tee preset and swaps its tee for a Comfort Colors 1717 block made from two presets: one sells the dark colors and one the light colors.

curl -X POST https://api.listingview.io/api/v1/presets/1837/copy \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": { "name": "Sweatshirt + CC tee · light and dark" },
    "pod_product_ops": [{
      "op": "replace",
      "target": { "product": 2 },
      "source": { "preset_id": 859, "product": 1 },
      "colors": { "union_of": [ { "preset_id": 859, "product": 1 }, { "preset_id": 1667, "product": 1 } ] },
      "prices": "per_color_from_owner",
      "slot_color_sources": {
        "Light Designs Here": { "preset_id": 859, "product": 1 },
        "Dark Designs Here": { "preset_id": 1667, "product": 1 }
      },
      "mockups": "carry_from_source",
      "photos_from_source": true
    }]
  }'
  • target is the product to replace, by its position in the preset you copy.

  • source is the product to bring in, by preset and position. It arrives with its provider and variants.

  • colors (required) is "source", {"union_of": [...]} or a list of colors.

  • prices (required) is "source", "per_color_from_owner" or a list of pod.v1 entries. per_color_from_owner prices each color the way the preset that sells it does.

  • slot_color_sources rebinds each slot to the colors of one source. Slot targets on the products you keep don't change.

  • mockups (required) is "carry_from_source", "keep" or "drop" (removes the replaced product's mockups). photos_from_source: true adds the source's photos.

  • prices_from_option (a set op on a copy only) gives every option of the other axis the named option's per-cell prices.

The result goes through every check a create runs. If any color would end up without a design, nothing is written. Each call takes up to 10 product ops and up to 5 source presets (swap_sources_over_limit); union_of takes up to 5 sources.

  • A source that isn't yours answers 404, naming the field, never the preset's owner.

  • A slot that can't move answers swap_print_area_missing when the new product has no matching print area.

  • A source that can't be read answers swap_source_unreadable.

  • Mockups showing both the replaced and a kept product answer swap_mockups_ambiguous. Replace those mockups in ops.

A swap works on copies only, never on PATCH.

Delete a preset

DELETE /presets/{preset_id} deletes one preset. Listings already made from it are unaffected.

{
  "deleted": true,
  "preset_id": 1201,
  "name": "Test 3",
  "deleted_at": "2026-10-16T14:05:09.412Z",
  "recoverable_until": "2026-11-15T14:05:09.412Z",
  "deletes_remaining_today": 19
}
  • In use. A preset a running or waiting bulk create or Etsy-draft job uses is refused with preset_in_use, naming the jobs.

  • Recovery. ListingView support can restore a deleted preset until recoverable_until, at least 30 days.

Where the API is stricter than the app

The app editor sometimes saves a preset that would fail later. The API refuses it up front instead:

  • Files that aren't ready. A design or file that isn't processed and scanned is refused, with an asset_not_ready entry in details.errors[]. It never becomes an empty slot.

  • Another shop's settings. A shipping profile, return policy or other setting from another shop is refused with PRESET_SHOP_MISMATCH, which lists the right shop's options in target_options. It is never swapped for the shop's first profile.

  • Mockup templates. A template that isn't yours or a shared one, isn't ready, or points at a missing slot is refused. The app only warns.

  • Design files you don't own are refused, not dropped.

  • Category attributes are checked when you save, not skipped when the listing is made.

Conflicts, refusals and test keys

  • Every problem at once. A refused write answers VALIDATION_FAILED with every problem in details.errors[]. Each entry has field (a path into your request), code, message, and candidates[] where there is a choice. Past 50 problems you get the first 50, plus details.error_count and truncated: true. Two exceptions: a variant limit answers POD_VARIANT_LIMIT, and a catalog that is still syncing answers SERVICE_UNAVAILABLE pod_catalog_pending.

  • Size. A preset body can be up to 256 KB. A bigger one answers 413 VALIDATION_FAILED with the reason too_large and limit_kind: "preset_body"; split the change into two calls.

  • Someone else edited it. CONFLICT with details.reason: "preset_changed", plus details.current and details.reread (how to read it again). Read the preset again, re-apply your change and retry. After 3 conflicts in a row, stop and tell the person: someone is editing at the same time.

  • Read-only parts. A section the API can only read answers section_read_only: send it exactly as read, or edit it in ListingView. A typed write over a stored section the API can't read at all answers PRESET_UNREADABLE with the reason section_unreadable.

  • Test keys. With an lv_test_ key, every write runs the full check and answers would_create, would_update or would_copy with the report. Nothing is saved, and nothing counts against your daily limits, though a limit that is already used up still refuses the call. A test-key edit includes pod_diff when print-on-demand cells would change; a test-key copy doesn't.

The full retry rules are in API: Getting started.

From the CLI or an AI app

  • CLI: listingview presets get 1667 --include spec,pod_variants --json reads a preset. On a terminal, listingview presets get 1667 prints the design-slot table. presets create, presets update, presets copy and presets delete take the request as flags or as JSON with --input @preset.json. The building-block reads are commands too, such as listingview pod-products list; listingview --help lists them all. See API: Command-line tool.

  • MCP: list_presets reads presets and their building blocks. taxonomy_id reads a category's attributes, shop_id a shop's profiles, and q, supplier, category, min_cost or max_cost search the print-on-demand catalog; add product_id, then provider_id, to drill in. On a preset read, include adds sections.

  • MCP: manage_preset creates, updates or copies a preset with operation (create, update or copy), typed fields or spec, ops, and pod_product_ops on a copy. To delete a preset, use delete_preset_or_draft with kind: "preset". See API: Connect AI apps.

Every endpoint above, with its full request and response schema, is in the API reference. Every error code is on the errors page.

Related features

A preset holds everything ListingView needs to turn a design into a listing: listing details, shop settings, print-on-demand products and prices, design slots, mockups, photos, digital files and Etsy variations. Through the API you can read a preset section by section, look up what presets are built from, and create, edit and copy print-on-demand, digital and physical presets.

Every write is checked the way the app editor saves, then refused whole if anything is wrong. Nothing is trimmed or dropped to make a write fit.

New to the API? Start with API: Getting started. For what presets do in the app, see Presets and POD Presets & Bulk Create.

What you need

  • Scopes. Reads need presets:read. Writes need presets:write. A Drafting Agent key holds both.

  • Plan. Reading presets and their building blocks works on every plan with API access. So do writes to digital and physical presets. Every write to a print-on-demand preset needs the Plus plan or higher: creates, edits, copies and renames. Below Plus they answer PLAN_INSUFFICIENT.

  • Ready files. Designs, photos and deliverables must be uploaded and show ready: true first. See API: Uploads.

  • Daily limits. An account can create or copy 100 presets a day through the API (DATA_BUDGET_EXCEEDED with the reason preset_writes_daily past it) and delete 20 (preset_deletes_daily). Edits don't count.

Read a preset

GET /presets/{preset_id} answers the preset's standard view: listing defaults, shop bindings, print-on-demand products and the design slots, each with its letter, its label, whether each listing brings its own design, and the garments and colors it prints on.

curl https://api.listingview.io/api/v1/presets/1667 \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY"
{
  "view": "preset.v1",
  "id": 1667,
  "name": "Halloween sweatshirt — light + dark",
  "type": "pod",
  "updated_at": "2026-10-14T18:22:05.123Z",
  "slots": [
    {
      "slot_id": "A",
      "position": 1,
      "label": "Light Designs Here",
      "enabled": true,
      "fill": "upload_required",
      "prints_on": [
        { "product": "Gildan 18000", "provider": "Monster Digital", "print_area": "front", "colors": ["Black", "Navy", "Forest Green"], "garment_tone": "dark" }
      ],
      "summary": "A · \"Light Designs Here\" · prints on dark garments (Black, Navy, Forest Green)"
    }
  ],
  "pod_products": [
    { "product": "Gildan 18000", "provider": "Monster Digital", "colors_selected": ["Black", "Navy", "Forest Green", "White", "Sand", "Ash"], "sizes_selected": ["S", "M", "L", "XL", "2XL"], "variant_count": 30, "price_range": { "min": 34.99, "max": 38.99 } }
  ],
  "mockups": { "count": 2, "slots": [{ "index": 0, "template_id": 77120, "design_slot": "A" }, { "index": 1, "template_id": 77121, "design_slot": "B" }] }
}

The answer is shortened here. Add check_shop_id to check the preset's shipping profile, return policy, section, production partners and processing profile against one of your shops. shop_check.bindings[] then marks each one valid, invalid (with that shop's target_options) or unverified.

A preset whose saved settings ListingView can't read still answers. Its sections are empty and warnings[] carries preset_data_unreadable: open it in ListingView and save it again.

More sections: include

Add include to get more, comma-separated:

include

What it adds

pod_variants

Every variant of each print-on-demand product: options, price, enabled, available and base_cost.

targets

Where each slot prints: product, print area with its size in inches, colors and placement.

mockups

Each mockup entry with design_slots_by_region and placement.

photos

The preset's static photos, by asset id, with thumbnails.

digital

Deliverables, design files, the destination folder and delivery-PDF settings.

variations

Etsy variations, the variation inventory and which variations set price, quantity and SKU.

properties

The category and the attributes Etsy shows buyers.

spec

The preset in the exact shape a create accepts. See below.

all

Everything above.

  • Entry refs. Entries carry an opaque ref, such as pp_… for a product or sl_… for a slot. Edits use these refs to name an entry.

  • etsy_type is physical, download or both.

  • A section ListingView can't read comes back null and is named in unreadable_sections[]. The rest of the answer still arrives.

  • Values the write model can't carry exactly are listed in unaddressable[], each with its effect: read_only, kept or changed_on_write.

  • File URLs. The only file URL in a preset read is a thumbnail_url. A link deliverable returns the address the seller typed. A link that points at ListingView or its storage reads url: null with redacted: "internal_url".

include=spec: when it is offered

spec is the preset in exactly the shape POST /presets takes, so a simple preset can be read, changed and posted back as a new one. It is returned only when ListingView can prove that writing it back recreates the preset exactly. Today that means:

  • Withheld for most presets. A preset with print-on-demand products, slots, mockups, photos, digital files, attributes or a category reads spec: null with spec_unavailable_reason: "spec_not_writable". unaddressable[] names each of those sections with family_unverified.

  • Presets made in the app carry their shop, which a create can't recreate. They read spec: null, and unaddressable[] says content_not_recreatable.

  • Other gaps. not_writable, round_trip_mismatch and spec_too_large (the spec would be over the preset body limit) also leave spec null. A two-variation preset whose app inventory prices some combinations from the per-variation cells reads per_axis_fallback. None of these refuse the read.

For any other preset, read the sections you need with the other includes and build the create body from them.

The building blocks

These reads list what presets are built from. They need presets:read, cost nothing, and work on every plan with API access.

Print-on-demand products

Search the print-on-demand catalog, then drill down to a product's providers, then to one provider's variants and print areas.

curl "https://api.listingview.io/api/v1/pod-products?q=comfort%20colors%201717" \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY"
{
  "products": [{
    "product_id": 706,
    "name": "Unisex Garment-Dyed T-shirt",
    "brand": "Comfort Colors",
    "model": "1717",
    "supplier": "printify",
    "category": { "id": "mens-t-shirts", "name": "T-Shirts" },
    "thumbnail": "https://images.printify.com/5853fec7ce46f30f8328200a/regular.jpg",
    "provider_count": 6,
    "min_cost": 12.29,
    "currency": "USD",
    "pod_catalog_as_of": "2026-10-25T05:12:44.000Z"
  }],
  "next_cursor": null,
  "hint": null,
  "warnings": []
}
  • Search (GET /pod-products): every word of q must match, up to 10 words. Filter by category, supplier (printify), min_cost and max_cost, and page with limit and cursor. product_id is the Printify blueprint id.

  • A product (GET /pod-products/{product_id}) lists its active providers[]: provider_id, name, recommended (Printify's default), location, in-stock colors[] (name, hex) and sizes[], min_cost, max_cost and shipping_summary[].

  • A provider (GET /pod-products/{product_id}/providers/{provider_id}) lists every variants[] row, out-of-stock ones with available: false, and its print_areas[]. A slot target names a print area by its id (such as pa_1) or its name:

{
  "product_id": 706,
  "product_name": "Comfort Colors 1717",
  "provider_id": 99,
  "provider_name": "Printify Choice",
  "currency": "USD",
  "variants": [
    { "options": { "color": "Black", "size": "S" }, "color_hex": "#1a1a1a", "base_cost": 12.29, "available": true },
    { "options": { "color": "Ivory", "size": "S" }, "color_hex": "#f2ecdf", "base_cost": 12.29, "available": false }
  ],
  "print_areas": [
    { "id": "pa_1", "name": "front", "position": "front", "decoration_method": "dtg", "width_in": 12, "height_in": 16, "width_px": 3600, "height_px": 4800, "dpi": 300 }
  ],
  "pod_catalog_as_of": "2026-10-25T05:12:44.000Z",
  "warnings": []
}

Answers come from ListingView's nightly copy of Printify's catalog, never a live Printify call. Every answer carries pod_catalog_as_of. When the copy is more than 8 days old, warnings[] says pod_catalog_stale: costs, colors or print areas may have changed since.

  • Not synced yet. A product ListingView hasn't synced answers NOT_FOUND with the reason pod_blueprint_unknown; one Printify dropped, pod_blueprint_unavailable. An unknown or inactive provider answers pod_provider_unknown or pod_provider_inactive, with candidates.

  • Still syncing. While the catalog or a provider's variants are still syncing, you get a retryable SERVICE_UNAVAILABLE with the reason pod_catalog_pending.

Category attributes

GET /etsy-taxonomy/{taxonomy_id}/properties lists the attributes Etsy offers for a category.

{
  "taxonomy_id": 482,
  "properties": [{
    "property_id": 200,
    "name": "Primary color",
    "required": false,
    "supports_variations": true,
    "supports_attributes": true,
    "scales": [],
    "possible_values": [
      { "value_id": 1, "name": "Black", "scale_id": null },
      { "value_id": 2, "name": "Blue", "scale_id": null }
    ],
    "max_values": 1
  }],
  "unverified": [],
  "unverified_reason": null
}

Properties that can only be a variation aren't listed. Answers come from ListingView's cache. On a miss a live key reads Etsy, which counts against your daily Etsy-call budget. A category Etsy doesn't know answers NOT_FOUND. A test key reads the cache only: a category that isn't cached answers properties: null with unverified_reason: "test_key_cache_miss".

Shop profiles

GET /my-shop/profiles?shop_id=38123456 lists the shop-specific choices a preset binds to, for one of your shops: shipping_profiles, return_policies, sections, production_partners and processing_profiles, each a list of {id, label, …}. With one connected shop you can leave shop_id out.

{
  "shop_id": 38123456,
  "shipping_profiles": null,
  "return_policies": [{ "id": 1234567890, "label": "Returns within 30 days", "accepts_returns": true, "accepts_exchanges": true, "return_deadline_days": 30 }],
  "sections": [{ "id": 45678901, "label": "Sweatshirts" }],
  "production_partners": [{ "id": 3456789, "label": "Monster Digital", "location": "US" }],
  "processing_profiles": [{ "id": 987654321, "label": "Made to order · 1-3 days", "readiness_state": "made_to_order", "min_processing_days": 1, "max_processing_days": 3 }],
  "unverified": ["shipping_profiles"],
  "unverified_reason": "etsy_unavailable",
  "retry_after": 60
}

A list ListingView couldn't read right now is null and named in unverified[]; the rest still answer. unverified_reason says why: etsy_unavailable, etsy_call_budget, test_key_cache_miss or reconnect_required. These are the same choices a refusal offers in target_options when a preset names another shop's setting. Answers come from ListingView's cache, and a live key reads missing lists from Etsy against your Etsy-call budget. A test key always reads processing_profiles: null.

Mockup templates

GET /mockup-templates lists the templates you can use: yours first, newest first, then ListingView's shared ones (owned: false). It pages with limit and cursor.

{
  "template_id": 77120,
  "name": "flatlay-black-tee.jpg",
  "owned": true,
  "thumbnail_url": "https://d111111abcdef8.cloudfront.net/assets-v2/1042/77120/thumb.webp?Expires=…",
  "preview_url": "https://d111111abcdef8.cloudfront.net/assets-v2/1042/77120/template-preview/9b1e4d7c2a6f8e03b5c1d9a7f4e2c680.jpg?Expires=…",
  "preview_pending": false,
  "width": 3000,
  "height": 2000,
  "has_regions": true,
  "provider": null,
  "revision": "5f0c2a9e1b7d4c3a8e6f1d2b9c0a7e41",
  "regions": [{
    "region_id": "3f1c9a52-6d1e-4b8a-9c3d-2e7f0a1b4c5d",
    "label": "Chest print",
    "fit": "contain",
    "h_align": "center",
    "v_align": "top",
    "blend_mode": "multiply",
    "opacity": 0.95,
    "quad": { "points": [[0.3625, 0.2], [0.6375, 0.2], [0.6375, 0.6125], [0.3625, 0.6125]] }
  }]
}
  • quad.points are the region's four corners relative to the photo. 0 to 1 spans the photo, and a region may overhang it, so a corner can read below 0 or above 1.

  • revision changes whenever the photo or a region is redrawn. Cache anything you build from a template on its revision.

Template previews: preview_url

preview_url is a copy of your own template photo that a tool can place designs on. For example, a script can paste each design into a region's quad to pick the best lead photo.

  • What it is. The whole photo, at most 1,600 px on the long edge. Never the original.

  • Short-lived. The link is signed for up to an hour. List the templates again for a fresh link.

  • Your own JPG and PNG templates only. PSD templates and ListingView's shared templates answer preview_url: null, and have no thumbnail_url either. That includes PSDs registered through SudoMock (provider: "sudomock").

  • Still being made. A new or redrawn template can answer preview_url: null with preview_pending: true. Read it again later. preview_pending: false with no preview means none can be made for that photo.

Prices: one unit, two entry shapes

Every price is a decimal amount in the shop's currency, with at most 2 decimals. No field anywhere is in cents. A price you read can be sent back as it is.

Print-on-demand prices come in two entry shapes. They follow the same rules:

Where

Entry shape

Example

A create's spec.pod_products[].prices, typed ops, a product swap, and listing writes

{options: {color?, size?}, price}

{"options": {"size": "L"}, "price": 26.99}

fields.pod_products[].prices on PATCH /presets/{preset_id}

{color?, size?, price}

{"color": "Maroon", "size": "S", "price": 32.99}

  • Color and size together price one variant.

  • A color alone prices that color in every size. A size alone prices that size in every color.

  • The exact entry wins. A color-and-size entry beats a color-only or size-only entry for the same variant.

  • Conflicts are refused. When a color price and a size price disagree for one variant, the call answers pod_price_ambiguous. Add a color-and-size entry for that variant.

  • Missing prices get a default. On a create, a variant with no price is priced at Printify cost × 1.2, rounded up to the next $0.50. On an edit, so is a variant you newly select that was stored switched off: it is switched on at that price. The answer lists each one in prices_defaulted[]. This is a warning, not a refusal. If the catalog has no cost for a variant, the write is refused.

  • Unpriced variants that are already on are refused (pod_variants_unpriced): send a price for them.

Create a preset

POST /presets takes fields and an optional spec:

  • fields: name (required, and not already used by one of your presets), listing_defaults (title, description, tags, price, quantity, who made it, when it was made, category, materials, SKU) and shop_bindings (shipping profile, return policy, section, production partners, processing profile).

  • spec: the preset's type (pod, digital or physical) and its typed sections: pod_products, slots, mockups, photos, digital, variations, variation_inventory, varies and properties. Without spec you get a physical preset with just the fields you sent.

The smallest create is a name. Fill in the rest later:

{ "fields": { "name": "Ceramic mug — draft" } }

The answer is the new preset, changed[] (every field the write set) and, for a typed write, prices_defaulted[], properties_cleared and warnings[]. Send an X-Idempotency-Key header so a retry can't make a second preset.

A print-on-demand preset from scratch

  1. Find the product and its provider with the print-on-demand product reads above.

  2. Read the provider's print areas, so each slot can target one by id or name.

  3. Pick mockup templates with GET /mockup-templates, and the shop's settings with GET /my-shop/profiles.

  4. Send POST /presets with spec.type: "pod".

This example makes a Comfort Colors 1717 tee from Printify Choice, in Black and Pepper, sizes S to L, priced per size, with one fixed design on the front:

curl -X POST https://api.listingview.io/api/v1/presets \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": {
      "name": "Desert sunset tee — front print",
      "listing_defaults": {
        "title": "Retro Desert Sunset Tee, Comfort Colors Vintage Shirt",
        "description": "Heavyweight ring-spun cotton tee, printed to order and shipped from the US.",
        "tags": ["desert tee", "sunset shirt", "retro graphic tee"],
        "who_made": "someone_else",
        "when_made": "made_to_order",
        "taxonomy_id": 482
      },
      "shop_bindings": {
        "shipping_profile_id": 209876543210,
        "return_policy_id": 1234567890,
        "production_partner_ids": [3456789],
        "readiness_state_id": 987654321
      }
    },
    "spec": {
      "type": "pod",
      "pod_products": [{
        "blueprint_id": 706,
        "print_provider_id": 99,
        "options": { "color": ["Black", "Pepper"], "size": ["S", "M", "L"] },
        "prices": [
          { "options": { "size": "S" }, "price": 24.99 },
          { "options": { "size": "M" }, "price": 24.99 },
          { "options": { "size": "L" }, "price": 26.99 }
        ]
      }],
      "slots": [{
        "label": "Front design",
        "fill": { "fixed": 88130 },
        "targets": [{ "product": 1, "print_area": "front" }]
      }]
    }
  }'

Mockups aren't rendered when you save. Bulk Create renders them when it makes listings.

The print-on-demand sections

  • type is required in spec and can never change. A section that doesn't fit the type, such as digital on a pod preset, is refused with section_not_for_type.

  • pod_products[] (up to 10): blueprint_id, print_provider_id, options (color, size), prices and size_label_prefix. print_provider_id is required: ListingView never picks a provider for you.

  • slots[] (up to 10): label, fill, enabled and targets[]. Slots are lettered A to J by their position among the enabled slots. fill is "upload_required" (each listing brings its design) or {"fixed": asset_id} (the same design every time).

  • targets[] (up to 20 per slot): product (the product's 1-based position), print_area (id or name), options ({"color": [...]} or "all") and placement.

  • placement: fit, alignment, angle, the size and offset percentages, the maximum inches and margins. Size percentages run from 0 to 100, offsets from −100 to 100 and angle from −360 to 360. Inch fields must fit the print area. Leave placement out to get the app's default placement.

  • mockups[]: template_id and design_slot ("A" to "J"), both required. Optional: index (its image slot, 0 to 19), design_slots_by_region ({region_id: slot}, to put another slot's design in some regions) and placement (fit is cover, contain or stretch).

  • photos[]: static photos by index and asset_id, such as a size chart.

What a print-on-demand create checks

  • The provider prints that product and offers every color and size you chose (pod_option_unknown otherwise).

  • At most 100 variants per product (Printify's limit), 70 options per Etsy variation and 400 combinations in all (Etsy's limits). Over a limit answers POD_VARIANT_LIMIT, and details.limit_kind names which.

  • Every color you sell is covered by at least one enabled slot target (slot_without_color otherwise).

  • Every target's print area exists on that provider (slot_print_area_unknown), and every placement fits it (placement_out_of_bounds).

  • Every mockup template is yours or a shared one, and ready. Its region ids are real, and its design_slot is an enabled slot.

  • At most 20 photos and mockups together, Etsy's image limit (images_over_limit).

  • Every fixed design is yours and ready.

Two things aren't offered yet. Writing an auto-converted (derived) slot answers derived_slots_unsupported. A product option other than color and size answers pod_axis_unsupported.

Digital presets

A digital preset delivers files to buyers. Upload the files first with purpose: "digital_file" (see API: Uploads), then create the preset. This example delivers a PDF and a folder of bonus files, saves into a destination folder, shows a cover design on a mockup and adds a size-chart photo:

{
  "fields": { "name": "Printable planner — PDF + bonus pack" },
  "spec": {
    "type": "digital",
    "digital": {
      "deliverables": [
        { "kind": "file", "asset_id": 88123 },
        { "kind": "collection", "collection_id": 4521 }
      ],
      "parent_collection_id": 4522,
      "design_files": [{ "asset_id": 88124, "label": "Planner cover" }]
    },
    "mockups": [{ "index": 0, "template_id": 77120, "design_slot": "A" }],
    "photos": [{ "index": 1, "asset_id": 88125 }]
  }
}
  • Deliverables. A file must be a ready deliverable. Every file in a collection, sub-folders included, must be yours, ready and scanned clean (collection_file_not_ready). An empty collection is refused (collection_empty). A preset needs at least one deliverable (digital_files_required).

  • How buyers get the files. Each listing delivers one PDF with a download button per deliverable. A preset holds up to 50 deliverables, and its collections can bring up to 200 files to one listing (too_many_collection_files).

  • Design files (up to 10) are ready PNG, JPEG or WebP images used on mockups (design_not_image otherwise).

  • Optional. parent_collection_id (where the files are saved), and pdf_settings for the delivery PDF.

  • Old presets. A digital preset that still carries legacy inline files answers legacy_preset_files when an Etsy draft uses it. Re-save its deliverables in ListingView.

Link deliverables

A preset can still hold a link deliverable: {"kind": "link", "url", "label"}. The link must pass ListingView's link screen, or it is refused with link_not_allowed and details.link_rules naming each rule it broke: an https:// URL of at most 2,048 characters, on the standard port, with no user name or password, no IP address and no reserved or internal host, and a plain-text label of at most 100 characters.

Etsy drafts made through the API never deliver a link: a digital draft whose preset or item carries one is refused with link_deliverables_unsupported. Use a file or collection deliverable for anything an API draft delivers. A deliverable that points at a link saved in your link library reads kind: "saved_link": an edit keeps it in place, and a create from spec doesn't carry it.

Physical presets: variations and attributes

Physical and digital presets can carry Etsy variations. Category attributes work on every preset type.

  • variations[] (up to 2): name and options[] (up to 70 each). ListingView sets property_id from the variation's name and position; leave it out or send it as read. An op that sets it is refused (op_not_supported).

  • variation_inventory is by_option or by_combination, never both: setting one replaces the other. Rows are {options, price, quantity, sku, visible}. Quantity runs from 0 to 999, and a SKU can be up to 32 characters. A new preset with two variations takes by_combination (by_option_two_axes otherwise).

  • varies says which variations set the price, quantity and sku.

  • properties[]: property_id, value_ids[] or values[], and an optional scale_id. Attributes need a category (listing_defaults.taxonomy_id) first.

Attributes are checked against the category's properties before anything is saved: the property must exist for the category, use allowed values and a valid scale, and stay within max_values. A property that only works as a variation is refused as an attribute (property_variation_only). If Etsy's category data can't be read, the write answers EXTERNAL_API_ERROR with the reason taxonomy_unavailable and saves nothing. Retry after retry_after.

Changing taxonomy_id clears the category's attributes, and the answer says properties_cleared: true.

Two variations are locked. On a preset with two variations, the API can't change the variations, their cells or varies. Send them exactly as read, or edit them in ListingView. Any other change answers op_not_supported with the reason two_axis_variations_locked. With two variations, an option can't contain ||| (variation_option_delimiter).

Edit a preset

PATCH /presets/{preset_id} changes only what you send. It requires expected_updated_at: the updated_at from your last read. If the preset changed since, nothing is saved and the answer is CONFLICT with the reason preset_changed.

You can send fields, typed ops[], or both. fields covers the name, listing defaults, shop bindings, slot labels and on/off, and, on a print-on-demand preset, each product's colors, sizes and prices among the variants it already has:

curl -X PATCH https://api.listingview.io/api/v1/presets/1831 \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "expected_updated_at": "2026-10-16T09:12:44.512Z",
    "fields": {
      "pod_products": [{
        "position": 1,
        "product": "Gildan 18000",
        "colors_selected": ["Black", "Navy", "Forest Green", "Maroon"],
        "prices": [
          { "size": "S", "price": 34.99 },
          { "size": "M", "price": 34.99 },
          { "size": "2XL", "price": 38.99 },
          { "color": "Maroon", "size": "S", "price": 32.99 }
        ]
      }]
    }
  }'

This sells two more colors and reprices three sizes in every color. Slot changes work the same way, by position:

{
  "expected_updated_at": "2026-10-16T09:12:44.512Z",
  "fields": {
    "slots": [
      { "position": 1, "label": "Light ink (dark garments)" },
      { "position": 3, "enabled": false }
    ]
  }
}

Add check_shop_id to require every shop-specific id to belong to that shop.

Typed ops

For anything else, send ops[]: up to 100 typed operations, each {op, section, ref?, field?, position?, value?, prices?, target?}. Read the preset with include=all first, so you know each entry's ref.

{
  "expected_updated_at": "2026-10-16T09:12:44.512Z",
  "ops": [{ "op": "replace", "section": "photos", "value": [] }]
}
  • add inserts value into a list at position (1-based; the end by default).

  • remove drops the entry ref names.

  • replace with a ref replaces that entry. Without a ref it replaces the whole list; an empty list clears it.

  • set sets one field: of the entry ref names, or of the section. set order puts the named entries first, in that order.

  • Refs are a 1-based position as read, a ref a read returned (pp_…, sl_…, mk_…, ph_… and so on), or, for slots, the letter or the exact label. Refs always mean the preset as read at expected_updated_at, never shifted by earlier ops in the same call. A label two slots share answers ref_ambiguous; an entry an earlier op removed, ref_removed.

  • Order. Ops run before fields. Edit each section once per call (section_edited_twice otherwise).

What ops cover on a print-on-demand preset:

  • Products. Add a product from the catalog, or remove one. A product a slot still targets can be removed only if the same call retargets or removes that slot (slot_target_dangling otherwise).

  • Provider. set print_provider_id switches a product to another provider. Print areas are matched by name. Percentage placements are kept and listed in placement_rescaled[]. An inch field that no longer fits answers placement_out_of_bounds. The product itself (its blueprint_id) can't change in an edit (pod_base_mismatch): to use another product, swap it on a copy.

  • Colors and sizes. set options changes them, including variants the provider offers that the preset didn't have yet. It is the only op that takes prices: pod.v1 entries for the new cells. Any you leave out are listed in prices_defaulted[]. A new color must be covered by a slot.

  • Slots. Add, remove, relabel, switch on or off, retarget or re-place a slot. keep_only keeps the named slots and removes the rest. Mockups follow their slot, not its letter.

  • Mockups and photos. Replace, add, remove, reorder or clear them.

Ops apply in order to one copy of the preset, and the result is checked once at the end, so a step in between may be invalid. The call saves everything or nothing.

The answer carries diff, the sections that changed, and for print-on-demand changes pod_diff: products added, removed or changed, variants added, removed or repriced, and the slots, mockups and photos that changed. To check every variant afterward, read include=pod_variants.

The exact shape of every op is in the API reference.

Print-on-demand edits are strict

A print-on-demand write must leave the whole print-on-demand setup valid, including problems the preset already had: those are listed as "Already on the preset — fix it in this request". If ListingView can't fully read a preset's print-on-demand data, every change to it is refused with pod_preset_unreadable; edit that preset in ListingView. Two known limits:

  • A slot target that points at a product the preset no longer has makes the preset unreadable this way.

  • A select on a preset made in the app overrides a cell the seller had switched off inside the new selection. A row that is already on but has no price is still refused as unpriced.

Copy a preset

POST /presets/{preset_id}/copy copies a preset, optionally with fields and ops[] edits on the copy, and optionally to another of your shops. Without a name the copy is called "Copy of" plus the original's name. A name one of your presets already has answers CONFLICT with the reason name_taken and existing_preset_id. expected_updated_at is optional on a copy.

{
  "preset_id": 2041,
  "fields": {
    "name": "Linen apron — gift edition",
    "listing_defaults": { "price": 48 }
  }
}

preset_id goes in the path. The rest is the body.

Copy to another shop

Send target_shop_id. Shop-specific ids are checked against that shop and never swapped. A mismatch is refused with that shop's options, so you can resend with a top-level shop_bindings chosen from them (not also inside fields; both answer duplicate). A print-on-demand preset copied to another shop needs pod_blanks: "keep" (PRESET_POD_BLANK_ACK_REQUIRED otherwise), and that shop must be linked to Printify (PRINTIFY_SHOP_NOT_MAPPED otherwise). Every print-on-demand copy lists its products in carried_blanks[]:

{
  "target_shop_id": 38123456,
  "fields": { "name": "Halloween sweatshirt — second shop" },
  "shop_bindings": {
    "shipping_profile_id": 209876543299,
    "return_policy_id": null,
    "shop_section_id": null,
    "readiness_state_id": 987654399
  },
  "pod_blanks": "keep"
}

Swap a product

pod_product_ops[] swaps a print-on-demand product of the copy for a product block from your other presets. This example copies a sweatshirt-and-tee preset and swaps its tee for a Comfort Colors 1717 block made from two presets: one sells the dark colors and one the light colors.

curl -X POST https://api.listingview.io/api/v1/presets/1837/copy \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": { "name": "Sweatshirt + CC tee · light and dark" },
    "pod_product_ops": [{
      "op": "replace",
      "target": { "product": 2 },
      "source": { "preset_id": 859, "product": 1 },
      "colors": { "union_of": [ { "preset_id": 859, "product": 1 }, { "preset_id": 1667, "product": 1 } ] },
      "prices": "per_color_from_owner",
      "slot_color_sources": {
        "Light Designs Here": { "preset_id": 859, "product": 1 },
        "Dark Designs Here": { "preset_id": 1667, "product": 1 }
      },
      "mockups": "carry_from_source",
      "photos_from_source": true
    }]
  }'
  • target is the product to replace, by its position in the preset you copy.

  • source is the product to bring in, by preset and position. It arrives with its provider and variants.

  • colors (required) is "source", {"union_of": [...]} or a list of colors.

  • prices (required) is "source", "per_color_from_owner" or a list of pod.v1 entries. per_color_from_owner prices each color the way the preset that sells it does.

  • slot_color_sources rebinds each slot to the colors of one source. Slot targets on the products you keep don't change.

  • mockups (required) is "carry_from_source", "keep" or "drop" (removes the replaced product's mockups). photos_from_source: true adds the source's photos.

  • prices_from_option (a set op on a copy only) gives every option of the other axis the named option's per-cell prices.

The result goes through every check a create runs. If any color would end up without a design, nothing is written. Each call takes up to 10 product ops and up to 5 source presets (swap_sources_over_limit); union_of takes up to 5 sources.

  • A source that isn't yours answers 404, naming the field, never the preset's owner.

  • A slot that can't move answers swap_print_area_missing when the new product has no matching print area.

  • A source that can't be read answers swap_source_unreadable.

  • Mockups showing both the replaced and a kept product answer swap_mockups_ambiguous. Replace those mockups in ops.

A swap works on copies only, never on PATCH.

Delete a preset

DELETE /presets/{preset_id} deletes one preset. Listings already made from it are unaffected.

{
  "deleted": true,
  "preset_id": 1201,
  "name": "Test 3",
  "deleted_at": "2026-10-16T14:05:09.412Z",
  "recoverable_until": "2026-11-15T14:05:09.412Z",
  "deletes_remaining_today": 19
}
  • In use. A preset a running or waiting bulk create or Etsy-draft job uses is refused with preset_in_use, naming the jobs.

  • Recovery. ListingView support can restore a deleted preset until recoverable_until, at least 30 days.

Where the API is stricter than the app

The app editor sometimes saves a preset that would fail later. The API refuses it up front instead:

  • Files that aren't ready. A design or file that isn't processed and scanned is refused, with an asset_not_ready entry in details.errors[]. It never becomes an empty slot.

  • Another shop's settings. A shipping profile, return policy or other setting from another shop is refused with PRESET_SHOP_MISMATCH, which lists the right shop's options in target_options. It is never swapped for the shop's first profile.

  • Mockup templates. A template that isn't yours or a shared one, isn't ready, or points at a missing slot is refused. The app only warns.

  • Design files you don't own are refused, not dropped.

  • Category attributes are checked when you save, not skipped when the listing is made.

Conflicts, refusals and test keys

  • Every problem at once. A refused write answers VALIDATION_FAILED with every problem in details.errors[]. Each entry has field (a path into your request), code, message, and candidates[] where there is a choice. Past 50 problems you get the first 50, plus details.error_count and truncated: true. Two exceptions: a variant limit answers POD_VARIANT_LIMIT, and a catalog that is still syncing answers SERVICE_UNAVAILABLE pod_catalog_pending.

  • Size. A preset body can be up to 256 KB. A bigger one answers 413 VALIDATION_FAILED with the reason too_large and limit_kind: "preset_body"; split the change into two calls.

  • Someone else edited it. CONFLICT with details.reason: "preset_changed", plus details.current and details.reread (how to read it again). Read the preset again, re-apply your change and retry. After 3 conflicts in a row, stop and tell the person: someone is editing at the same time.

  • Read-only parts. A section the API can only read answers section_read_only: send it exactly as read, or edit it in ListingView. A typed write over a stored section the API can't read at all answers PRESET_UNREADABLE with the reason section_unreadable.

  • Test keys. With an lv_test_ key, every write runs the full check and answers would_create, would_update or would_copy with the report. Nothing is saved, and nothing counts against your daily limits, though a limit that is already used up still refuses the call. A test-key edit includes pod_diff when print-on-demand cells would change; a test-key copy doesn't.

The full retry rules are in API: Getting started.

From the CLI or an AI app

  • CLI: listingview presets get 1667 --include spec,pod_variants --json reads a preset. On a terminal, listingview presets get 1667 prints the design-slot table. presets create, presets update, presets copy and presets delete take the request as flags or as JSON with --input @preset.json. The building-block reads are commands too, such as listingview pod-products list; listingview --help lists them all. See API: Command-line tool.

  • MCP: list_presets reads presets and their building blocks. taxonomy_id reads a category's attributes, shop_id a shop's profiles, and q, supplier, category, min_cost or max_cost search the print-on-demand catalog; add product_id, then provider_id, to drill in. On a preset read, include adds sections.

  • MCP: manage_preset creates, updates or copies a preset with operation (create, update or copy), typed fields or spec, ops, and pod_product_ops on a copy. To delete a preset, use delete_preset_or_draft with kind: "preset". See API: Connect AI apps.

Every endpoint above, with its full request and response schema, is in the API reference. Every error code is on the errors page.

Related features

A preset holds everything ListingView needs to turn a design into a listing: listing details, shop settings, print-on-demand products and prices, design slots, mockups, photos, digital files and Etsy variations. Through the API you can read a preset section by section, look up what presets are built from, and create, edit and copy print-on-demand, digital and physical presets.

Every write is checked the way the app editor saves, then refused whole if anything is wrong. Nothing is trimmed or dropped to make a write fit.

New to the API? Start with API: Getting started. For what presets do in the app, see Presets and POD Presets & Bulk Create.

What you need

  • Scopes. Reads need presets:read. Writes need presets:write. A Drafting Agent key holds both.

  • Plan. Reading presets and their building blocks works on every plan with API access. So do writes to digital and physical presets. Every write to a print-on-demand preset needs the Plus plan or higher: creates, edits, copies and renames. Below Plus they answer PLAN_INSUFFICIENT.

  • Ready files. Designs, photos and deliverables must be uploaded and show ready: true first. See API: Uploads.

  • Daily limits. An account can create or copy 100 presets a day through the API (DATA_BUDGET_EXCEEDED with the reason preset_writes_daily past it) and delete 20 (preset_deletes_daily). Edits don't count.

Read a preset

GET /presets/{preset_id} answers the preset's standard view: listing defaults, shop bindings, print-on-demand products and the design slots, each with its letter, its label, whether each listing brings its own design, and the garments and colors it prints on.

curl https://api.listingview.io/api/v1/presets/1667 \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY"
{
  "view": "preset.v1",
  "id": 1667,
  "name": "Halloween sweatshirt — light + dark",
  "type": "pod",
  "updated_at": "2026-10-14T18:22:05.123Z",
  "slots": [
    {
      "slot_id": "A",
      "position": 1,
      "label": "Light Designs Here",
      "enabled": true,
      "fill": "upload_required",
      "prints_on": [
        { "product": "Gildan 18000", "provider": "Monster Digital", "print_area": "front", "colors": ["Black", "Navy", "Forest Green"], "garment_tone": "dark" }
      ],
      "summary": "A · \"Light Designs Here\" · prints on dark garments (Black, Navy, Forest Green)"
    }
  ],
  "pod_products": [
    { "product": "Gildan 18000", "provider": "Monster Digital", "colors_selected": ["Black", "Navy", "Forest Green", "White", "Sand", "Ash"], "sizes_selected": ["S", "M", "L", "XL", "2XL"], "variant_count": 30, "price_range": { "min": 34.99, "max": 38.99 } }
  ],
  "mockups": { "count": 2, "slots": [{ "index": 0, "template_id": 77120, "design_slot": "A" }, { "index": 1, "template_id": 77121, "design_slot": "B" }] }
}

The answer is shortened here. Add check_shop_id to check the preset's shipping profile, return policy, section, production partners and processing profile against one of your shops. shop_check.bindings[] then marks each one valid, invalid (with that shop's target_options) or unverified.

A preset whose saved settings ListingView can't read still answers. Its sections are empty and warnings[] carries preset_data_unreadable: open it in ListingView and save it again.

More sections: include

Add include to get more, comma-separated:

include

What it adds

pod_variants

Every variant of each print-on-demand product: options, price, enabled, available and base_cost.

targets

Where each slot prints: product, print area with its size in inches, colors and placement.

mockups

Each mockup entry with design_slots_by_region and placement.

photos

The preset's static photos, by asset id, with thumbnails.

digital

Deliverables, design files, the destination folder and delivery-PDF settings.

variations

Etsy variations, the variation inventory and which variations set price, quantity and SKU.

properties

The category and the attributes Etsy shows buyers.

spec

The preset in the exact shape a create accepts. See below.

all

Everything above.

  • Entry refs. Entries carry an opaque ref, such as pp_… for a product or sl_… for a slot. Edits use these refs to name an entry.

  • etsy_type is physical, download or both.

  • A section ListingView can't read comes back null and is named in unreadable_sections[]. The rest of the answer still arrives.

  • Values the write model can't carry exactly are listed in unaddressable[], each with its effect: read_only, kept or changed_on_write.

  • File URLs. The only file URL in a preset read is a thumbnail_url. A link deliverable returns the address the seller typed. A link that points at ListingView or its storage reads url: null with redacted: "internal_url".

include=spec: when it is offered

spec is the preset in exactly the shape POST /presets takes, so a simple preset can be read, changed and posted back as a new one. It is returned only when ListingView can prove that writing it back recreates the preset exactly. Today that means:

  • Withheld for most presets. A preset with print-on-demand products, slots, mockups, photos, digital files, attributes or a category reads spec: null with spec_unavailable_reason: "spec_not_writable". unaddressable[] names each of those sections with family_unverified.

  • Presets made in the app carry their shop, which a create can't recreate. They read spec: null, and unaddressable[] says content_not_recreatable.

  • Other gaps. not_writable, round_trip_mismatch and spec_too_large (the spec would be over the preset body limit) also leave spec null. A two-variation preset whose app inventory prices some combinations from the per-variation cells reads per_axis_fallback. None of these refuse the read.

For any other preset, read the sections you need with the other includes and build the create body from them.

The building blocks

These reads list what presets are built from. They need presets:read, cost nothing, and work on every plan with API access.

Print-on-demand products

Search the print-on-demand catalog, then drill down to a product's providers, then to one provider's variants and print areas.

curl "https://api.listingview.io/api/v1/pod-products?q=comfort%20colors%201717" \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY"
{
  "products": [{
    "product_id": 706,
    "name": "Unisex Garment-Dyed T-shirt",
    "brand": "Comfort Colors",
    "model": "1717",
    "supplier": "printify",
    "category": { "id": "mens-t-shirts", "name": "T-Shirts" },
    "thumbnail": "https://images.printify.com/5853fec7ce46f30f8328200a/regular.jpg",
    "provider_count": 6,
    "min_cost": 12.29,
    "currency": "USD",
    "pod_catalog_as_of": "2026-10-25T05:12:44.000Z"
  }],
  "next_cursor": null,
  "hint": null,
  "warnings": []
}
  • Search (GET /pod-products): every word of q must match, up to 10 words. Filter by category, supplier (printify), min_cost and max_cost, and page with limit and cursor. product_id is the Printify blueprint id.

  • A product (GET /pod-products/{product_id}) lists its active providers[]: provider_id, name, recommended (Printify's default), location, in-stock colors[] (name, hex) and sizes[], min_cost, max_cost and shipping_summary[].

  • A provider (GET /pod-products/{product_id}/providers/{provider_id}) lists every variants[] row, out-of-stock ones with available: false, and its print_areas[]. A slot target names a print area by its id (such as pa_1) or its name:

{
  "product_id": 706,
  "product_name": "Comfort Colors 1717",
  "provider_id": 99,
  "provider_name": "Printify Choice",
  "currency": "USD",
  "variants": [
    { "options": { "color": "Black", "size": "S" }, "color_hex": "#1a1a1a", "base_cost": 12.29, "available": true },
    { "options": { "color": "Ivory", "size": "S" }, "color_hex": "#f2ecdf", "base_cost": 12.29, "available": false }
  ],
  "print_areas": [
    { "id": "pa_1", "name": "front", "position": "front", "decoration_method": "dtg", "width_in": 12, "height_in": 16, "width_px": 3600, "height_px": 4800, "dpi": 300 }
  ],
  "pod_catalog_as_of": "2026-10-25T05:12:44.000Z",
  "warnings": []
}

Answers come from ListingView's nightly copy of Printify's catalog, never a live Printify call. Every answer carries pod_catalog_as_of. When the copy is more than 8 days old, warnings[] says pod_catalog_stale: costs, colors or print areas may have changed since.

  • Not synced yet. A product ListingView hasn't synced answers NOT_FOUND with the reason pod_blueprint_unknown; one Printify dropped, pod_blueprint_unavailable. An unknown or inactive provider answers pod_provider_unknown or pod_provider_inactive, with candidates.

  • Still syncing. While the catalog or a provider's variants are still syncing, you get a retryable SERVICE_UNAVAILABLE with the reason pod_catalog_pending.

Category attributes

GET /etsy-taxonomy/{taxonomy_id}/properties lists the attributes Etsy offers for a category.

{
  "taxonomy_id": 482,
  "properties": [{
    "property_id": 200,
    "name": "Primary color",
    "required": false,
    "supports_variations": true,
    "supports_attributes": true,
    "scales": [],
    "possible_values": [
      { "value_id": 1, "name": "Black", "scale_id": null },
      { "value_id": 2, "name": "Blue", "scale_id": null }
    ],
    "max_values": 1
  }],
  "unverified": [],
  "unverified_reason": null
}

Properties that can only be a variation aren't listed. Answers come from ListingView's cache. On a miss a live key reads Etsy, which counts against your daily Etsy-call budget. A category Etsy doesn't know answers NOT_FOUND. A test key reads the cache only: a category that isn't cached answers properties: null with unverified_reason: "test_key_cache_miss".

Shop profiles

GET /my-shop/profiles?shop_id=38123456 lists the shop-specific choices a preset binds to, for one of your shops: shipping_profiles, return_policies, sections, production_partners and processing_profiles, each a list of {id, label, …}. With one connected shop you can leave shop_id out.

{
  "shop_id": 38123456,
  "shipping_profiles": null,
  "return_policies": [{ "id": 1234567890, "label": "Returns within 30 days", "accepts_returns": true, "accepts_exchanges": true, "return_deadline_days": 30 }],
  "sections": [{ "id": 45678901, "label": "Sweatshirts" }],
  "production_partners": [{ "id": 3456789, "label": "Monster Digital", "location": "US" }],
  "processing_profiles": [{ "id": 987654321, "label": "Made to order · 1-3 days", "readiness_state": "made_to_order", "min_processing_days": 1, "max_processing_days": 3 }],
  "unverified": ["shipping_profiles"],
  "unverified_reason": "etsy_unavailable",
  "retry_after": 60
}

A list ListingView couldn't read right now is null and named in unverified[]; the rest still answer. unverified_reason says why: etsy_unavailable, etsy_call_budget, test_key_cache_miss or reconnect_required. These are the same choices a refusal offers in target_options when a preset names another shop's setting. Answers come from ListingView's cache, and a live key reads missing lists from Etsy against your Etsy-call budget. A test key always reads processing_profiles: null.

Mockup templates

GET /mockup-templates lists the templates you can use: yours first, newest first, then ListingView's shared ones (owned: false). It pages with limit and cursor.

{
  "template_id": 77120,
  "name": "flatlay-black-tee.jpg",
  "owned": true,
  "thumbnail_url": "https://d111111abcdef8.cloudfront.net/assets-v2/1042/77120/thumb.webp?Expires=…",
  "preview_url": "https://d111111abcdef8.cloudfront.net/assets-v2/1042/77120/template-preview/9b1e4d7c2a6f8e03b5c1d9a7f4e2c680.jpg?Expires=…",
  "preview_pending": false,
  "width": 3000,
  "height": 2000,
  "has_regions": true,
  "provider": null,
  "revision": "5f0c2a9e1b7d4c3a8e6f1d2b9c0a7e41",
  "regions": [{
    "region_id": "3f1c9a52-6d1e-4b8a-9c3d-2e7f0a1b4c5d",
    "label": "Chest print",
    "fit": "contain",
    "h_align": "center",
    "v_align": "top",
    "blend_mode": "multiply",
    "opacity": 0.95,
    "quad": { "points": [[0.3625, 0.2], [0.6375, 0.2], [0.6375, 0.6125], [0.3625, 0.6125]] }
  }]
}
  • quad.points are the region's four corners relative to the photo. 0 to 1 spans the photo, and a region may overhang it, so a corner can read below 0 or above 1.

  • revision changes whenever the photo or a region is redrawn. Cache anything you build from a template on its revision.

Template previews: preview_url

preview_url is a copy of your own template photo that a tool can place designs on. For example, a script can paste each design into a region's quad to pick the best lead photo.

  • What it is. The whole photo, at most 1,600 px on the long edge. Never the original.

  • Short-lived. The link is signed for up to an hour. List the templates again for a fresh link.

  • Your own JPG and PNG templates only. PSD templates and ListingView's shared templates answer preview_url: null, and have no thumbnail_url either. That includes PSDs registered through SudoMock (provider: "sudomock").

  • Still being made. A new or redrawn template can answer preview_url: null with preview_pending: true. Read it again later. preview_pending: false with no preview means none can be made for that photo.

Prices: one unit, two entry shapes

Every price is a decimal amount in the shop's currency, with at most 2 decimals. No field anywhere is in cents. A price you read can be sent back as it is.

Print-on-demand prices come in two entry shapes. They follow the same rules:

Where

Entry shape

Example

A create's spec.pod_products[].prices, typed ops, a product swap, and listing writes

{options: {color?, size?}, price}

{"options": {"size": "L"}, "price": 26.99}

fields.pod_products[].prices on PATCH /presets/{preset_id}

{color?, size?, price}

{"color": "Maroon", "size": "S", "price": 32.99}

  • Color and size together price one variant.

  • A color alone prices that color in every size. A size alone prices that size in every color.

  • The exact entry wins. A color-and-size entry beats a color-only or size-only entry for the same variant.

  • Conflicts are refused. When a color price and a size price disagree for one variant, the call answers pod_price_ambiguous. Add a color-and-size entry for that variant.

  • Missing prices get a default. On a create, a variant with no price is priced at Printify cost × 1.2, rounded up to the next $0.50. On an edit, so is a variant you newly select that was stored switched off: it is switched on at that price. The answer lists each one in prices_defaulted[]. This is a warning, not a refusal. If the catalog has no cost for a variant, the write is refused.

  • Unpriced variants that are already on are refused (pod_variants_unpriced): send a price for them.

Create a preset

POST /presets takes fields and an optional spec:

  • fields: name (required, and not already used by one of your presets), listing_defaults (title, description, tags, price, quantity, who made it, when it was made, category, materials, SKU) and shop_bindings (shipping profile, return policy, section, production partners, processing profile).

  • spec: the preset's type (pod, digital or physical) and its typed sections: pod_products, slots, mockups, photos, digital, variations, variation_inventory, varies and properties. Without spec you get a physical preset with just the fields you sent.

The smallest create is a name. Fill in the rest later:

{ "fields": { "name": "Ceramic mug — draft" } }

The answer is the new preset, changed[] (every field the write set) and, for a typed write, prices_defaulted[], properties_cleared and warnings[]. Send an X-Idempotency-Key header so a retry can't make a second preset.

A print-on-demand preset from scratch

  1. Find the product and its provider with the print-on-demand product reads above.

  2. Read the provider's print areas, so each slot can target one by id or name.

  3. Pick mockup templates with GET /mockup-templates, and the shop's settings with GET /my-shop/profiles.

  4. Send POST /presets with spec.type: "pod".

This example makes a Comfort Colors 1717 tee from Printify Choice, in Black and Pepper, sizes S to L, priced per size, with one fixed design on the front:

curl -X POST https://api.listingview.io/api/v1/presets \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": {
      "name": "Desert sunset tee — front print",
      "listing_defaults": {
        "title": "Retro Desert Sunset Tee, Comfort Colors Vintage Shirt",
        "description": "Heavyweight ring-spun cotton tee, printed to order and shipped from the US.",
        "tags": ["desert tee", "sunset shirt", "retro graphic tee"],
        "who_made": "someone_else",
        "when_made": "made_to_order",
        "taxonomy_id": 482
      },
      "shop_bindings": {
        "shipping_profile_id": 209876543210,
        "return_policy_id": 1234567890,
        "production_partner_ids": [3456789],
        "readiness_state_id": 987654321
      }
    },
    "spec": {
      "type": "pod",
      "pod_products": [{
        "blueprint_id": 706,
        "print_provider_id": 99,
        "options": { "color": ["Black", "Pepper"], "size": ["S", "M", "L"] },
        "prices": [
          { "options": { "size": "S" }, "price": 24.99 },
          { "options": { "size": "M" }, "price": 24.99 },
          { "options": { "size": "L" }, "price": 26.99 }
        ]
      }],
      "slots": [{
        "label": "Front design",
        "fill": { "fixed": 88130 },
        "targets": [{ "product": 1, "print_area": "front" }]
      }]
    }
  }'

Mockups aren't rendered when you save. Bulk Create renders them when it makes listings.

The print-on-demand sections

  • type is required in spec and can never change. A section that doesn't fit the type, such as digital on a pod preset, is refused with section_not_for_type.

  • pod_products[] (up to 10): blueprint_id, print_provider_id, options (color, size), prices and size_label_prefix. print_provider_id is required: ListingView never picks a provider for you.

  • slots[] (up to 10): label, fill, enabled and targets[]. Slots are lettered A to J by their position among the enabled slots. fill is "upload_required" (each listing brings its design) or {"fixed": asset_id} (the same design every time).

  • targets[] (up to 20 per slot): product (the product's 1-based position), print_area (id or name), options ({"color": [...]} or "all") and placement.

  • placement: fit, alignment, angle, the size and offset percentages, the maximum inches and margins. Size percentages run from 0 to 100, offsets from −100 to 100 and angle from −360 to 360. Inch fields must fit the print area. Leave placement out to get the app's default placement.

  • mockups[]: template_id and design_slot ("A" to "J"), both required. Optional: index (its image slot, 0 to 19), design_slots_by_region ({region_id: slot}, to put another slot's design in some regions) and placement (fit is cover, contain or stretch).

  • photos[]: static photos by index and asset_id, such as a size chart.

What a print-on-demand create checks

  • The provider prints that product and offers every color and size you chose (pod_option_unknown otherwise).

  • At most 100 variants per product (Printify's limit), 70 options per Etsy variation and 400 combinations in all (Etsy's limits). Over a limit answers POD_VARIANT_LIMIT, and details.limit_kind names which.

  • Every color you sell is covered by at least one enabled slot target (slot_without_color otherwise).

  • Every target's print area exists on that provider (slot_print_area_unknown), and every placement fits it (placement_out_of_bounds).

  • Every mockup template is yours or a shared one, and ready. Its region ids are real, and its design_slot is an enabled slot.

  • At most 20 photos and mockups together, Etsy's image limit (images_over_limit).

  • Every fixed design is yours and ready.

Two things aren't offered yet. Writing an auto-converted (derived) slot answers derived_slots_unsupported. A product option other than color and size answers pod_axis_unsupported.

Digital presets

A digital preset delivers files to buyers. Upload the files first with purpose: "digital_file" (see API: Uploads), then create the preset. This example delivers a PDF and a folder of bonus files, saves into a destination folder, shows a cover design on a mockup and adds a size-chart photo:

{
  "fields": { "name": "Printable planner — PDF + bonus pack" },
  "spec": {
    "type": "digital",
    "digital": {
      "deliverables": [
        { "kind": "file", "asset_id": 88123 },
        { "kind": "collection", "collection_id": 4521 }
      ],
      "parent_collection_id": 4522,
      "design_files": [{ "asset_id": 88124, "label": "Planner cover" }]
    },
    "mockups": [{ "index": 0, "template_id": 77120, "design_slot": "A" }],
    "photos": [{ "index": 1, "asset_id": 88125 }]
  }
}
  • Deliverables. A file must be a ready deliverable. Every file in a collection, sub-folders included, must be yours, ready and scanned clean (collection_file_not_ready). An empty collection is refused (collection_empty). A preset needs at least one deliverable (digital_files_required).

  • How buyers get the files. Each listing delivers one PDF with a download button per deliverable. A preset holds up to 50 deliverables, and its collections can bring up to 200 files to one listing (too_many_collection_files).

  • Design files (up to 10) are ready PNG, JPEG or WebP images used on mockups (design_not_image otherwise).

  • Optional. parent_collection_id (where the files are saved), and pdf_settings for the delivery PDF.

  • Old presets. A digital preset that still carries legacy inline files answers legacy_preset_files when an Etsy draft uses it. Re-save its deliverables in ListingView.

Link deliverables

A preset can still hold a link deliverable: {"kind": "link", "url", "label"}. The link must pass ListingView's link screen, or it is refused with link_not_allowed and details.link_rules naming each rule it broke: an https:// URL of at most 2,048 characters, on the standard port, with no user name or password, no IP address and no reserved or internal host, and a plain-text label of at most 100 characters.

Etsy drafts made through the API never deliver a link: a digital draft whose preset or item carries one is refused with link_deliverables_unsupported. Use a file or collection deliverable for anything an API draft delivers. A deliverable that points at a link saved in your link library reads kind: "saved_link": an edit keeps it in place, and a create from spec doesn't carry it.

Physical presets: variations and attributes

Physical and digital presets can carry Etsy variations. Category attributes work on every preset type.

  • variations[] (up to 2): name and options[] (up to 70 each). ListingView sets property_id from the variation's name and position; leave it out or send it as read. An op that sets it is refused (op_not_supported).

  • variation_inventory is by_option or by_combination, never both: setting one replaces the other. Rows are {options, price, quantity, sku, visible}. Quantity runs from 0 to 999, and a SKU can be up to 32 characters. A new preset with two variations takes by_combination (by_option_two_axes otherwise).

  • varies says which variations set the price, quantity and sku.

  • properties[]: property_id, value_ids[] or values[], and an optional scale_id. Attributes need a category (listing_defaults.taxonomy_id) first.

Attributes are checked against the category's properties before anything is saved: the property must exist for the category, use allowed values and a valid scale, and stay within max_values. A property that only works as a variation is refused as an attribute (property_variation_only). If Etsy's category data can't be read, the write answers EXTERNAL_API_ERROR with the reason taxonomy_unavailable and saves nothing. Retry after retry_after.

Changing taxonomy_id clears the category's attributes, and the answer says properties_cleared: true.

Two variations are locked. On a preset with two variations, the API can't change the variations, their cells or varies. Send them exactly as read, or edit them in ListingView. Any other change answers op_not_supported with the reason two_axis_variations_locked. With two variations, an option can't contain ||| (variation_option_delimiter).

Edit a preset

PATCH /presets/{preset_id} changes only what you send. It requires expected_updated_at: the updated_at from your last read. If the preset changed since, nothing is saved and the answer is CONFLICT with the reason preset_changed.

You can send fields, typed ops[], or both. fields covers the name, listing defaults, shop bindings, slot labels and on/off, and, on a print-on-demand preset, each product's colors, sizes and prices among the variants it already has:

curl -X PATCH https://api.listingview.io/api/v1/presets/1831 \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "expected_updated_at": "2026-10-16T09:12:44.512Z",
    "fields": {
      "pod_products": [{
        "position": 1,
        "product": "Gildan 18000",
        "colors_selected": ["Black", "Navy", "Forest Green", "Maroon"],
        "prices": [
          { "size": "S", "price": 34.99 },
          { "size": "M", "price": 34.99 },
          { "size": "2XL", "price": 38.99 },
          { "color": "Maroon", "size": "S", "price": 32.99 }
        ]
      }]
    }
  }'

This sells two more colors and reprices three sizes in every color. Slot changes work the same way, by position:

{
  "expected_updated_at": "2026-10-16T09:12:44.512Z",
  "fields": {
    "slots": [
      { "position": 1, "label": "Light ink (dark garments)" },
      { "position": 3, "enabled": false }
    ]
  }
}

Add check_shop_id to require every shop-specific id to belong to that shop.

Typed ops

For anything else, send ops[]: up to 100 typed operations, each {op, section, ref?, field?, position?, value?, prices?, target?}. Read the preset with include=all first, so you know each entry's ref.

{
  "expected_updated_at": "2026-10-16T09:12:44.512Z",
  "ops": [{ "op": "replace", "section": "photos", "value": [] }]
}
  • add inserts value into a list at position (1-based; the end by default).

  • remove drops the entry ref names.

  • replace with a ref replaces that entry. Without a ref it replaces the whole list; an empty list clears it.

  • set sets one field: of the entry ref names, or of the section. set order puts the named entries first, in that order.

  • Refs are a 1-based position as read, a ref a read returned (pp_…, sl_…, mk_…, ph_… and so on), or, for slots, the letter or the exact label. Refs always mean the preset as read at expected_updated_at, never shifted by earlier ops in the same call. A label two slots share answers ref_ambiguous; an entry an earlier op removed, ref_removed.

  • Order. Ops run before fields. Edit each section once per call (section_edited_twice otherwise).

What ops cover on a print-on-demand preset:

  • Products. Add a product from the catalog, or remove one. A product a slot still targets can be removed only if the same call retargets or removes that slot (slot_target_dangling otherwise).

  • Provider. set print_provider_id switches a product to another provider. Print areas are matched by name. Percentage placements are kept and listed in placement_rescaled[]. An inch field that no longer fits answers placement_out_of_bounds. The product itself (its blueprint_id) can't change in an edit (pod_base_mismatch): to use another product, swap it on a copy.

  • Colors and sizes. set options changes them, including variants the provider offers that the preset didn't have yet. It is the only op that takes prices: pod.v1 entries for the new cells. Any you leave out are listed in prices_defaulted[]. A new color must be covered by a slot.

  • Slots. Add, remove, relabel, switch on or off, retarget or re-place a slot. keep_only keeps the named slots and removes the rest. Mockups follow their slot, not its letter.

  • Mockups and photos. Replace, add, remove, reorder or clear them.

Ops apply in order to one copy of the preset, and the result is checked once at the end, so a step in between may be invalid. The call saves everything or nothing.

The answer carries diff, the sections that changed, and for print-on-demand changes pod_diff: products added, removed or changed, variants added, removed or repriced, and the slots, mockups and photos that changed. To check every variant afterward, read include=pod_variants.

The exact shape of every op is in the API reference.

Print-on-demand edits are strict

A print-on-demand write must leave the whole print-on-demand setup valid, including problems the preset already had: those are listed as "Already on the preset — fix it in this request". If ListingView can't fully read a preset's print-on-demand data, every change to it is refused with pod_preset_unreadable; edit that preset in ListingView. Two known limits:

  • A slot target that points at a product the preset no longer has makes the preset unreadable this way.

  • A select on a preset made in the app overrides a cell the seller had switched off inside the new selection. A row that is already on but has no price is still refused as unpriced.

Copy a preset

POST /presets/{preset_id}/copy copies a preset, optionally with fields and ops[] edits on the copy, and optionally to another of your shops. Without a name the copy is called "Copy of" plus the original's name. A name one of your presets already has answers CONFLICT with the reason name_taken and existing_preset_id. expected_updated_at is optional on a copy.

{
  "preset_id": 2041,
  "fields": {
    "name": "Linen apron — gift edition",
    "listing_defaults": { "price": 48 }
  }
}

preset_id goes in the path. The rest is the body.

Copy to another shop

Send target_shop_id. Shop-specific ids are checked against that shop and never swapped. A mismatch is refused with that shop's options, so you can resend with a top-level shop_bindings chosen from them (not also inside fields; both answer duplicate). A print-on-demand preset copied to another shop needs pod_blanks: "keep" (PRESET_POD_BLANK_ACK_REQUIRED otherwise), and that shop must be linked to Printify (PRINTIFY_SHOP_NOT_MAPPED otherwise). Every print-on-demand copy lists its products in carried_blanks[]:

{
  "target_shop_id": 38123456,
  "fields": { "name": "Halloween sweatshirt — second shop" },
  "shop_bindings": {
    "shipping_profile_id": 209876543299,
    "return_policy_id": null,
    "shop_section_id": null,
    "readiness_state_id": 987654399
  },
  "pod_blanks": "keep"
}

Swap a product

pod_product_ops[] swaps a print-on-demand product of the copy for a product block from your other presets. This example copies a sweatshirt-and-tee preset and swaps its tee for a Comfort Colors 1717 block made from two presets: one sells the dark colors and one the light colors.

curl -X POST https://api.listingview.io/api/v1/presets/1837/copy \
  -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": { "name": "Sweatshirt + CC tee · light and dark" },
    "pod_product_ops": [{
      "op": "replace",
      "target": { "product": 2 },
      "source": { "preset_id": 859, "product": 1 },
      "colors": { "union_of": [ { "preset_id": 859, "product": 1 }, { "preset_id": 1667, "product": 1 } ] },
      "prices": "per_color_from_owner",
      "slot_color_sources": {
        "Light Designs Here": { "preset_id": 859, "product": 1 },
        "Dark Designs Here": { "preset_id": 1667, "product": 1 }
      },
      "mockups": "carry_from_source",
      "photos_from_source": true
    }]
  }'
  • target is the product to replace, by its position in the preset you copy.

  • source is the product to bring in, by preset and position. It arrives with its provider and variants.

  • colors (required) is "source", {"union_of": [...]} or a list of colors.

  • prices (required) is "source", "per_color_from_owner" or a list of pod.v1 entries. per_color_from_owner prices each color the way the preset that sells it does.

  • slot_color_sources rebinds each slot to the colors of one source. Slot targets on the products you keep don't change.

  • mockups (required) is "carry_from_source", "keep" or "drop" (removes the replaced product's mockups). photos_from_source: true adds the source's photos.

  • prices_from_option (a set op on a copy only) gives every option of the other axis the named option's per-cell prices.

The result goes through every check a create runs. If any color would end up without a design, nothing is written. Each call takes up to 10 product ops and up to 5 source presets (swap_sources_over_limit); union_of takes up to 5 sources.

  • A source that isn't yours answers 404, naming the field, never the preset's owner.

  • A slot that can't move answers swap_print_area_missing when the new product has no matching print area.

  • A source that can't be read answers swap_source_unreadable.

  • Mockups showing both the replaced and a kept product answer swap_mockups_ambiguous. Replace those mockups in ops.

A swap works on copies only, never on PATCH.

Delete a preset

DELETE /presets/{preset_id} deletes one preset. Listings already made from it are unaffected.

{
  "deleted": true,
  "preset_id": 1201,
  "name": "Test 3",
  "deleted_at": "2026-10-16T14:05:09.412Z",
  "recoverable_until": "2026-11-15T14:05:09.412Z",
  "deletes_remaining_today": 19
}
  • In use. A preset a running or waiting bulk create or Etsy-draft job uses is refused with preset_in_use, naming the jobs.

  • Recovery. ListingView support can restore a deleted preset until recoverable_until, at least 30 days.

Where the API is stricter than the app

The app editor sometimes saves a preset that would fail later. The API refuses it up front instead:

  • Files that aren't ready. A design or file that isn't processed and scanned is refused, with an asset_not_ready entry in details.errors[]. It never becomes an empty slot.

  • Another shop's settings. A shipping profile, return policy or other setting from another shop is refused with PRESET_SHOP_MISMATCH, which lists the right shop's options in target_options. It is never swapped for the shop's first profile.

  • Mockup templates. A template that isn't yours or a shared one, isn't ready, or points at a missing slot is refused. The app only warns.

  • Design files you don't own are refused, not dropped.

  • Category attributes are checked when you save, not skipped when the listing is made.

Conflicts, refusals and test keys

  • Every problem at once. A refused write answers VALIDATION_FAILED with every problem in details.errors[]. Each entry has field (a path into your request), code, message, and candidates[] where there is a choice. Past 50 problems you get the first 50, plus details.error_count and truncated: true. Two exceptions: a variant limit answers POD_VARIANT_LIMIT, and a catalog that is still syncing answers SERVICE_UNAVAILABLE pod_catalog_pending.

  • Size. A preset body can be up to 256 KB. A bigger one answers 413 VALIDATION_FAILED with the reason too_large and limit_kind: "preset_body"; split the change into two calls.

  • Someone else edited it. CONFLICT with details.reason: "preset_changed", plus details.current and details.reread (how to read it again). Read the preset again, re-apply your change and retry. After 3 conflicts in a row, stop and tell the person: someone is editing at the same time.

  • Read-only parts. A section the API can only read answers section_read_only: send it exactly as read, or edit it in ListingView. A typed write over a stored section the API can't read at all answers PRESET_UNREADABLE with the reason section_unreadable.

  • Test keys. With an lv_test_ key, every write runs the full check and answers would_create, would_update or would_copy with the report. Nothing is saved, and nothing counts against your daily limits, though a limit that is already used up still refuses the call. A test-key edit includes pod_diff when print-on-demand cells would change; a test-key copy doesn't.

The full retry rules are in API: Getting started.

From the CLI or an AI app

  • CLI: listingview presets get 1667 --include spec,pod_variants --json reads a preset. On a terminal, listingview presets get 1667 prints the design-slot table. presets create, presets update, presets copy and presets delete take the request as flags or as JSON with --input @preset.json. The building-block reads are commands too, such as listingview pod-products list; listingview --help lists them all. See API: Command-line tool.

  • MCP: list_presets reads presets and their building blocks. taxonomy_id reads a category's attributes, shop_id a shop's profiles, and q, supplier, category, min_cost or max_cost search the print-on-demand catalog; add product_id, then provider_id, to drill in. On a preset read, include adds sections.

  • MCP: manage_preset creates, updates or copies a preset with operation (create, update or copy), typed fields or spec, ops, and pod_product_ops on a copy. To delete a preset, use delete_preset_or_draft with kind: "preset". See API: Connect AI apps.

Every endpoint above, with its full request and response schema, is in the API reference. Every error code is on the errors page.

Related features

Still need help?

Can’t find what you’re looking for? Our team is happy to help.

A.I. Summary