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
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 needpresets: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: truefirst. See API: Uploads.Daily limits. An account can create or copy 100 presets a day through the API (
DATA_BUDGET_EXCEEDEDwith the reasonpreset_writes_dailypast 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:
| What it adds |
|---|---|
| Every variant of each print-on-demand product: |
| Where each slot prints: product, print area with its size in inches, colors and placement. |
| Each mockup entry with |
| The preset's static photos, by asset id, with thumbnails. |
| Deliverables, design files, the destination folder and delivery-PDF settings. |
| Etsy variations, the variation inventory and which variations set price, quantity and SKU. |
| The category and the attributes Etsy shows buyers. |
| The preset in the exact shape a create accepts. See below. |
| Everything above. |
Entry refs. Entries carry an opaque
ref, such aspp_…for a product orsl_…for a slot. Edits use these refs to name an entry.etsy_typeisphysical,downloadorboth.A section ListingView can't read comes back
nulland is named inunreadable_sections[]. The rest of the answer still arrives.Values the write model can't carry exactly are listed in
unaddressable[], each with itseffect:read_only,keptorchanged_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 readsurl: nullwithredacted: "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: nullwithspec_unavailable_reason: "spec_not_writable".unaddressable[]names each of those sections withfamily_unverified.Presets made in the app carry their shop, which a create can't recreate. They read
spec: null, andunaddressable[]sayscontent_not_recreatable.Other gaps.
not_writable,round_trip_mismatchandspec_too_large(the spec would be over the preset body limit) also leavespecnull. A two-variation preset whose app inventory prices some combinations from the per-variation cells readsper_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 ofqmust match, up to 10 words. Filter bycategory,supplier(printify),min_costandmax_cost, and page withlimitandcursor.product_idis the Printify blueprint id.A product (
GET /pod-products/{product_id}) lists its activeproviders[]:provider_id,name,recommended(Printify's default),location, in-stockcolors[](name,hex) andsizes[],min_cost,max_costandshipping_summary[].A provider (
GET /pod-products/{product_id}/providers/{provider_id}) lists everyvariants[]row, out-of-stock ones withavailable: false, and itsprint_areas[]. A slot target names a print area by itsid(such aspa_1) or itsname:
{ "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_FOUNDwith the reasonpod_blueprint_unknown; one Printify dropped,pod_blueprint_unavailable. An unknown or inactive provider answerspod_provider_unknownorpod_provider_inactive, withcandidates.Still syncing. While the catalog or a provider's variants are still syncing, you get a retryable
SERVICE_UNAVAILABLEwith the reasonpod_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.pointsare 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.revisionchanges whenever the photo or a region is redrawn. Cache anything you build from a template on itsrevision.
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 nothumbnail_urleither. That includes PSDs registered through SudoMock (provider: "sudomock").Still being made. A new or redrawn template can answer
preview_url: nullwithpreview_pending: true. Read it again later.preview_pending: falsewith 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 |
|
|
|
|
|
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) andshop_bindings(shipping profile, return policy, section, production partners, processing profile).spec: the preset'stype(pod,digitalorphysical) and its typed sections:pod_products,slots,mockups,photos,digital,variations,variation_inventory,variesandproperties. Withoutspecyou 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
Find the product and its provider with the print-on-demand product reads above.
Read the provider's print areas, so each slot can target one by
idorname.Pick mockup templates with
GET /mockup-templates, and the shop's settings withGET /my-shop/profiles.Send
POST /presetswithspec.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
typeis required inspecand can never change. A section that doesn't fit the type, such asdigitalon apodpreset, is refused withsection_not_for_type.pod_products[](up to 10):blueprint_id,print_provider_id,options(color,size),pricesandsize_label_prefix.print_provider_idis required: ListingView never picks a provider for you.slots[](up to 10):label,fill,enabledandtargets[]. Slots are lettered A to J by their position among the enabled slots.fillis"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(idorname),options({"color": [...]}or"all") andplacement.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 andanglefrom −360 to 360. Inch fields must fit the print area. Leaveplacementout to get the app's default placement.mockups[]:template_idanddesign_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) andplacement(fitiscover,containorstretch).photos[]: static photos byindexandasset_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_unknownotherwise).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, anddetails.limit_kindnames which.Every color you sell is covered by at least one enabled slot target (
slot_without_colorotherwise).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_slotis an enabled slot.At most 20 photos and mockups together, Etsy's image limit (
images_over_limit).Every
fixeddesign 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
filemust be a ready deliverable. Every file in acollection, 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_imageotherwise).Optional.
parent_collection_id(where the files are saved), andpdf_settingsfor the delivery PDF.Old presets. A digital preset that still carries legacy inline files answers
legacy_preset_fileswhen 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):nameandoptions[](up to 70 each). ListingView setsproperty_idfrom 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_inventoryisby_optionorby_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 takesby_combination(by_option_two_axesotherwise).variessays which variations set theprice,quantityandsku.properties[]:property_id,value_ids[]orvalues[], and an optionalscale_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": [] }] }
addinsertsvalueinto a list atposition(1-based; the end by default).removedrops the entryrefnames.replacewith arefreplaces that entry. Without arefit replaces the whole list; an empty list clears it.setsets onefield: of the entryrefnames, or of the section.set orderputs 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 atexpected_updated_at, never shifted by earlier ops in the same call. A label two slots share answersref_ambiguous; an entry an earlier op removed,ref_removed.Order. Ops run before
fields. Edit each section once per call (section_edited_twiceotherwise).
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_danglingotherwise).Provider.
setprint_provider_idswitches a product to another provider. Print areas are matched by name. Percentage placements are kept and listed inplacement_rescaled[]. An inch field that no longer fits answersplacement_out_of_bounds. The product itself (itsblueprint_id) can't change in an edit (pod_base_mismatch): to use another product, swap it on a copy.Colors and sizes.
setoptionschanges them, including variants the provider offers that the preset didn't have yet. It is the only op that takesprices: pod.v1 entries for the new cells. Any you leave out are listed inprices_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_onlykeeps 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 }] }'
targetis the product to replace, by its position in the preset you copy.sourceis 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_ownerprices each color the way the preset that sells it does.slot_color_sourcesrebinds 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: trueadds the source's photos.prices_from_option(asetop 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_missingwhen 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 inops.
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_readyentry indetails.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 intarget_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_FAILEDwith every problem indetails.errors[]. Each entry hasfield(a path into your request),code,message, andcandidates[]where there is a choice. Past 50 problems you get the first 50, plusdetails.error_countandtruncated: true. Two exceptions: a variant limit answersPOD_VARIANT_LIMIT, and a catalog that is still syncing answersSERVICE_UNAVAILABLEpod_catalog_pending.Size. A preset body can be up to 256 KB. A bigger one answers
413VALIDATION_FAILEDwith the reasontoo_largeandlimit_kind: "preset_body"; split the change into two calls.Someone else edited it.
CONFLICTwithdetails.reason: "preset_changed", plusdetails.currentanddetails.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 answersPRESET_UNREADABLEwith the reasonsection_unreadable.Test keys. With an
lv_test_key, every write runs the full check and answerswould_create,would_updateorwould_copywith 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 includespod_diffwhen 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 --jsonreads a preset. On a terminal,listingview presets get 1667prints the design-slot table.presets create,presets update,presets copyandpresets deletetake the request as flags or as JSON with--input @preset.json. The building-block reads are commands too, such aslistingview pod-products list;listingview --helplists them all. See API: Command-line tool.MCP:
list_presetsreads presets and their building blocks.taxonomy_idreads a category's attributes,shop_ida shop's profiles, andq,supplier,category,min_costormax_costsearch the print-on-demand catalog; addproduct_id, thenprovider_id, to drill in. On a preset read,includeadds sections.MCP:
manage_presetcreates, updates or copies a preset withoperation(create,updateorcopy), typedfieldsorspec,ops, andpod_product_opson a copy. To delete a preset, usedelete_preset_or_draftwithkind: "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
API: Getting started: keys, idempotency keys, errors and the retry rules
API: Uploads: get designs and deliverables into your library
API: Etsy drafts: turn a preset and your designs into Etsy drafts
API: Connect AI apps:
list_presetsandmanage_presetin Claude Code or CursorAPI: Command-line tool: the same calls as terminal commands
Presets: presets in the app
POD Presets & Bulk Create: print-on-demand presets in the app
Using the ListingView API, MCP server & CLI: the seller guide to keys, presets and Etsy drafts
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 needpresets: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: truefirst. See API: Uploads.Daily limits. An account can create or copy 100 presets a day through the API (
DATA_BUDGET_EXCEEDEDwith the reasonpreset_writes_dailypast 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:
| What it adds |
|---|---|
| Every variant of each print-on-demand product: |
| Where each slot prints: product, print area with its size in inches, colors and placement. |
| Each mockup entry with |
| The preset's static photos, by asset id, with thumbnails. |
| Deliverables, design files, the destination folder and delivery-PDF settings. |
| Etsy variations, the variation inventory and which variations set price, quantity and SKU. |
| The category and the attributes Etsy shows buyers. |
| The preset in the exact shape a create accepts. See below. |
| Everything above. |
Entry refs. Entries carry an opaque
ref, such aspp_…for a product orsl_…for a slot. Edits use these refs to name an entry.etsy_typeisphysical,downloadorboth.A section ListingView can't read comes back
nulland is named inunreadable_sections[]. The rest of the answer still arrives.Values the write model can't carry exactly are listed in
unaddressable[], each with itseffect:read_only,keptorchanged_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 readsurl: nullwithredacted: "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: nullwithspec_unavailable_reason: "spec_not_writable".unaddressable[]names each of those sections withfamily_unverified.Presets made in the app carry their shop, which a create can't recreate. They read
spec: null, andunaddressable[]sayscontent_not_recreatable.Other gaps.
not_writable,round_trip_mismatchandspec_too_large(the spec would be over the preset body limit) also leavespecnull. A two-variation preset whose app inventory prices some combinations from the per-variation cells readsper_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 ofqmust match, up to 10 words. Filter bycategory,supplier(printify),min_costandmax_cost, and page withlimitandcursor.product_idis the Printify blueprint id.A product (
GET /pod-products/{product_id}) lists its activeproviders[]:provider_id,name,recommended(Printify's default),location, in-stockcolors[](name,hex) andsizes[],min_cost,max_costandshipping_summary[].A provider (
GET /pod-products/{product_id}/providers/{provider_id}) lists everyvariants[]row, out-of-stock ones withavailable: false, and itsprint_areas[]. A slot target names a print area by itsid(such aspa_1) or itsname:
{ "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_FOUNDwith the reasonpod_blueprint_unknown; one Printify dropped,pod_blueprint_unavailable. An unknown or inactive provider answerspod_provider_unknownorpod_provider_inactive, withcandidates.Still syncing. While the catalog or a provider's variants are still syncing, you get a retryable
SERVICE_UNAVAILABLEwith the reasonpod_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.pointsare 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.revisionchanges whenever the photo or a region is redrawn. Cache anything you build from a template on itsrevision.
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 nothumbnail_urleither. That includes PSDs registered through SudoMock (provider: "sudomock").Still being made. A new or redrawn template can answer
preview_url: nullwithpreview_pending: true. Read it again later.preview_pending: falsewith 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 |
|
|
|
|
|
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) andshop_bindings(shipping profile, return policy, section, production partners, processing profile).spec: the preset'stype(pod,digitalorphysical) and its typed sections:pod_products,slots,mockups,photos,digital,variations,variation_inventory,variesandproperties. Withoutspecyou 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
Find the product and its provider with the print-on-demand product reads above.
Read the provider's print areas, so each slot can target one by
idorname.Pick mockup templates with
GET /mockup-templates, and the shop's settings withGET /my-shop/profiles.Send
POST /presetswithspec.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
typeis required inspecand can never change. A section that doesn't fit the type, such asdigitalon apodpreset, is refused withsection_not_for_type.pod_products[](up to 10):blueprint_id,print_provider_id,options(color,size),pricesandsize_label_prefix.print_provider_idis required: ListingView never picks a provider for you.slots[](up to 10):label,fill,enabledandtargets[]. Slots are lettered A to J by their position among the enabled slots.fillis"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(idorname),options({"color": [...]}or"all") andplacement.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 andanglefrom −360 to 360. Inch fields must fit the print area. Leaveplacementout to get the app's default placement.mockups[]:template_idanddesign_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) andplacement(fitiscover,containorstretch).photos[]: static photos byindexandasset_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_unknownotherwise).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, anddetails.limit_kindnames which.Every color you sell is covered by at least one enabled slot target (
slot_without_colorotherwise).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_slotis an enabled slot.At most 20 photos and mockups together, Etsy's image limit (
images_over_limit).Every
fixeddesign 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
filemust be a ready deliverable. Every file in acollection, 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_imageotherwise).Optional.
parent_collection_id(where the files are saved), andpdf_settingsfor the delivery PDF.Old presets. A digital preset that still carries legacy inline files answers
legacy_preset_fileswhen 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):nameandoptions[](up to 70 each). ListingView setsproperty_idfrom 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_inventoryisby_optionorby_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 takesby_combination(by_option_two_axesotherwise).variessays which variations set theprice,quantityandsku.properties[]:property_id,value_ids[]orvalues[], and an optionalscale_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": [] }] }
addinsertsvalueinto a list atposition(1-based; the end by default).removedrops the entryrefnames.replacewith arefreplaces that entry. Without arefit replaces the whole list; an empty list clears it.setsets onefield: of the entryrefnames, or of the section.set orderputs 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 atexpected_updated_at, never shifted by earlier ops in the same call. A label two slots share answersref_ambiguous; an entry an earlier op removed,ref_removed.Order. Ops run before
fields. Edit each section once per call (section_edited_twiceotherwise).
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_danglingotherwise).Provider.
setprint_provider_idswitches a product to another provider. Print areas are matched by name. Percentage placements are kept and listed inplacement_rescaled[]. An inch field that no longer fits answersplacement_out_of_bounds. The product itself (itsblueprint_id) can't change in an edit (pod_base_mismatch): to use another product, swap it on a copy.Colors and sizes.
setoptionschanges them, including variants the provider offers that the preset didn't have yet. It is the only op that takesprices: pod.v1 entries for the new cells. Any you leave out are listed inprices_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_onlykeeps 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 }] }'
targetis the product to replace, by its position in the preset you copy.sourceis 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_ownerprices each color the way the preset that sells it does.slot_color_sourcesrebinds 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: trueadds the source's photos.prices_from_option(asetop 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_missingwhen 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 inops.
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_readyentry indetails.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 intarget_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_FAILEDwith every problem indetails.errors[]. Each entry hasfield(a path into your request),code,message, andcandidates[]where there is a choice. Past 50 problems you get the first 50, plusdetails.error_countandtruncated: true. Two exceptions: a variant limit answersPOD_VARIANT_LIMIT, and a catalog that is still syncing answersSERVICE_UNAVAILABLEpod_catalog_pending.Size. A preset body can be up to 256 KB. A bigger one answers
413VALIDATION_FAILEDwith the reasontoo_largeandlimit_kind: "preset_body"; split the change into two calls.Someone else edited it.
CONFLICTwithdetails.reason: "preset_changed", plusdetails.currentanddetails.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 answersPRESET_UNREADABLEwith the reasonsection_unreadable.Test keys. With an
lv_test_key, every write runs the full check and answerswould_create,would_updateorwould_copywith 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 includespod_diffwhen 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 --jsonreads a preset. On a terminal,listingview presets get 1667prints the design-slot table.presets create,presets update,presets copyandpresets deletetake the request as flags or as JSON with--input @preset.json. The building-block reads are commands too, such aslistingview pod-products list;listingview --helplists them all. See API: Command-line tool.MCP:
list_presetsreads presets and their building blocks.taxonomy_idreads a category's attributes,shop_ida shop's profiles, andq,supplier,category,min_costormax_costsearch the print-on-demand catalog; addproduct_id, thenprovider_id, to drill in. On a preset read,includeadds sections.MCP:
manage_presetcreates, updates or copies a preset withoperation(create,updateorcopy), typedfieldsorspec,ops, andpod_product_opson a copy. To delete a preset, usedelete_preset_or_draftwithkind: "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
API: Getting started: keys, idempotency keys, errors and the retry rules
API: Uploads: get designs and deliverables into your library
API: Etsy drafts: turn a preset and your designs into Etsy drafts
API: Connect AI apps:
list_presetsandmanage_presetin Claude Code or CursorAPI: Command-line tool: the same calls as terminal commands
Presets: presets in the app
POD Presets & Bulk Create: print-on-demand presets in the app
Using the ListingView API, MCP server & CLI: the seller guide to keys, presets and Etsy drafts
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 needpresets: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: truefirst. See API: Uploads.Daily limits. An account can create or copy 100 presets a day through the API (
DATA_BUDGET_EXCEEDEDwith the reasonpreset_writes_dailypast 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:
| What it adds |
|---|---|
| Every variant of each print-on-demand product: |
| Where each slot prints: product, print area with its size in inches, colors and placement. |
| Each mockup entry with |
| The preset's static photos, by asset id, with thumbnails. |
| Deliverables, design files, the destination folder and delivery-PDF settings. |
| Etsy variations, the variation inventory and which variations set price, quantity and SKU. |
| The category and the attributes Etsy shows buyers. |
| The preset in the exact shape a create accepts. See below. |
| Everything above. |
Entry refs. Entries carry an opaque
ref, such aspp_…for a product orsl_…for a slot. Edits use these refs to name an entry.etsy_typeisphysical,downloadorboth.A section ListingView can't read comes back
nulland is named inunreadable_sections[]. The rest of the answer still arrives.Values the write model can't carry exactly are listed in
unaddressable[], each with itseffect:read_only,keptorchanged_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 readsurl: nullwithredacted: "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: nullwithspec_unavailable_reason: "spec_not_writable".unaddressable[]names each of those sections withfamily_unverified.Presets made in the app carry their shop, which a create can't recreate. They read
spec: null, andunaddressable[]sayscontent_not_recreatable.Other gaps.
not_writable,round_trip_mismatchandspec_too_large(the spec would be over the preset body limit) also leavespecnull. A two-variation preset whose app inventory prices some combinations from the per-variation cells readsper_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 ofqmust match, up to 10 words. Filter bycategory,supplier(printify),min_costandmax_cost, and page withlimitandcursor.product_idis the Printify blueprint id.A product (
GET /pod-products/{product_id}) lists its activeproviders[]:provider_id,name,recommended(Printify's default),location, in-stockcolors[](name,hex) andsizes[],min_cost,max_costandshipping_summary[].A provider (
GET /pod-products/{product_id}/providers/{provider_id}) lists everyvariants[]row, out-of-stock ones withavailable: false, and itsprint_areas[]. A slot target names a print area by itsid(such aspa_1) or itsname:
{ "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_FOUNDwith the reasonpod_blueprint_unknown; one Printify dropped,pod_blueprint_unavailable. An unknown or inactive provider answerspod_provider_unknownorpod_provider_inactive, withcandidates.Still syncing. While the catalog or a provider's variants are still syncing, you get a retryable
SERVICE_UNAVAILABLEwith the reasonpod_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.pointsare 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.revisionchanges whenever the photo or a region is redrawn. Cache anything you build from a template on itsrevision.
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 nothumbnail_urleither. That includes PSDs registered through SudoMock (provider: "sudomock").Still being made. A new or redrawn template can answer
preview_url: nullwithpreview_pending: true. Read it again later.preview_pending: falsewith 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 |
|
|
|
|
|
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) andshop_bindings(shipping profile, return policy, section, production partners, processing profile).spec: the preset'stype(pod,digitalorphysical) and its typed sections:pod_products,slots,mockups,photos,digital,variations,variation_inventory,variesandproperties. Withoutspecyou 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
Find the product and its provider with the print-on-demand product reads above.
Read the provider's print areas, so each slot can target one by
idorname.Pick mockup templates with
GET /mockup-templates, and the shop's settings withGET /my-shop/profiles.Send
POST /presetswithspec.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
typeis required inspecand can never change. A section that doesn't fit the type, such asdigitalon apodpreset, is refused withsection_not_for_type.pod_products[](up to 10):blueprint_id,print_provider_id,options(color,size),pricesandsize_label_prefix.print_provider_idis required: ListingView never picks a provider for you.slots[](up to 10):label,fill,enabledandtargets[]. Slots are lettered A to J by their position among the enabled slots.fillis"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(idorname),options({"color": [...]}or"all") andplacement.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 andanglefrom −360 to 360. Inch fields must fit the print area. Leaveplacementout to get the app's default placement.mockups[]:template_idanddesign_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) andplacement(fitiscover,containorstretch).photos[]: static photos byindexandasset_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_unknownotherwise).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, anddetails.limit_kindnames which.Every color you sell is covered by at least one enabled slot target (
slot_without_colorotherwise).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_slotis an enabled slot.At most 20 photos and mockups together, Etsy's image limit (
images_over_limit).Every
fixeddesign 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
filemust be a ready deliverable. Every file in acollection, 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_imageotherwise).Optional.
parent_collection_id(where the files are saved), andpdf_settingsfor the delivery PDF.Old presets. A digital preset that still carries legacy inline files answers
legacy_preset_fileswhen 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):nameandoptions[](up to 70 each). ListingView setsproperty_idfrom 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_inventoryisby_optionorby_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 takesby_combination(by_option_two_axesotherwise).variessays which variations set theprice,quantityandsku.properties[]:property_id,value_ids[]orvalues[], and an optionalscale_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": [] }] }
addinsertsvalueinto a list atposition(1-based; the end by default).removedrops the entryrefnames.replacewith arefreplaces that entry. Without arefit replaces the whole list; an empty list clears it.setsets onefield: of the entryrefnames, or of the section.set orderputs 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 atexpected_updated_at, never shifted by earlier ops in the same call. A label two slots share answersref_ambiguous; an entry an earlier op removed,ref_removed.Order. Ops run before
fields. Edit each section once per call (section_edited_twiceotherwise).
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_danglingotherwise).Provider.
setprint_provider_idswitches a product to another provider. Print areas are matched by name. Percentage placements are kept and listed inplacement_rescaled[]. An inch field that no longer fits answersplacement_out_of_bounds. The product itself (itsblueprint_id) can't change in an edit (pod_base_mismatch): to use another product, swap it on a copy.Colors and sizes.
setoptionschanges them, including variants the provider offers that the preset didn't have yet. It is the only op that takesprices: pod.v1 entries for the new cells. Any you leave out are listed inprices_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_onlykeeps 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 }] }'
targetis the product to replace, by its position in the preset you copy.sourceis 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_ownerprices each color the way the preset that sells it does.slot_color_sourcesrebinds 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: trueadds the source's photos.prices_from_option(asetop 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_missingwhen 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 inops.
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_readyentry indetails.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 intarget_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_FAILEDwith every problem indetails.errors[]. Each entry hasfield(a path into your request),code,message, andcandidates[]where there is a choice. Past 50 problems you get the first 50, plusdetails.error_countandtruncated: true. Two exceptions: a variant limit answersPOD_VARIANT_LIMIT, and a catalog that is still syncing answersSERVICE_UNAVAILABLEpod_catalog_pending.Size. A preset body can be up to 256 KB. A bigger one answers
413VALIDATION_FAILEDwith the reasontoo_largeandlimit_kind: "preset_body"; split the change into two calls.Someone else edited it.
CONFLICTwithdetails.reason: "preset_changed", plusdetails.currentanddetails.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 answersPRESET_UNREADABLEwith the reasonsection_unreadable.Test keys. With an
lv_test_key, every write runs the full check and answerswould_create,would_updateorwould_copywith 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 includespod_diffwhen 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 --jsonreads a preset. On a terminal,listingview presets get 1667prints the design-slot table.presets create,presets update,presets copyandpresets deletetake the request as flags or as JSON with--input @preset.json. The building-block reads are commands too, such aslistingview pod-products list;listingview --helplists them all. See API: Command-line tool.MCP:
list_presetsreads presets and their building blocks.taxonomy_idreads a category's attributes,shop_ida shop's profiles, andq,supplier,category,min_costormax_costsearch the print-on-demand catalog; addproduct_id, thenprovider_id, to drill in. On a preset read,includeadds sections.MCP:
manage_presetcreates, updates or copies a preset withoperation(create,updateorcopy), typedfieldsorspec,ops, andpod_product_opson a copy. To delete a preset, usedelete_preset_or_draftwithkind: "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
API: Getting started: keys, idempotency keys, errors and the retry rules
API: Uploads: get designs and deliverables into your library
API: Etsy drafts: turn a preset and your designs into Etsy drafts
API: Connect AI apps:
list_presetsandmanage_presetin Claude Code or CursorAPI: Command-line tool: the same calls as terminal commands
Presets: presets in the app
POD Presets & Bulk Create: print-on-demand presets in the app
Using the ListingView API, MCP server & CLI: the seller guide to keys, presets and Etsy drafts
Still need help?
Can’t find what you’re looking for? Our team is happy to help.
A.I. Summary