API, MCP & CLI
API: Etsy drafts
Turn a ListingView preset and your designs into Etsy drafts through the API: stage a batch for a person to review in Saved listings, submit it as they left it, poll the job, and delete and redraft safely.
Updated
With a Drafting Agent key, an agent turns a preset and your designs into Etsy drafts. Drafts are hidden from shoppers. The agent can send a batch straight to Etsy, or stage it first so a person can check it in ListingView.
This guide covers both, then the staged loop from start to finish:
Write the listing copy with AI.
Stage the batch. Nothing reaches Etsy.
A person reviews and edits it in ListingView, under Saved listings.
Read the batch as the person left it.
Update it in one call, if anything still needs to change.
Submit it. ListingView makes the Etsy drafts.
Poll the job, then find it later in the job history.
New to the API? Start with API: Getting started. To build the preset first, see API: Presets.
What you need
A Drafting Agent key. Creating, staging, editing, submitting and deleting need
etsy_drafts:write, which a Drafting Agent key holds. Staging writes nothing to Etsy, but it still needs that scope.Plus plan or higher to create, stage, edit, submit and delete, and to poll one job.
Reading after a downgrade. The staged-batch list, one staged batch and the job history stay readable below Plus for a key that holds
catalog:read. A key that holds onlyetsy_drafts:writereads them on Plus or higher.A working preset and ready designs. See API: Presets and API: Uploads.
A Drafting Agent key can never publish. Drafts go live only when someone publishes them. See API: Publish & reprice.
Create Etsy drafts now
POST /etsy-drafts drafts a batch straight to Etsy from one of your presets. Fill each of the preset's upload slots with a ready design's asset id. An idempotency key is required.
curl -X POST https://api.listingview.io/api/v1/etsy-drafts \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: drops-2026-10-16-ghost-tee" \ -H "Content-Type: application/json" \ -d '{ "shop_id": 38123456, "preset_id": 1667, "expected_preset_updated_at": "2026-10-14T18:22:05.123Z", "items": [{ "slots": { "A": 90211 }, "title": "Retro Ghost Tee, Spooky Season Shirt", "tags": ["halloween shirt", "ghost tee", "spooky season"], "client_item_id": "drop-0412-ghost" }] }'
{ "job_id": "edj_48213", "status": "pending", "replayed": false, "shop_id": 38123456, "preset_id": 1667, "item_count": 1, "next_poll_after_ms": 5000, "next_step": "Poll get_bulk_job_status with this job_id; drafts appear one by one. Wait next_poll_after_ms between calls." }
The whole job is admitted or refused. Your plan's per-job and per-shop daily draft caps apply.
GET /usageshows them.expected_preset_updated_atis the preset'supdated_atfrom your read. If the preset changed since, nothing is drafted (preset_changed).One file, two slots. The same asset may fill two slots, for example a light and a dark slot.
The same key returns the same job. Resending the identical request with the same key answers
replayed: trueand the samejob_id, for as long as the job exists. The same key with a different body is refused withidempotency_key_reused.The same item under a new key is refused. A
client_item_idyou sent to this shop in the last 7 days is refused with409 DUPLICATE_ITEMwhile it is still queued or processing, once it became a draft, or when its outcome is unknown.details.duplicates[]names each id and its earlier status. It can go again only if it failed withretry_safe: true, was cancelled before it ran, or was skipped by a job that ended.Test keys answer the plan and write nothing.
Poll the job as in step 7.
1. Write the copy first
Staging refuses inline AI copy with ai_requires_submit. Write the copy first with POST /ai/listing-copy, then stage the batch with the finished copy.
curl -X POST https://api.listingview.io/api/v1/ai/listing-copy \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "design_asset_ids": [88123], "current_copy": { "title": "Sunset Mountain Sweatshirt", "description": "Retro sunset crewneck in soft white ink.", "tags": ["sunset sweatshirt", "mountain shirt", "hiking gift"], "price": 34.5 }, "instructions": "Cozy autumn tone. Mention the night sky.", "use_custom_instructions": true }'
Improve or write fresh. With
current_copy, the AI improves that copy. Without it, the AI writes from the artwork alone.current_copyneeds a title, description or tags; a price alone is refused.House instructions.
use_custom_instructionsis on by default. It applies the seller's saved Custom Instructions in ListingView, in addition to yourinstructions.The answer has
title,tags,description,fields,saw_artwork,model,tokens_charged,applied: false,warnings[]andgrounding.status(grounded,candidates-only,skippedorunknown). Nothing is saved.The model always sees the design. If it can't, the call is refused with
AI_VISION_UNAVAILABLEand nothing is charged.Tags when you improve copy. When you send
current_copywithinstructions, the AI doesn't top up the tag list on its own, so you may get fewer than 13 tags back.warnings[]doesn't say so; count them. To top up tags, sendfields: ["tags"], the tags you already chose incurrent_copy.tags, and an instruction such as "suggest 10 MORE, none repeating them".Tokens. Every call spends AI tokens, also when the returned title repeats yours. A low balance is refused with
AI_TOKENS_INSUFFICIENTbefore anything is spent. At most 2 AI calls run at once per account; a third answersRATE_LIMITEDwith the reasonai_concurrency.Scope. AI copy needs
catalog:write.
Inline AI copy on a create that drafts now (ai_copy, ai) also needs catalog:write. It writes from the artwork only: send current_copy to POST /ai/listing-copy instead.
2. Stage the batch
Send POST /etsy-drafts with submit: false. An idempotency key is required.
curl -X POST https://api.listingview.io/api/v1/etsy-drafts \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: stage-2026-10-26-batch-01" \ -H "Content-Type: application/json" \ -d '{ "shop_id": 38123456, "preset_id": 1667, "expected_preset_updated_at": "2026-10-14T18:22:05.123Z", "submit": false, "items": [ { "slots": { "A": 90501 }, "title": "Cottagecore Frog Tee, Mushroom Shirt", "description": "A soft garment-dyed tee with a hand-drawn frog.", "tags": ["frog shirt", "cottagecore tee", "mushroom shirt"], "client_item_id": "drop-0526-frog" }, { "slots": { "A": 90502 }, "title": "Moth Club Tee, Dark Academia Shirt", "description": "A soft garment-dyed tee with a vintage moth print.", "tags": ["moth shirt", "dark academia", "insect tee"], "client_item_id": "drop-0526-moth" } ] }'
{ "replayed": false, "shop_id": 38123456, "preset_id": 1667, "item_count": 2, "next_poll_after_ms": null, "next_step": "Staged: nothing was sent to Etsy. The batch waits in ListingView → Create listings → Saved listings (review_url) for the person to review and edit. …", "draft_id": "stg_48216", "revision": 1, "review_url": "https://app.listingview.io/dashboard/listing-creation?tab=saved", "items": [ { "index": 0, "client_item_id": "drop-0526-frog" }, { "index": 1, "client_item_id": "drop-0526-moth" } ] }
What staging checks. The shop, the preset, the slots, that every file is ready, the shop settings, the print-on-demand rules and digital deliverables. Nothing reaches Etsy.
Checked at submit, not now. Your daily draft caps, a busy shop, and whether every preset photo can still be sent.
Batch size. A batch holds up to 100 items, and never more than your plan's per-job cap. A bigger batch is refused with
DATA_BUDGET_EXCEEDEDand the reasonetsy_drafts_job. Stage several batches instead.review_urlis a full link to the Saved listings tab in ListingView. Give it to the person who reviews the batch.Test keys answer
would_stageand save nothing.
What each item can carry
Items take the usual fields: title, description, tags, price, quantity, sku and client_item_id. They can also carry these. This example shows the first three on one item:
{ "slots": { "A": { "asset_id": 90411, "placements": [{ "target": { "product": 1, "print_area": "front" }, "placement": { "design_width_pct": 62.5, "design_height_pct": 41.2, "x_offset_pct": 50, "y_offset_pct": 12 } }] } }, "pod_selection": [{ "product": 1, "options": { "color": ["White", "Natural", "Ivory"] } }], "photos": { "order": [{ "mockup": 2 }, { "asset_id": 90412 }] }, "dimensions": { "weight": 6.2, "length": 10, "width": 8, "height": 1, "weight_unit": "oz", "dimensions_unit": "in" }, "title": "Luna Moth Tee, Cottagecore Shirt", "client_item_id": "drop-0526-moth" }
slotsfills each design slot: an asset id, as in{"A": 90211}, or an asset id with this listing's ownplacements. Placements use the same fields and ranges as a preset (see API: Presets).pod_selectionsells fewer colors or sizes on this one listing, for example colors a design's ink doesn't suit. It can only remove options, never add them. An item is refused if it leaves an enabled slot with no color (slot_without_color) or a product with no variant (no_variants_left).Removing a color needs
photos. Mockups of removed colors are not dropped for you. Ifpod_selectionremoves a color on a preset with mockups and the item sends nophotos, it is refused withcolor_prune_photos_requiredand the mockups as candidates. Drop those mockups withphotos.drop, or list the ones to show withphotos.order. Removing sizes never needs this.photossets the photo order on Etsy, lead photo first.ordertakes{"mockup": <index>}and{"asset_id": <id>}entries;droptakes{"mockup": <index>}. The mockup index is theindexthe preset'sinclude=mockupsread returns. Up to 20 photos.dimensionssets the packageweight,length,width,height,weight_unitanddimensions_unit, on print-on-demand and physical presets.digitalgives a digital item its own files. See Digital drafts below.Physical photos.
images[]stays for physical presets only. Videos always come from the preset.
A refused item names its path, such as items[7].slots.B.placements[0]. Every problem in the batch is listed at once.
Recover a lost stage response
If the answer never arrives, replay the request: send the same body with the same idempotency key (X-Idempotency-Key on REST, idempotency_key on MCP). While the batch is still open, you get the same draft_id back, even days later. The replay answers the batch as it is now, with its current revision. The same key with a different body is refused with CONFLICT and the reason idempotency_key_reused.
Once the batch is submitted or discarded, the key is free again. A replay then stages a new batch, or is refused with DUPLICATE_ITEM under the same 7-day rule as a direct create.
3. A person reviews it in ListingView
The batch appears in ListingView under Saved listings, as a card like any other saved draft: the number of listings and when it was saved. The person opens it, fixes what they want and saves. Their saved edits are what you submit.
Wait for the go-ahead. A submit sends the last version the person saved, not edits still open on their screen. Submit only after they say the review is done.
They can publish it themselves. Every card has a Publish to Etsy button. If the person uses it, your later submit tells you so (see 6. Submit).
4. Read the batch as it is now
GET /etsy-drafts/staged/{draft_id} returns the batch exactly as it is now, with every item in the same shape you stage with.
{ "draft_id": "stg_48213", "source": "api", "status": "staged", "submitted_by": null, "job_id": null, "shop_id": 38123456, "preset_id": 1667, "preset_missing": false, "revision": 3, "edited_in_app": true, "staging_key_active": true, "item_count": 2, "items": [ { "index": 0, "client_item_id": "drop-0412-ghost", "title": "Ghost Frog Tee, Spooky Cottagecore Shirt", "tags": ["frog shirt", "cottagecore tee"], "slots": { "A": 9001, "B": 9002 }, "pod_selection": [{ "product": 1, "options": { "color": ["Black", "Navy"] } }] }, { "index": 1, "client_item_id": null, "title": "Moth Club Tee", "tags": null, "slots": { "A": 9005, "B": 9006 }, "app_only_fields": ["videos"] } ], "app_only_fields": ["videos"] }
The answer is shortened here. In this example the person fixed a title, deleted a row and added one.
revisiongoes up with every saved change, by anyone. Send it back asexpected_revisionwhen you edit.client_item_idshows only when ListingView can prove which row it belongs to. A row the person added, or one it can't match after their edits, readsnull. Address those byindex.app_only_fields[]names any edit the API's item fields can't express. You get the names only, never the values. An id that is no longer in your account is listed underunresolvable[], never shown.status,submitted_byandjob_idsay whether the batch has been sent, and from where. A submitted batch readsitems: []: read its items on the job.preset_missing: truemeans the preset was deleted after you staged.
List your batches
GET /etsy-drafts/staged lists your open batches as {batches[], next_cursor}, filtered by shop_id or preset_id, with limit and cursor paging:
{ "source": "api", "draft_id": "stg_48213", "shop_id": 38123456, "preset_id": 1667, "revision": 3, "item_count": 12, "updated_at": "2026-10-26T15:02:11.000Z", "edited_in_app": true, "staging_key_active": true }
edited_in_app: truemeans a person saved changes in ListingView since your last write. Your own edits never set it.Drafts saved in the app are listed with
source: "app"for a key that holdscatalog:read. The API can read them but can't edit, submit or discard them: those calls answer403with the reasonapp_draft. A key that holds onlyetsy_drafts:writenever sees them.shop_idfilters API-staged batches only.
5. Update the batch: send every edit in one PATCH
Collect every change, then send them together in one PATCH /etsy-drafts/staged/{draft_id}. expected_revision and an idempotency key are required.
curl -X PATCH https://api.listingview.io/api/v1/etsy-drafts/staged/stg_48213 \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: drops-2026-10-27-batch-edit" \ -H "Content-Type: application/json" \ -d '{ "expected_revision": 3, "ops": [ { "op": "remove_items", "index": 5 }, { "op": "remove_items", "index": 2 }, { "op": "update_item", "client_item_id": "drop-0412-ghost", "item": { "slots": { "B": { "asset_id": 9010, "placements": [{ "target": { "product": 1, "print_area": "front" }, "placement": { "design_width_pct": 70, "x_offset_pct": 50 } }] } } } }, { "op": "add_items", "items": [{ "slots": { "A": 9011, "B": 9012 }, "title": "Toad Choir Tee", "tags": ["toad shirt"], "client_item_id": "drop-0412-toad" }] } ] }'
Operations.
add_items(withitems[]),remove_items,replace_itemandupdate_item(withitem), up to 100 per call. Address an item byclient_item_idwhere it has one, otherwise byindex. One op per item per call (item_addressed_twiceotherwise).Indexes don't shift. Every
indexin one PATCH means the batch as you read it atexpected_revision. Removing index 5 and index 2 in one call removes the rows that were 5 and 2.update_itemkeeps what you don't send, including the person's in-app edits.slotsmerge letter by letter. An item'sdigitalreplaces that item's files.App-only edits are protected. A change that would rebuild something the person set in the app that the API can't express is refused with
app_only_fields, naming it.All or nothing. Every operation applies, or none do. The answer is the batch as it is now, with the new
revision. A change that would leave no items is refused (batch_empty).One PATCH, not many. Your edits and discards share one rate limit with the app's own saves for this account: 30 writes a minute. Past it you get
RATE_LIMITEDwith the reasondraft_writes. Small, frequent PATCHes run into it, and so does the person editing in the app.Someone else saved first. A stale
expected_revisionis refused withCONFLICTand the reasondraft_changed. Re-read, re-apply your edits and retry. After 3 conflicts in a row, stop and tell the person.The preset was deleted. Any op other than
remove_itemsanswersCONFLICTwith the reasonpreset_missing.Checked again later. Every check runs again in full at submit.
Test keys answer
would_edit.
6. Submit
POST /etsy-drafts/staged/{draft_id}/submit sends the batch as it is now, with every edit the person saved. It needs a new idempotency key: the key you staged with is refused with idempotency_key_reused.
curl -X POST https://api.listingview.io/api/v1/etsy-drafts/staged/stg_48213/submit \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: drops-2026-10-27-submit-2" \ -H "Content-Type: application/json" \ -d '{ "expected_revision": 4, "drop_ineligible": true }'
{ "job_id": "edj_48213", "status": "pending", "replayed": false, "item_count": 1, "next_poll_after_ms": 2000, "draft_id": "stg_48213", "edited_in_app": false, "dropped": [{ "index": 1, "client_item_id": "drop-0526-moth", "reason": "asset_not_ready", "message": "It uses assets that aren't ready yet (90812). Wait until list_assets shows them ready." }] }
The answer is shortened here. Poll the job_id like any Etsy-draft job (step 7).
Every check runs again on the batch as it is now. An edit made in the app can't slip a file that isn't ready, or another shop's setting, past it.
Photos. A preset or item photo must be one of your own ready library files, or a public
https://image. A photo ListingView can't check or send is refused withphoto_reselect_required: upload or import it again and pick it again.expected_revisionis optional. Leave it out to accept whatever the person saved. Send it to refuse the submit if anything changed since your read (draft_changed).The preset changed. If the preset was edited after you staged, the submit answers
preset_changed. Read the batch again, then sendaccept_preset_changes: trueto submit it against the preset as it is now. A deleted preset answerspreset_missing.Items that fail. By default a failing item refuses the whole batch with
items_ineligible, anddetails.itemsnames each item with its reasons. Withdrop_ineligible: true, failing items are left out and listed indropped[], and the rest go to Etsy. Dropped items also show as failed on the job.A refused submit changes nothing. The batch stays staged exactly as it was.
Limits count on the day you submit. A daily-cap refusal leaves the batch staged, with
retry_afterat the next midnight UTC. A busy shop answersSHOP_BUSY, which is retryable.Sending it twice is safe. The same idempotency key returns the same job. A batch this account's API already submitted answers
replayed: truewith the same job.Test keys answer
would_submit.
Who submitted
If the person published the batch from the app, your submit answers CONFLICT with the reason draft_already_submitted. details.job_id names the job, and details.submitted_by is "app". The batch read shows status: "submitted" and submitted_by. Poll that job, or tell the person it was published from the app.
If staging_key_active is false, the key that staged the batch was revoked or has expired. Don't submit that batch without the person's go-ahead.
7. Poll the job, then find it in the history
GET /etsy-drafts/jobs/{job_id} reports the job and each item. Wait next_poll_after_ms between calls.
{ "job_id": "edj_48214", "status": "running", "counts": { "total": 3, "queued": 1, "processing": 0, "drafted": 1, "failed": 1, "skipped": 0, "cancelled": 0, "unknown_outcome": 0 }, "next_poll_after_ms": 5000, "items": [ { "index": 1, "client_item_id": "drop-0413-b", "status": "failed", "retry_safe": true, "etsy_listing_id": null, "error": { "source": "etsy", "code": "ITEM_FAILED", "message": "price → Price must be greater than or equal to 0.2", "field": "price", "reason": null, "details": null } } ] }
The answer is shortened here.
Per item you get its
status, its Etsy listing id and editor link once it is a draft, andpushableonce it is ready to publish. A drafted item can readpushable: falsewithpushable_reason: "catalog_sync_pending"for a short while.Failed items carry Etsy's own wording and
retry_safe. Items markedretry_safe: truecan go in a new job.unknown_outcomemeans the item may or may not exist on Etsy, for example when Etsy didn't answer the create in time. ListingView never sends a create twice on its own. Check the shop first, and never resubmit it blindly.What was applied. Each item also echoes what it applied, so you can check it against what you sent:
applied_placements,pruned_optionsandphoto_order. For a batch the person edited in the app, onlypruned_optionsis echoed.indexis the item's position in the batch as it was submitted.
GET /etsy-drafts/jobs lists your Etsy-draft jobs, newest first, from the last 30 days. Filter by shop_id, preset_id, status or since, and page with cursor.
{ "job_id": "edj_48213", "shop_id": 38123456, "preset_id": 1667, "status": "completed", "created_at": "2026-10-25T14:02:11.000Z", "finished_at": "2026-10-25T14:09:47.000Z", "items_total": 12, "items_drafted": 12, "items_failed": 0, "source": "api", "key_name": "drops-bot" }
Window. A
sinceolder than 30 days is refused withtoo_old, plusdetails.max_lookback_daysandearliest_since. A futuresinceis refused within_future.Dates.
created_atandsinceare when the job, or its staged batch, was created, not when it was submitted.App jobs. A key that holds
catalog:readalso sees bulk creates started in the app (source: "app", nokey_name), including a staged batch the person published from the app. A key that holds onlyetsy_drafts:writesees API jobs only.Not listed. Jobs still preparing, or that ended before submit. Read those by
job_id.With no jobs, you get
{"jobs": [], "next_cursor": null}.
Digital drafts
A digital preset makes digital Etsy drafts, staged or not. Each item can bring its own files, because a digital listing usually delivers its own file:
{ "title": "2027 Weekly Planner Printable, Minimalist PDF", "tags": ["weekly planner", "printable planner", "2027 planner"], "digital": { "deliverables": [ { "kind": "file", "asset_id": 88201 }, { "kind": "collection", "collection_id": 4522 } ], "design_files": [{ "asset_id": 88202 }] }, "client_item_id": "planner-weekly-2027" }
An item's files replace the preset's. Send every file the listing delivers. Without
digital, the item delivers the preset's files.Files and collections only. A link deliverable, the item's own or one the preset carries, is refused with
link_deliverables_unsupported.At least one file. Every item needs at least one ready file or collection (
digital_files_required).Ready and scanned. Every file, and every file inside a collection, must be ready and scanned clean (
collection_file_not_ready). An empty collection is refused (collection_empty).Limits. A listing can deliver up to 20 deliverables, design files included (
too_many_deliverables), and up to 200 files through its collections (too_many_collection_files).Old presets. A preset that still carries legacy inline files answers
legacy_preset_files. Re-save its deliverables in ListingView.Where a refusal points. A file from the preset is named
preset.digital.deliverables[k]orpreset.digital.design_files[k]. On a staged submit, an item's own file is nameditems[i].digitalFiles[k]. A not-ready file at submit is named by its file only.
What the job reports
Each digital item shows deliverables (count and kinds), delivery (pdf, raw_fallback, failed, or null while it isn't settled yet) and delivered_files.
Drafted means delivered. A digital item counts as drafted only when Etsy confirmed every delivery file. Otherwise it is failed, with its etsy_listing_id and a reason:
delivery_upload_failed: the Etsy draft exists, but none of its files reached Etsy;delivery_incomplete: the Etsy draft exists, but only some of its files reached Etsy (delivered_filessays how many did).
{ "index": 1, "client_item_id": "planner-02", "status": "failed", "retry_safe": false, "etsy_listing_id": 4459812290, "error": { "source": "listingview", "code": "ITEM_FAILED", "message": "The Etsy draft was created, but only some of its download files reached Etsy, so a buyer would not receive everything. Delete this draft, then create it again.", "reason": "delivery_incomplete" }, "deliverables": { "count": 6, "kinds": ["file"] }, "delivery": "failed", "delivered_files": 5 }
These items are always retry_safe: false. Delete the draft (see Redraft below), then draft it again.
Discard a staged batch
DELETE /etsy-drafts/staged/{draft_id} discards a batch the API staged.
{ "draft_id": "stg_48213", "deleted": true, "next_step": "Discarded: the batch is gone from Saved listings and its staged-batch slot is free. Nothing was sent to Etsy; its designs stay in your asset library." }
Nothing on Etsy changes. Your uploaded files stay in your library.
Already submitted. A submitted batch answers
CONFLICTwith the reasondraft_already_submittedand itsjob_id. Poll that job instead.Retrying is safe. A retry with the same idempotency key answers
deleted: trueagain.Test keys answer
would_discard.
How long a staged batch stays
Until you act on it. A staged batch stays until it is submitted or discarded. It never expires, and there's no timer or reminder.
Up to 18 at a time. An account can have up to 18 API-staged batches open. They sit inside the account's 20 open saved drafts, which leaves room for drafts made in the app.
When the slots are full, staging answers
CONFLICTwith the reasondraft_slots_full.details.scopesays which ran out:api_staged(the 18) oropen_drafts(the 20, app and API together). Submit or discard a batch, then stage again.Check the count first.
GET /usageshowsstaged_batches:api_stagedandopen_drafts, each withused,limit,remainingandexhausted.Paused by ListingView. If ListingView pauses Etsy drafts, staging, editing, submitting and discarding answer
SERVICE_UNAVAILABLEwith the reasonetsy_drafts_disableduntil it resumes. Listing and reading still work, a same-key replay still answers, and no staged batch is cancelled. Digital drafts can be paused on their own: a digital preset then answersFEATURE_UNAVAILABLE, while print-on-demand and physical drafts carry on.Plan changes. Below the Plus plan, edit, submit and discard answer
PLAN_INSUFFICIENT.
When a key is revoked
Revoked by you. The batch stays. Any other key on the account with
etsy_drafts:writecan edit or submit it, and the list showsstaging_key_active: false.Revoked by ListingView support, for example after a leak. That key's staged batches are discarded. If one is still found, an edit or submit discards it then and answers
CONFLICTwith the reasonstaging_key_revoked.
Redraft: delete an Etsy draft, then draft again
When drafts went to Etsy wrong, for example with the wrong preset, delete them and draft them again. A Drafting Agent key can do the whole loop.
Look the drafts up by Etsy id
POST /catalog/listings/lookup checks up to 100 of your Etsy listings in one call. It's a read and needs catalog:read.
curl -X POST https://api.listingview.io/api/v1/catalog/listings/lookup \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "shop_id": 38123456, "etsy_listing_ids": [4012345678, 4012345679] }'
{ "shop_id": 38123456, "partial": false, "retry_after": null, "etsy_calls": 0, "counts": { "requested": 2, "found": 1, "not_found": 1 }, "results": [ { "etsy_listing_id": 4012345678, "status": "found", "catalog_listing_id": 91234, "shop_id": 38123456, "state": "draft", "partial": false }, { "etsy_listing_id": 4012345679, "status": "not_found", "catalog_listing_id": null, "shop_id": null, "state": null } ] }
Without
includeyou get ListingView's syncedstate, with no Etsy call. An id that isn't a synced listing of your shop answersnot_foundand costs nothing.include: ["live_state"]adds the state on Etsy right now, in one Etsy call for the whole list. See API: Publish & reprice forpod_matrixandlive_inventory.Live parts count against your Etsy-call budget. A part that can't be read is marked on that listing with
live_unavailable_reason, and the answer haspartial: true. It never fails the call.The Etsy connection is never renewed inside a lookup. While it is being renewed, live parts read
token_refresh_pending; retry afterretry_after.With one connected shop you can leave
shop_idout.
Delete each draft
DELETE /catalog/listings/{listing_id}?shop_id= deletes one Etsy draft. listing_id is the ListingView catalog id, or etsy: plus the Etsy listing id. An idempotency key is required.
curl -X DELETE "https://api.listingview.io/api/v1/catalog/listings/etsy:4012345679?shop_id=38123456&confirm_shared_delete=true" \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: $IDEMPOTENCY_KEY"
{ "deleted": true, "already_deleted": true, "listing_id": 91235, "etsy_listing_id": 4012345679, "title": "Witchy Vibes Crewneck", "deleted_at": "2026-10-26T15:05:40.019Z", "shared_confirmed": true, "deletes_remaining_today": 37, "audit_id": 88124 }
What goes. The Etsy draft, its listing in ListingView and its own Printify product.
Already gone. A draft that's already deleted answers
200withalready_deleted: true.The seller is told. Each delete puts a notice in ListingView, titled "Etsy draft deleted by your API key". It names the key, the draft and the shop, and ends: "If you didn't expect this, revoke the key in Settings → API Keys."
A daily limit. Deletes have their own daily limit per account, shown as
etsy_draft_deletesinGET /usageand asdeletes_remaining_todayin each answer. Past it you getDATA_BUDGET_EXCEEDEDwith the reasonetsy_draft_deletes_daily. Before a redraft, check this and your Etsy-draft caps in/usage, so a redraft never stops halfway.Test keys answer
would_delete.
What the delete checks
The delete runs these checks in order and stops at the first that fails:
Check | When it fails |
|---|---|
The listing is in your shop |
|
This account's Bulk Create made the draft, through the app or the API |
|
No other ListingView account shares the listing |
|
ListingView isn't pushing or publishing that listing right now |
|
The shop's Etsy connection is current |
|
Your shop connection allows deletes |
|
You have deletes left today |
|
Etsy still shows it as a draft, read live right before the delete |
|
Shared shops. Up to 8 ListingView accounts can connect the same Etsy shop. A listing is shared when another of them also manages it. Deleting the draft removes it for all of them. Their own Printify products are left alone. The API never sends
confirm_shared_deletefor you.After the checks. If Etsy itself refuses the delete, you get
CONFLICTwith the reasonetsy_not_deletable. If Etsy deleted the draft but ListingView's cleanup failed, you getSERVICE_UNAVAILABLEwith the reasoncleanup_pending. Retry it: the retry finishes the cleanup.
The race the API can't close
Etsy has no "delete only if still a draft" call. ListingView reads the live state at most 2 seconds before it deletes, and holds the same lock its API publishing uses, so it never races itself.
One gap remains. If a person activates that same draft on Etsy.com, or publishes it from the ListingView app, in that split second, the now-live listing is deleted. This risk is accepted. Run redrafts only on drafts nobody is publishing by hand at that moment.
Draft again
Stage or create the drafts again as above, with a new idempotency key.
Conflicts and retries
A conflict (
draft_changed,preset_changed): read again, re-apply, retry. After 3 conflicts in a row, stop and tell the person.A retryable error (
isRetryable: true): waitretry_after, with some jitter. Stop after 3 tries and tell the person.Not retryable: never retry. Fix the input, or ask the person.
No clear outcome (
unknown_outcome, orretry_safe: false): the draft may or may not exist on Etsy. Check it on Etsy, or read the job or the listing, before you do anything else. Never resubmit it blindly.
The full rules are in API: Getting started.
From the CLI or an AI app
CLI:
listingview etsy-drafts create --shop-id 38123456 --preset-id 1667 --items items.json --stagestages a batch. Running the same command again returns the same batch. The staged batch has its own commands:etsy-drafts staged list,get,update,submitanddelete.listingview etsy-drafts jobs status edj_48213 --watchfollows a job. An item with an unknown outcome makes a job command exit with code 9: check the shop, and never resubmit it. See API: Command-line tool.MCP:
create_etsy_draftstakesoperation:create(the default; drafts now),stage,updateorsubmit.updateandsubmitneeddraft_id;opsandexpected_revisiongo withupdate, andexpected_revision,drop_ineligibleandaccept_preset_changeswithsubmit. An idempotency key is required on every call.MCP:
get_bulk_job_statusreads jobs and batches.list: "staged"lists staged batches anddraft_idreads one.list: "jobs"lists the job history andjob_idreads one job.MCP:
delete_preset_or_draftdeletes one thing, bykind.kind: "staged_batch"withdraft_iddiscards a batch.kind: "etsy_draft"withlisting_idoretsy_listing_id(never both), plusshop_idandconfirm_shared_delete, deletes an Etsy draft.MCP:
get_listingwithetsy_listing_idsandincluderuns the lookup.enhance_listing_with_aitakescurrent_copyanduse_custom_instructions. 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: Presets: build the preset a batch drafts from
API: Uploads: get designs and deliverables into your library
API: Publish & reprice: publish drafts with a separate Publisher key
API: Limits & budgets: draft caps, delete limits and
/usageAPI: Connect AI apps: the tools above in Claude Code or Cursor
API: Command-line tool: the same calls as terminal commands
Bulk Create: Saved listings and Bulk Create in the app
Using the ListingView API, MCP server & CLI: the seller guide to keys, presets and Etsy drafts
With a Drafting Agent key, an agent turns a preset and your designs into Etsy drafts. Drafts are hidden from shoppers. The agent can send a batch straight to Etsy, or stage it first so a person can check it in ListingView.
This guide covers both, then the staged loop from start to finish:
Write the listing copy with AI.
Stage the batch. Nothing reaches Etsy.
A person reviews and edits it in ListingView, under Saved listings.
Read the batch as the person left it.
Update it in one call, if anything still needs to change.
Submit it. ListingView makes the Etsy drafts.
Poll the job, then find it later in the job history.
New to the API? Start with API: Getting started. To build the preset first, see API: Presets.
What you need
A Drafting Agent key. Creating, staging, editing, submitting and deleting need
etsy_drafts:write, which a Drafting Agent key holds. Staging writes nothing to Etsy, but it still needs that scope.Plus plan or higher to create, stage, edit, submit and delete, and to poll one job.
Reading after a downgrade. The staged-batch list, one staged batch and the job history stay readable below Plus for a key that holds
catalog:read. A key that holds onlyetsy_drafts:writereads them on Plus or higher.A working preset and ready designs. See API: Presets and API: Uploads.
A Drafting Agent key can never publish. Drafts go live only when someone publishes them. See API: Publish & reprice.
Create Etsy drafts now
POST /etsy-drafts drafts a batch straight to Etsy from one of your presets. Fill each of the preset's upload slots with a ready design's asset id. An idempotency key is required.
curl -X POST https://api.listingview.io/api/v1/etsy-drafts \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: drops-2026-10-16-ghost-tee" \ -H "Content-Type: application/json" \ -d '{ "shop_id": 38123456, "preset_id": 1667, "expected_preset_updated_at": "2026-10-14T18:22:05.123Z", "items": [{ "slots": { "A": 90211 }, "title": "Retro Ghost Tee, Spooky Season Shirt", "tags": ["halloween shirt", "ghost tee", "spooky season"], "client_item_id": "drop-0412-ghost" }] }'
{ "job_id": "edj_48213", "status": "pending", "replayed": false, "shop_id": 38123456, "preset_id": 1667, "item_count": 1, "next_poll_after_ms": 5000, "next_step": "Poll get_bulk_job_status with this job_id; drafts appear one by one. Wait next_poll_after_ms between calls." }
The whole job is admitted or refused. Your plan's per-job and per-shop daily draft caps apply.
GET /usageshows them.expected_preset_updated_atis the preset'supdated_atfrom your read. If the preset changed since, nothing is drafted (preset_changed).One file, two slots. The same asset may fill two slots, for example a light and a dark slot.
The same key returns the same job. Resending the identical request with the same key answers
replayed: trueand the samejob_id, for as long as the job exists. The same key with a different body is refused withidempotency_key_reused.The same item under a new key is refused. A
client_item_idyou sent to this shop in the last 7 days is refused with409 DUPLICATE_ITEMwhile it is still queued or processing, once it became a draft, or when its outcome is unknown.details.duplicates[]names each id and its earlier status. It can go again only if it failed withretry_safe: true, was cancelled before it ran, or was skipped by a job that ended.Test keys answer the plan and write nothing.
Poll the job as in step 7.
1. Write the copy first
Staging refuses inline AI copy with ai_requires_submit. Write the copy first with POST /ai/listing-copy, then stage the batch with the finished copy.
curl -X POST https://api.listingview.io/api/v1/ai/listing-copy \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "design_asset_ids": [88123], "current_copy": { "title": "Sunset Mountain Sweatshirt", "description": "Retro sunset crewneck in soft white ink.", "tags": ["sunset sweatshirt", "mountain shirt", "hiking gift"], "price": 34.5 }, "instructions": "Cozy autumn tone. Mention the night sky.", "use_custom_instructions": true }'
Improve or write fresh. With
current_copy, the AI improves that copy. Without it, the AI writes from the artwork alone.current_copyneeds a title, description or tags; a price alone is refused.House instructions.
use_custom_instructionsis on by default. It applies the seller's saved Custom Instructions in ListingView, in addition to yourinstructions.The answer has
title,tags,description,fields,saw_artwork,model,tokens_charged,applied: false,warnings[]andgrounding.status(grounded,candidates-only,skippedorunknown). Nothing is saved.The model always sees the design. If it can't, the call is refused with
AI_VISION_UNAVAILABLEand nothing is charged.Tags when you improve copy. When you send
current_copywithinstructions, the AI doesn't top up the tag list on its own, so you may get fewer than 13 tags back.warnings[]doesn't say so; count them. To top up tags, sendfields: ["tags"], the tags you already chose incurrent_copy.tags, and an instruction such as "suggest 10 MORE, none repeating them".Tokens. Every call spends AI tokens, also when the returned title repeats yours. A low balance is refused with
AI_TOKENS_INSUFFICIENTbefore anything is spent. At most 2 AI calls run at once per account; a third answersRATE_LIMITEDwith the reasonai_concurrency.Scope. AI copy needs
catalog:write.
Inline AI copy on a create that drafts now (ai_copy, ai) also needs catalog:write. It writes from the artwork only: send current_copy to POST /ai/listing-copy instead.
2. Stage the batch
Send POST /etsy-drafts with submit: false. An idempotency key is required.
curl -X POST https://api.listingview.io/api/v1/etsy-drafts \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: stage-2026-10-26-batch-01" \ -H "Content-Type: application/json" \ -d '{ "shop_id": 38123456, "preset_id": 1667, "expected_preset_updated_at": "2026-10-14T18:22:05.123Z", "submit": false, "items": [ { "slots": { "A": 90501 }, "title": "Cottagecore Frog Tee, Mushroom Shirt", "description": "A soft garment-dyed tee with a hand-drawn frog.", "tags": ["frog shirt", "cottagecore tee", "mushroom shirt"], "client_item_id": "drop-0526-frog" }, { "slots": { "A": 90502 }, "title": "Moth Club Tee, Dark Academia Shirt", "description": "A soft garment-dyed tee with a vintage moth print.", "tags": ["moth shirt", "dark academia", "insect tee"], "client_item_id": "drop-0526-moth" } ] }'
{ "replayed": false, "shop_id": 38123456, "preset_id": 1667, "item_count": 2, "next_poll_after_ms": null, "next_step": "Staged: nothing was sent to Etsy. The batch waits in ListingView → Create listings → Saved listings (review_url) for the person to review and edit. …", "draft_id": "stg_48216", "revision": 1, "review_url": "https://app.listingview.io/dashboard/listing-creation?tab=saved", "items": [ { "index": 0, "client_item_id": "drop-0526-frog" }, { "index": 1, "client_item_id": "drop-0526-moth" } ] }
What staging checks. The shop, the preset, the slots, that every file is ready, the shop settings, the print-on-demand rules and digital deliverables. Nothing reaches Etsy.
Checked at submit, not now. Your daily draft caps, a busy shop, and whether every preset photo can still be sent.
Batch size. A batch holds up to 100 items, and never more than your plan's per-job cap. A bigger batch is refused with
DATA_BUDGET_EXCEEDEDand the reasonetsy_drafts_job. Stage several batches instead.review_urlis a full link to the Saved listings tab in ListingView. Give it to the person who reviews the batch.Test keys answer
would_stageand save nothing.
What each item can carry
Items take the usual fields: title, description, tags, price, quantity, sku and client_item_id. They can also carry these. This example shows the first three on one item:
{ "slots": { "A": { "asset_id": 90411, "placements": [{ "target": { "product": 1, "print_area": "front" }, "placement": { "design_width_pct": 62.5, "design_height_pct": 41.2, "x_offset_pct": 50, "y_offset_pct": 12 } }] } }, "pod_selection": [{ "product": 1, "options": { "color": ["White", "Natural", "Ivory"] } }], "photos": { "order": [{ "mockup": 2 }, { "asset_id": 90412 }] }, "dimensions": { "weight": 6.2, "length": 10, "width": 8, "height": 1, "weight_unit": "oz", "dimensions_unit": "in" }, "title": "Luna Moth Tee, Cottagecore Shirt", "client_item_id": "drop-0526-moth" }
slotsfills each design slot: an asset id, as in{"A": 90211}, or an asset id with this listing's ownplacements. Placements use the same fields and ranges as a preset (see API: Presets).pod_selectionsells fewer colors or sizes on this one listing, for example colors a design's ink doesn't suit. It can only remove options, never add them. An item is refused if it leaves an enabled slot with no color (slot_without_color) or a product with no variant (no_variants_left).Removing a color needs
photos. Mockups of removed colors are not dropped for you. Ifpod_selectionremoves a color on a preset with mockups and the item sends nophotos, it is refused withcolor_prune_photos_requiredand the mockups as candidates. Drop those mockups withphotos.drop, or list the ones to show withphotos.order. Removing sizes never needs this.photossets the photo order on Etsy, lead photo first.ordertakes{"mockup": <index>}and{"asset_id": <id>}entries;droptakes{"mockup": <index>}. The mockup index is theindexthe preset'sinclude=mockupsread returns. Up to 20 photos.dimensionssets the packageweight,length,width,height,weight_unitanddimensions_unit, on print-on-demand and physical presets.digitalgives a digital item its own files. See Digital drafts below.Physical photos.
images[]stays for physical presets only. Videos always come from the preset.
A refused item names its path, such as items[7].slots.B.placements[0]. Every problem in the batch is listed at once.
Recover a lost stage response
If the answer never arrives, replay the request: send the same body with the same idempotency key (X-Idempotency-Key on REST, idempotency_key on MCP). While the batch is still open, you get the same draft_id back, even days later. The replay answers the batch as it is now, with its current revision. The same key with a different body is refused with CONFLICT and the reason idempotency_key_reused.
Once the batch is submitted or discarded, the key is free again. A replay then stages a new batch, or is refused with DUPLICATE_ITEM under the same 7-day rule as a direct create.
3. A person reviews it in ListingView
The batch appears in ListingView under Saved listings, as a card like any other saved draft: the number of listings and when it was saved. The person opens it, fixes what they want and saves. Their saved edits are what you submit.
Wait for the go-ahead. A submit sends the last version the person saved, not edits still open on their screen. Submit only after they say the review is done.
They can publish it themselves. Every card has a Publish to Etsy button. If the person uses it, your later submit tells you so (see 6. Submit).
4. Read the batch as it is now
GET /etsy-drafts/staged/{draft_id} returns the batch exactly as it is now, with every item in the same shape you stage with.
{ "draft_id": "stg_48213", "source": "api", "status": "staged", "submitted_by": null, "job_id": null, "shop_id": 38123456, "preset_id": 1667, "preset_missing": false, "revision": 3, "edited_in_app": true, "staging_key_active": true, "item_count": 2, "items": [ { "index": 0, "client_item_id": "drop-0412-ghost", "title": "Ghost Frog Tee, Spooky Cottagecore Shirt", "tags": ["frog shirt", "cottagecore tee"], "slots": { "A": 9001, "B": 9002 }, "pod_selection": [{ "product": 1, "options": { "color": ["Black", "Navy"] } }] }, { "index": 1, "client_item_id": null, "title": "Moth Club Tee", "tags": null, "slots": { "A": 9005, "B": 9006 }, "app_only_fields": ["videos"] } ], "app_only_fields": ["videos"] }
The answer is shortened here. In this example the person fixed a title, deleted a row and added one.
revisiongoes up with every saved change, by anyone. Send it back asexpected_revisionwhen you edit.client_item_idshows only when ListingView can prove which row it belongs to. A row the person added, or one it can't match after their edits, readsnull. Address those byindex.app_only_fields[]names any edit the API's item fields can't express. You get the names only, never the values. An id that is no longer in your account is listed underunresolvable[], never shown.status,submitted_byandjob_idsay whether the batch has been sent, and from where. A submitted batch readsitems: []: read its items on the job.preset_missing: truemeans the preset was deleted after you staged.
List your batches
GET /etsy-drafts/staged lists your open batches as {batches[], next_cursor}, filtered by shop_id or preset_id, with limit and cursor paging:
{ "source": "api", "draft_id": "stg_48213", "shop_id": 38123456, "preset_id": 1667, "revision": 3, "item_count": 12, "updated_at": "2026-10-26T15:02:11.000Z", "edited_in_app": true, "staging_key_active": true }
edited_in_app: truemeans a person saved changes in ListingView since your last write. Your own edits never set it.Drafts saved in the app are listed with
source: "app"for a key that holdscatalog:read. The API can read them but can't edit, submit or discard them: those calls answer403with the reasonapp_draft. A key that holds onlyetsy_drafts:writenever sees them.shop_idfilters API-staged batches only.
5. Update the batch: send every edit in one PATCH
Collect every change, then send them together in one PATCH /etsy-drafts/staged/{draft_id}. expected_revision and an idempotency key are required.
curl -X PATCH https://api.listingview.io/api/v1/etsy-drafts/staged/stg_48213 \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: drops-2026-10-27-batch-edit" \ -H "Content-Type: application/json" \ -d '{ "expected_revision": 3, "ops": [ { "op": "remove_items", "index": 5 }, { "op": "remove_items", "index": 2 }, { "op": "update_item", "client_item_id": "drop-0412-ghost", "item": { "slots": { "B": { "asset_id": 9010, "placements": [{ "target": { "product": 1, "print_area": "front" }, "placement": { "design_width_pct": 70, "x_offset_pct": 50 } }] } } } }, { "op": "add_items", "items": [{ "slots": { "A": 9011, "B": 9012 }, "title": "Toad Choir Tee", "tags": ["toad shirt"], "client_item_id": "drop-0412-toad" }] } ] }'
Operations.
add_items(withitems[]),remove_items,replace_itemandupdate_item(withitem), up to 100 per call. Address an item byclient_item_idwhere it has one, otherwise byindex. One op per item per call (item_addressed_twiceotherwise).Indexes don't shift. Every
indexin one PATCH means the batch as you read it atexpected_revision. Removing index 5 and index 2 in one call removes the rows that were 5 and 2.update_itemkeeps what you don't send, including the person's in-app edits.slotsmerge letter by letter. An item'sdigitalreplaces that item's files.App-only edits are protected. A change that would rebuild something the person set in the app that the API can't express is refused with
app_only_fields, naming it.All or nothing. Every operation applies, or none do. The answer is the batch as it is now, with the new
revision. A change that would leave no items is refused (batch_empty).One PATCH, not many. Your edits and discards share one rate limit with the app's own saves for this account: 30 writes a minute. Past it you get
RATE_LIMITEDwith the reasondraft_writes. Small, frequent PATCHes run into it, and so does the person editing in the app.Someone else saved first. A stale
expected_revisionis refused withCONFLICTand the reasondraft_changed. Re-read, re-apply your edits and retry. After 3 conflicts in a row, stop and tell the person.The preset was deleted. Any op other than
remove_itemsanswersCONFLICTwith the reasonpreset_missing.Checked again later. Every check runs again in full at submit.
Test keys answer
would_edit.
6. Submit
POST /etsy-drafts/staged/{draft_id}/submit sends the batch as it is now, with every edit the person saved. It needs a new idempotency key: the key you staged with is refused with idempotency_key_reused.
curl -X POST https://api.listingview.io/api/v1/etsy-drafts/staged/stg_48213/submit \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: drops-2026-10-27-submit-2" \ -H "Content-Type: application/json" \ -d '{ "expected_revision": 4, "drop_ineligible": true }'
{ "job_id": "edj_48213", "status": "pending", "replayed": false, "item_count": 1, "next_poll_after_ms": 2000, "draft_id": "stg_48213", "edited_in_app": false, "dropped": [{ "index": 1, "client_item_id": "drop-0526-moth", "reason": "asset_not_ready", "message": "It uses assets that aren't ready yet (90812). Wait until list_assets shows them ready." }] }
The answer is shortened here. Poll the job_id like any Etsy-draft job (step 7).
Every check runs again on the batch as it is now. An edit made in the app can't slip a file that isn't ready, or another shop's setting, past it.
Photos. A preset or item photo must be one of your own ready library files, or a public
https://image. A photo ListingView can't check or send is refused withphoto_reselect_required: upload or import it again and pick it again.expected_revisionis optional. Leave it out to accept whatever the person saved. Send it to refuse the submit if anything changed since your read (draft_changed).The preset changed. If the preset was edited after you staged, the submit answers
preset_changed. Read the batch again, then sendaccept_preset_changes: trueto submit it against the preset as it is now. A deleted preset answerspreset_missing.Items that fail. By default a failing item refuses the whole batch with
items_ineligible, anddetails.itemsnames each item with its reasons. Withdrop_ineligible: true, failing items are left out and listed indropped[], and the rest go to Etsy. Dropped items also show as failed on the job.A refused submit changes nothing. The batch stays staged exactly as it was.
Limits count on the day you submit. A daily-cap refusal leaves the batch staged, with
retry_afterat the next midnight UTC. A busy shop answersSHOP_BUSY, which is retryable.Sending it twice is safe. The same idempotency key returns the same job. A batch this account's API already submitted answers
replayed: truewith the same job.Test keys answer
would_submit.
Who submitted
If the person published the batch from the app, your submit answers CONFLICT with the reason draft_already_submitted. details.job_id names the job, and details.submitted_by is "app". The batch read shows status: "submitted" and submitted_by. Poll that job, or tell the person it was published from the app.
If staging_key_active is false, the key that staged the batch was revoked or has expired. Don't submit that batch without the person's go-ahead.
7. Poll the job, then find it in the history
GET /etsy-drafts/jobs/{job_id} reports the job and each item. Wait next_poll_after_ms between calls.
{ "job_id": "edj_48214", "status": "running", "counts": { "total": 3, "queued": 1, "processing": 0, "drafted": 1, "failed": 1, "skipped": 0, "cancelled": 0, "unknown_outcome": 0 }, "next_poll_after_ms": 5000, "items": [ { "index": 1, "client_item_id": "drop-0413-b", "status": "failed", "retry_safe": true, "etsy_listing_id": null, "error": { "source": "etsy", "code": "ITEM_FAILED", "message": "price → Price must be greater than or equal to 0.2", "field": "price", "reason": null, "details": null } } ] }
The answer is shortened here.
Per item you get its
status, its Etsy listing id and editor link once it is a draft, andpushableonce it is ready to publish. A drafted item can readpushable: falsewithpushable_reason: "catalog_sync_pending"for a short while.Failed items carry Etsy's own wording and
retry_safe. Items markedretry_safe: truecan go in a new job.unknown_outcomemeans the item may or may not exist on Etsy, for example when Etsy didn't answer the create in time. ListingView never sends a create twice on its own. Check the shop first, and never resubmit it blindly.What was applied. Each item also echoes what it applied, so you can check it against what you sent:
applied_placements,pruned_optionsandphoto_order. For a batch the person edited in the app, onlypruned_optionsis echoed.indexis the item's position in the batch as it was submitted.
GET /etsy-drafts/jobs lists your Etsy-draft jobs, newest first, from the last 30 days. Filter by shop_id, preset_id, status or since, and page with cursor.
{ "job_id": "edj_48213", "shop_id": 38123456, "preset_id": 1667, "status": "completed", "created_at": "2026-10-25T14:02:11.000Z", "finished_at": "2026-10-25T14:09:47.000Z", "items_total": 12, "items_drafted": 12, "items_failed": 0, "source": "api", "key_name": "drops-bot" }
Window. A
sinceolder than 30 days is refused withtoo_old, plusdetails.max_lookback_daysandearliest_since. A futuresinceis refused within_future.Dates.
created_atandsinceare when the job, or its staged batch, was created, not when it was submitted.App jobs. A key that holds
catalog:readalso sees bulk creates started in the app (source: "app", nokey_name), including a staged batch the person published from the app. A key that holds onlyetsy_drafts:writesees API jobs only.Not listed. Jobs still preparing, or that ended before submit. Read those by
job_id.With no jobs, you get
{"jobs": [], "next_cursor": null}.
Digital drafts
A digital preset makes digital Etsy drafts, staged or not. Each item can bring its own files, because a digital listing usually delivers its own file:
{ "title": "2027 Weekly Planner Printable, Minimalist PDF", "tags": ["weekly planner", "printable planner", "2027 planner"], "digital": { "deliverables": [ { "kind": "file", "asset_id": 88201 }, { "kind": "collection", "collection_id": 4522 } ], "design_files": [{ "asset_id": 88202 }] }, "client_item_id": "planner-weekly-2027" }
An item's files replace the preset's. Send every file the listing delivers. Without
digital, the item delivers the preset's files.Files and collections only. A link deliverable, the item's own or one the preset carries, is refused with
link_deliverables_unsupported.At least one file. Every item needs at least one ready file or collection (
digital_files_required).Ready and scanned. Every file, and every file inside a collection, must be ready and scanned clean (
collection_file_not_ready). An empty collection is refused (collection_empty).Limits. A listing can deliver up to 20 deliverables, design files included (
too_many_deliverables), and up to 200 files through its collections (too_many_collection_files).Old presets. A preset that still carries legacy inline files answers
legacy_preset_files. Re-save its deliverables in ListingView.Where a refusal points. A file from the preset is named
preset.digital.deliverables[k]orpreset.digital.design_files[k]. On a staged submit, an item's own file is nameditems[i].digitalFiles[k]. A not-ready file at submit is named by its file only.
What the job reports
Each digital item shows deliverables (count and kinds), delivery (pdf, raw_fallback, failed, or null while it isn't settled yet) and delivered_files.
Drafted means delivered. A digital item counts as drafted only when Etsy confirmed every delivery file. Otherwise it is failed, with its etsy_listing_id and a reason:
delivery_upload_failed: the Etsy draft exists, but none of its files reached Etsy;delivery_incomplete: the Etsy draft exists, but only some of its files reached Etsy (delivered_filessays how many did).
{ "index": 1, "client_item_id": "planner-02", "status": "failed", "retry_safe": false, "etsy_listing_id": 4459812290, "error": { "source": "listingview", "code": "ITEM_FAILED", "message": "The Etsy draft was created, but only some of its download files reached Etsy, so a buyer would not receive everything. Delete this draft, then create it again.", "reason": "delivery_incomplete" }, "deliverables": { "count": 6, "kinds": ["file"] }, "delivery": "failed", "delivered_files": 5 }
These items are always retry_safe: false. Delete the draft (see Redraft below), then draft it again.
Discard a staged batch
DELETE /etsy-drafts/staged/{draft_id} discards a batch the API staged.
{ "draft_id": "stg_48213", "deleted": true, "next_step": "Discarded: the batch is gone from Saved listings and its staged-batch slot is free. Nothing was sent to Etsy; its designs stay in your asset library." }
Nothing on Etsy changes. Your uploaded files stay in your library.
Already submitted. A submitted batch answers
CONFLICTwith the reasondraft_already_submittedand itsjob_id. Poll that job instead.Retrying is safe. A retry with the same idempotency key answers
deleted: trueagain.Test keys answer
would_discard.
How long a staged batch stays
Until you act on it. A staged batch stays until it is submitted or discarded. It never expires, and there's no timer or reminder.
Up to 18 at a time. An account can have up to 18 API-staged batches open. They sit inside the account's 20 open saved drafts, which leaves room for drafts made in the app.
When the slots are full, staging answers
CONFLICTwith the reasondraft_slots_full.details.scopesays which ran out:api_staged(the 18) oropen_drafts(the 20, app and API together). Submit or discard a batch, then stage again.Check the count first.
GET /usageshowsstaged_batches:api_stagedandopen_drafts, each withused,limit,remainingandexhausted.Paused by ListingView. If ListingView pauses Etsy drafts, staging, editing, submitting and discarding answer
SERVICE_UNAVAILABLEwith the reasonetsy_drafts_disableduntil it resumes. Listing and reading still work, a same-key replay still answers, and no staged batch is cancelled. Digital drafts can be paused on their own: a digital preset then answersFEATURE_UNAVAILABLE, while print-on-demand and physical drafts carry on.Plan changes. Below the Plus plan, edit, submit and discard answer
PLAN_INSUFFICIENT.
When a key is revoked
Revoked by you. The batch stays. Any other key on the account with
etsy_drafts:writecan edit or submit it, and the list showsstaging_key_active: false.Revoked by ListingView support, for example after a leak. That key's staged batches are discarded. If one is still found, an edit or submit discards it then and answers
CONFLICTwith the reasonstaging_key_revoked.
Redraft: delete an Etsy draft, then draft again
When drafts went to Etsy wrong, for example with the wrong preset, delete them and draft them again. A Drafting Agent key can do the whole loop.
Look the drafts up by Etsy id
POST /catalog/listings/lookup checks up to 100 of your Etsy listings in one call. It's a read and needs catalog:read.
curl -X POST https://api.listingview.io/api/v1/catalog/listings/lookup \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "shop_id": 38123456, "etsy_listing_ids": [4012345678, 4012345679] }'
{ "shop_id": 38123456, "partial": false, "retry_after": null, "etsy_calls": 0, "counts": { "requested": 2, "found": 1, "not_found": 1 }, "results": [ { "etsy_listing_id": 4012345678, "status": "found", "catalog_listing_id": 91234, "shop_id": 38123456, "state": "draft", "partial": false }, { "etsy_listing_id": 4012345679, "status": "not_found", "catalog_listing_id": null, "shop_id": null, "state": null } ] }
Without
includeyou get ListingView's syncedstate, with no Etsy call. An id that isn't a synced listing of your shop answersnot_foundand costs nothing.include: ["live_state"]adds the state on Etsy right now, in one Etsy call for the whole list. See API: Publish & reprice forpod_matrixandlive_inventory.Live parts count against your Etsy-call budget. A part that can't be read is marked on that listing with
live_unavailable_reason, and the answer haspartial: true. It never fails the call.The Etsy connection is never renewed inside a lookup. While it is being renewed, live parts read
token_refresh_pending; retry afterretry_after.With one connected shop you can leave
shop_idout.
Delete each draft
DELETE /catalog/listings/{listing_id}?shop_id= deletes one Etsy draft. listing_id is the ListingView catalog id, or etsy: plus the Etsy listing id. An idempotency key is required.
curl -X DELETE "https://api.listingview.io/api/v1/catalog/listings/etsy:4012345679?shop_id=38123456&confirm_shared_delete=true" \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: $IDEMPOTENCY_KEY"
{ "deleted": true, "already_deleted": true, "listing_id": 91235, "etsy_listing_id": 4012345679, "title": "Witchy Vibes Crewneck", "deleted_at": "2026-10-26T15:05:40.019Z", "shared_confirmed": true, "deletes_remaining_today": 37, "audit_id": 88124 }
What goes. The Etsy draft, its listing in ListingView and its own Printify product.
Already gone. A draft that's already deleted answers
200withalready_deleted: true.The seller is told. Each delete puts a notice in ListingView, titled "Etsy draft deleted by your API key". It names the key, the draft and the shop, and ends: "If you didn't expect this, revoke the key in Settings → API Keys."
A daily limit. Deletes have their own daily limit per account, shown as
etsy_draft_deletesinGET /usageand asdeletes_remaining_todayin each answer. Past it you getDATA_BUDGET_EXCEEDEDwith the reasonetsy_draft_deletes_daily. Before a redraft, check this and your Etsy-draft caps in/usage, so a redraft never stops halfway.Test keys answer
would_delete.
What the delete checks
The delete runs these checks in order and stops at the first that fails:
Check | When it fails |
|---|---|
The listing is in your shop |
|
This account's Bulk Create made the draft, through the app or the API |
|
No other ListingView account shares the listing |
|
ListingView isn't pushing or publishing that listing right now |
|
The shop's Etsy connection is current |
|
Your shop connection allows deletes |
|
You have deletes left today |
|
Etsy still shows it as a draft, read live right before the delete |
|
Shared shops. Up to 8 ListingView accounts can connect the same Etsy shop. A listing is shared when another of them also manages it. Deleting the draft removes it for all of them. Their own Printify products are left alone. The API never sends
confirm_shared_deletefor you.After the checks. If Etsy itself refuses the delete, you get
CONFLICTwith the reasonetsy_not_deletable. If Etsy deleted the draft but ListingView's cleanup failed, you getSERVICE_UNAVAILABLEwith the reasoncleanup_pending. Retry it: the retry finishes the cleanup.
The race the API can't close
Etsy has no "delete only if still a draft" call. ListingView reads the live state at most 2 seconds before it deletes, and holds the same lock its API publishing uses, so it never races itself.
One gap remains. If a person activates that same draft on Etsy.com, or publishes it from the ListingView app, in that split second, the now-live listing is deleted. This risk is accepted. Run redrafts only on drafts nobody is publishing by hand at that moment.
Draft again
Stage or create the drafts again as above, with a new idempotency key.
Conflicts and retries
A conflict (
draft_changed,preset_changed): read again, re-apply, retry. After 3 conflicts in a row, stop and tell the person.A retryable error (
isRetryable: true): waitretry_after, with some jitter. Stop after 3 tries and tell the person.Not retryable: never retry. Fix the input, or ask the person.
No clear outcome (
unknown_outcome, orretry_safe: false): the draft may or may not exist on Etsy. Check it on Etsy, or read the job or the listing, before you do anything else. Never resubmit it blindly.
The full rules are in API: Getting started.
From the CLI or an AI app
CLI:
listingview etsy-drafts create --shop-id 38123456 --preset-id 1667 --items items.json --stagestages a batch. Running the same command again returns the same batch. The staged batch has its own commands:etsy-drafts staged list,get,update,submitanddelete.listingview etsy-drafts jobs status edj_48213 --watchfollows a job. An item with an unknown outcome makes a job command exit with code 9: check the shop, and never resubmit it. See API: Command-line tool.MCP:
create_etsy_draftstakesoperation:create(the default; drafts now),stage,updateorsubmit.updateandsubmitneeddraft_id;opsandexpected_revisiongo withupdate, andexpected_revision,drop_ineligibleandaccept_preset_changeswithsubmit. An idempotency key is required on every call.MCP:
get_bulk_job_statusreads jobs and batches.list: "staged"lists staged batches anddraft_idreads one.list: "jobs"lists the job history andjob_idreads one job.MCP:
delete_preset_or_draftdeletes one thing, bykind.kind: "staged_batch"withdraft_iddiscards a batch.kind: "etsy_draft"withlisting_idoretsy_listing_id(never both), plusshop_idandconfirm_shared_delete, deletes an Etsy draft.MCP:
get_listingwithetsy_listing_idsandincluderuns the lookup.enhance_listing_with_aitakescurrent_copyanduse_custom_instructions. 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: Presets: build the preset a batch drafts from
API: Uploads: get designs and deliverables into your library
API: Publish & reprice: publish drafts with a separate Publisher key
API: Limits & budgets: draft caps, delete limits and
/usageAPI: Connect AI apps: the tools above in Claude Code or Cursor
API: Command-line tool: the same calls as terminal commands
Bulk Create: Saved listings and Bulk Create in the app
Using the ListingView API, MCP server & CLI: the seller guide to keys, presets and Etsy drafts
With a Drafting Agent key, an agent turns a preset and your designs into Etsy drafts. Drafts are hidden from shoppers. The agent can send a batch straight to Etsy, or stage it first so a person can check it in ListingView.
This guide covers both, then the staged loop from start to finish:
Write the listing copy with AI.
Stage the batch. Nothing reaches Etsy.
A person reviews and edits it in ListingView, under Saved listings.
Read the batch as the person left it.
Update it in one call, if anything still needs to change.
Submit it. ListingView makes the Etsy drafts.
Poll the job, then find it later in the job history.
New to the API? Start with API: Getting started. To build the preset first, see API: Presets.
What you need
A Drafting Agent key. Creating, staging, editing, submitting and deleting need
etsy_drafts:write, which a Drafting Agent key holds. Staging writes nothing to Etsy, but it still needs that scope.Plus plan or higher to create, stage, edit, submit and delete, and to poll one job.
Reading after a downgrade. The staged-batch list, one staged batch and the job history stay readable below Plus for a key that holds
catalog:read. A key that holds onlyetsy_drafts:writereads them on Plus or higher.A working preset and ready designs. See API: Presets and API: Uploads.
A Drafting Agent key can never publish. Drafts go live only when someone publishes them. See API: Publish & reprice.
Create Etsy drafts now
POST /etsy-drafts drafts a batch straight to Etsy from one of your presets. Fill each of the preset's upload slots with a ready design's asset id. An idempotency key is required.
curl -X POST https://api.listingview.io/api/v1/etsy-drafts \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: drops-2026-10-16-ghost-tee" \ -H "Content-Type: application/json" \ -d '{ "shop_id": 38123456, "preset_id": 1667, "expected_preset_updated_at": "2026-10-14T18:22:05.123Z", "items": [{ "slots": { "A": 90211 }, "title": "Retro Ghost Tee, Spooky Season Shirt", "tags": ["halloween shirt", "ghost tee", "spooky season"], "client_item_id": "drop-0412-ghost" }] }'
{ "job_id": "edj_48213", "status": "pending", "replayed": false, "shop_id": 38123456, "preset_id": 1667, "item_count": 1, "next_poll_after_ms": 5000, "next_step": "Poll get_bulk_job_status with this job_id; drafts appear one by one. Wait next_poll_after_ms between calls." }
The whole job is admitted or refused. Your plan's per-job and per-shop daily draft caps apply.
GET /usageshows them.expected_preset_updated_atis the preset'supdated_atfrom your read. If the preset changed since, nothing is drafted (preset_changed).One file, two slots. The same asset may fill two slots, for example a light and a dark slot.
The same key returns the same job. Resending the identical request with the same key answers
replayed: trueand the samejob_id, for as long as the job exists. The same key with a different body is refused withidempotency_key_reused.The same item under a new key is refused. A
client_item_idyou sent to this shop in the last 7 days is refused with409 DUPLICATE_ITEMwhile it is still queued or processing, once it became a draft, or when its outcome is unknown.details.duplicates[]names each id and its earlier status. It can go again only if it failed withretry_safe: true, was cancelled before it ran, or was skipped by a job that ended.Test keys answer the plan and write nothing.
Poll the job as in step 7.
1. Write the copy first
Staging refuses inline AI copy with ai_requires_submit. Write the copy first with POST /ai/listing-copy, then stage the batch with the finished copy.
curl -X POST https://api.listingview.io/api/v1/ai/listing-copy \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "design_asset_ids": [88123], "current_copy": { "title": "Sunset Mountain Sweatshirt", "description": "Retro sunset crewneck in soft white ink.", "tags": ["sunset sweatshirt", "mountain shirt", "hiking gift"], "price": 34.5 }, "instructions": "Cozy autumn tone. Mention the night sky.", "use_custom_instructions": true }'
Improve or write fresh. With
current_copy, the AI improves that copy. Without it, the AI writes from the artwork alone.current_copyneeds a title, description or tags; a price alone is refused.House instructions.
use_custom_instructionsis on by default. It applies the seller's saved Custom Instructions in ListingView, in addition to yourinstructions.The answer has
title,tags,description,fields,saw_artwork,model,tokens_charged,applied: false,warnings[]andgrounding.status(grounded,candidates-only,skippedorunknown). Nothing is saved.The model always sees the design. If it can't, the call is refused with
AI_VISION_UNAVAILABLEand nothing is charged.Tags when you improve copy. When you send
current_copywithinstructions, the AI doesn't top up the tag list on its own, so you may get fewer than 13 tags back.warnings[]doesn't say so; count them. To top up tags, sendfields: ["tags"], the tags you already chose incurrent_copy.tags, and an instruction such as "suggest 10 MORE, none repeating them".Tokens. Every call spends AI tokens, also when the returned title repeats yours. A low balance is refused with
AI_TOKENS_INSUFFICIENTbefore anything is spent. At most 2 AI calls run at once per account; a third answersRATE_LIMITEDwith the reasonai_concurrency.Scope. AI copy needs
catalog:write.
Inline AI copy on a create that drafts now (ai_copy, ai) also needs catalog:write. It writes from the artwork only: send current_copy to POST /ai/listing-copy instead.
2. Stage the batch
Send POST /etsy-drafts with submit: false. An idempotency key is required.
curl -X POST https://api.listingview.io/api/v1/etsy-drafts \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: stage-2026-10-26-batch-01" \ -H "Content-Type: application/json" \ -d '{ "shop_id": 38123456, "preset_id": 1667, "expected_preset_updated_at": "2026-10-14T18:22:05.123Z", "submit": false, "items": [ { "slots": { "A": 90501 }, "title": "Cottagecore Frog Tee, Mushroom Shirt", "description": "A soft garment-dyed tee with a hand-drawn frog.", "tags": ["frog shirt", "cottagecore tee", "mushroom shirt"], "client_item_id": "drop-0526-frog" }, { "slots": { "A": 90502 }, "title": "Moth Club Tee, Dark Academia Shirt", "description": "A soft garment-dyed tee with a vintage moth print.", "tags": ["moth shirt", "dark academia", "insect tee"], "client_item_id": "drop-0526-moth" } ] }'
{ "replayed": false, "shop_id": 38123456, "preset_id": 1667, "item_count": 2, "next_poll_after_ms": null, "next_step": "Staged: nothing was sent to Etsy. The batch waits in ListingView → Create listings → Saved listings (review_url) for the person to review and edit. …", "draft_id": "stg_48216", "revision": 1, "review_url": "https://app.listingview.io/dashboard/listing-creation?tab=saved", "items": [ { "index": 0, "client_item_id": "drop-0526-frog" }, { "index": 1, "client_item_id": "drop-0526-moth" } ] }
What staging checks. The shop, the preset, the slots, that every file is ready, the shop settings, the print-on-demand rules and digital deliverables. Nothing reaches Etsy.
Checked at submit, not now. Your daily draft caps, a busy shop, and whether every preset photo can still be sent.
Batch size. A batch holds up to 100 items, and never more than your plan's per-job cap. A bigger batch is refused with
DATA_BUDGET_EXCEEDEDand the reasonetsy_drafts_job. Stage several batches instead.review_urlis a full link to the Saved listings tab in ListingView. Give it to the person who reviews the batch.Test keys answer
would_stageand save nothing.
What each item can carry
Items take the usual fields: title, description, tags, price, quantity, sku and client_item_id. They can also carry these. This example shows the first three on one item:
{ "slots": { "A": { "asset_id": 90411, "placements": [{ "target": { "product": 1, "print_area": "front" }, "placement": { "design_width_pct": 62.5, "design_height_pct": 41.2, "x_offset_pct": 50, "y_offset_pct": 12 } }] } }, "pod_selection": [{ "product": 1, "options": { "color": ["White", "Natural", "Ivory"] } }], "photos": { "order": [{ "mockup": 2 }, { "asset_id": 90412 }] }, "dimensions": { "weight": 6.2, "length": 10, "width": 8, "height": 1, "weight_unit": "oz", "dimensions_unit": "in" }, "title": "Luna Moth Tee, Cottagecore Shirt", "client_item_id": "drop-0526-moth" }
slotsfills each design slot: an asset id, as in{"A": 90211}, or an asset id with this listing's ownplacements. Placements use the same fields and ranges as a preset (see API: Presets).pod_selectionsells fewer colors or sizes on this one listing, for example colors a design's ink doesn't suit. It can only remove options, never add them. An item is refused if it leaves an enabled slot with no color (slot_without_color) or a product with no variant (no_variants_left).Removing a color needs
photos. Mockups of removed colors are not dropped for you. Ifpod_selectionremoves a color on a preset with mockups and the item sends nophotos, it is refused withcolor_prune_photos_requiredand the mockups as candidates. Drop those mockups withphotos.drop, or list the ones to show withphotos.order. Removing sizes never needs this.photossets the photo order on Etsy, lead photo first.ordertakes{"mockup": <index>}and{"asset_id": <id>}entries;droptakes{"mockup": <index>}. The mockup index is theindexthe preset'sinclude=mockupsread returns. Up to 20 photos.dimensionssets the packageweight,length,width,height,weight_unitanddimensions_unit, on print-on-demand and physical presets.digitalgives a digital item its own files. See Digital drafts below.Physical photos.
images[]stays for physical presets only. Videos always come from the preset.
A refused item names its path, such as items[7].slots.B.placements[0]. Every problem in the batch is listed at once.
Recover a lost stage response
If the answer never arrives, replay the request: send the same body with the same idempotency key (X-Idempotency-Key on REST, idempotency_key on MCP). While the batch is still open, you get the same draft_id back, even days later. The replay answers the batch as it is now, with its current revision. The same key with a different body is refused with CONFLICT and the reason idempotency_key_reused.
Once the batch is submitted or discarded, the key is free again. A replay then stages a new batch, or is refused with DUPLICATE_ITEM under the same 7-day rule as a direct create.
3. A person reviews it in ListingView
The batch appears in ListingView under Saved listings, as a card like any other saved draft: the number of listings and when it was saved. The person opens it, fixes what they want and saves. Their saved edits are what you submit.
Wait for the go-ahead. A submit sends the last version the person saved, not edits still open on their screen. Submit only after they say the review is done.
They can publish it themselves. Every card has a Publish to Etsy button. If the person uses it, your later submit tells you so (see 6. Submit).
4. Read the batch as it is now
GET /etsy-drafts/staged/{draft_id} returns the batch exactly as it is now, with every item in the same shape you stage with.
{ "draft_id": "stg_48213", "source": "api", "status": "staged", "submitted_by": null, "job_id": null, "shop_id": 38123456, "preset_id": 1667, "preset_missing": false, "revision": 3, "edited_in_app": true, "staging_key_active": true, "item_count": 2, "items": [ { "index": 0, "client_item_id": "drop-0412-ghost", "title": "Ghost Frog Tee, Spooky Cottagecore Shirt", "tags": ["frog shirt", "cottagecore tee"], "slots": { "A": 9001, "B": 9002 }, "pod_selection": [{ "product": 1, "options": { "color": ["Black", "Navy"] } }] }, { "index": 1, "client_item_id": null, "title": "Moth Club Tee", "tags": null, "slots": { "A": 9005, "B": 9006 }, "app_only_fields": ["videos"] } ], "app_only_fields": ["videos"] }
The answer is shortened here. In this example the person fixed a title, deleted a row and added one.
revisiongoes up with every saved change, by anyone. Send it back asexpected_revisionwhen you edit.client_item_idshows only when ListingView can prove which row it belongs to. A row the person added, or one it can't match after their edits, readsnull. Address those byindex.app_only_fields[]names any edit the API's item fields can't express. You get the names only, never the values. An id that is no longer in your account is listed underunresolvable[], never shown.status,submitted_byandjob_idsay whether the batch has been sent, and from where. A submitted batch readsitems: []: read its items on the job.preset_missing: truemeans the preset was deleted after you staged.
List your batches
GET /etsy-drafts/staged lists your open batches as {batches[], next_cursor}, filtered by shop_id or preset_id, with limit and cursor paging:
{ "source": "api", "draft_id": "stg_48213", "shop_id": 38123456, "preset_id": 1667, "revision": 3, "item_count": 12, "updated_at": "2026-10-26T15:02:11.000Z", "edited_in_app": true, "staging_key_active": true }
edited_in_app: truemeans a person saved changes in ListingView since your last write. Your own edits never set it.Drafts saved in the app are listed with
source: "app"for a key that holdscatalog:read. The API can read them but can't edit, submit or discard them: those calls answer403with the reasonapp_draft. A key that holds onlyetsy_drafts:writenever sees them.shop_idfilters API-staged batches only.
5. Update the batch: send every edit in one PATCH
Collect every change, then send them together in one PATCH /etsy-drafts/staged/{draft_id}. expected_revision and an idempotency key are required.
curl -X PATCH https://api.listingview.io/api/v1/etsy-drafts/staged/stg_48213 \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: drops-2026-10-27-batch-edit" \ -H "Content-Type: application/json" \ -d '{ "expected_revision": 3, "ops": [ { "op": "remove_items", "index": 5 }, { "op": "remove_items", "index": 2 }, { "op": "update_item", "client_item_id": "drop-0412-ghost", "item": { "slots": { "B": { "asset_id": 9010, "placements": [{ "target": { "product": 1, "print_area": "front" }, "placement": { "design_width_pct": 70, "x_offset_pct": 50 } }] } } } }, { "op": "add_items", "items": [{ "slots": { "A": 9011, "B": 9012 }, "title": "Toad Choir Tee", "tags": ["toad shirt"], "client_item_id": "drop-0412-toad" }] } ] }'
Operations.
add_items(withitems[]),remove_items,replace_itemandupdate_item(withitem), up to 100 per call. Address an item byclient_item_idwhere it has one, otherwise byindex. One op per item per call (item_addressed_twiceotherwise).Indexes don't shift. Every
indexin one PATCH means the batch as you read it atexpected_revision. Removing index 5 and index 2 in one call removes the rows that were 5 and 2.update_itemkeeps what you don't send, including the person's in-app edits.slotsmerge letter by letter. An item'sdigitalreplaces that item's files.App-only edits are protected. A change that would rebuild something the person set in the app that the API can't express is refused with
app_only_fields, naming it.All or nothing. Every operation applies, or none do. The answer is the batch as it is now, with the new
revision. A change that would leave no items is refused (batch_empty).One PATCH, not many. Your edits and discards share one rate limit with the app's own saves for this account: 30 writes a minute. Past it you get
RATE_LIMITEDwith the reasondraft_writes. Small, frequent PATCHes run into it, and so does the person editing in the app.Someone else saved first. A stale
expected_revisionis refused withCONFLICTand the reasondraft_changed. Re-read, re-apply your edits and retry. After 3 conflicts in a row, stop and tell the person.The preset was deleted. Any op other than
remove_itemsanswersCONFLICTwith the reasonpreset_missing.Checked again later. Every check runs again in full at submit.
Test keys answer
would_edit.
6. Submit
POST /etsy-drafts/staged/{draft_id}/submit sends the batch as it is now, with every edit the person saved. It needs a new idempotency key: the key you staged with is refused with idempotency_key_reused.
curl -X POST https://api.listingview.io/api/v1/etsy-drafts/staged/stg_48213/submit \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: drops-2026-10-27-submit-2" \ -H "Content-Type: application/json" \ -d '{ "expected_revision": 4, "drop_ineligible": true }'
{ "job_id": "edj_48213", "status": "pending", "replayed": false, "item_count": 1, "next_poll_after_ms": 2000, "draft_id": "stg_48213", "edited_in_app": false, "dropped": [{ "index": 1, "client_item_id": "drop-0526-moth", "reason": "asset_not_ready", "message": "It uses assets that aren't ready yet (90812). Wait until list_assets shows them ready." }] }
The answer is shortened here. Poll the job_id like any Etsy-draft job (step 7).
Every check runs again on the batch as it is now. An edit made in the app can't slip a file that isn't ready, or another shop's setting, past it.
Photos. A preset or item photo must be one of your own ready library files, or a public
https://image. A photo ListingView can't check or send is refused withphoto_reselect_required: upload or import it again and pick it again.expected_revisionis optional. Leave it out to accept whatever the person saved. Send it to refuse the submit if anything changed since your read (draft_changed).The preset changed. If the preset was edited after you staged, the submit answers
preset_changed. Read the batch again, then sendaccept_preset_changes: trueto submit it against the preset as it is now. A deleted preset answerspreset_missing.Items that fail. By default a failing item refuses the whole batch with
items_ineligible, anddetails.itemsnames each item with its reasons. Withdrop_ineligible: true, failing items are left out and listed indropped[], and the rest go to Etsy. Dropped items also show as failed on the job.A refused submit changes nothing. The batch stays staged exactly as it was.
Limits count on the day you submit. A daily-cap refusal leaves the batch staged, with
retry_afterat the next midnight UTC. A busy shop answersSHOP_BUSY, which is retryable.Sending it twice is safe. The same idempotency key returns the same job. A batch this account's API already submitted answers
replayed: truewith the same job.Test keys answer
would_submit.
Who submitted
If the person published the batch from the app, your submit answers CONFLICT with the reason draft_already_submitted. details.job_id names the job, and details.submitted_by is "app". The batch read shows status: "submitted" and submitted_by. Poll that job, or tell the person it was published from the app.
If staging_key_active is false, the key that staged the batch was revoked or has expired. Don't submit that batch without the person's go-ahead.
7. Poll the job, then find it in the history
GET /etsy-drafts/jobs/{job_id} reports the job and each item. Wait next_poll_after_ms between calls.
{ "job_id": "edj_48214", "status": "running", "counts": { "total": 3, "queued": 1, "processing": 0, "drafted": 1, "failed": 1, "skipped": 0, "cancelled": 0, "unknown_outcome": 0 }, "next_poll_after_ms": 5000, "items": [ { "index": 1, "client_item_id": "drop-0413-b", "status": "failed", "retry_safe": true, "etsy_listing_id": null, "error": { "source": "etsy", "code": "ITEM_FAILED", "message": "price → Price must be greater than or equal to 0.2", "field": "price", "reason": null, "details": null } } ] }
The answer is shortened here.
Per item you get its
status, its Etsy listing id and editor link once it is a draft, andpushableonce it is ready to publish. A drafted item can readpushable: falsewithpushable_reason: "catalog_sync_pending"for a short while.Failed items carry Etsy's own wording and
retry_safe. Items markedretry_safe: truecan go in a new job.unknown_outcomemeans the item may or may not exist on Etsy, for example when Etsy didn't answer the create in time. ListingView never sends a create twice on its own. Check the shop first, and never resubmit it blindly.What was applied. Each item also echoes what it applied, so you can check it against what you sent:
applied_placements,pruned_optionsandphoto_order. For a batch the person edited in the app, onlypruned_optionsis echoed.indexis the item's position in the batch as it was submitted.
GET /etsy-drafts/jobs lists your Etsy-draft jobs, newest first, from the last 30 days. Filter by shop_id, preset_id, status or since, and page with cursor.
{ "job_id": "edj_48213", "shop_id": 38123456, "preset_id": 1667, "status": "completed", "created_at": "2026-10-25T14:02:11.000Z", "finished_at": "2026-10-25T14:09:47.000Z", "items_total": 12, "items_drafted": 12, "items_failed": 0, "source": "api", "key_name": "drops-bot" }
Window. A
sinceolder than 30 days is refused withtoo_old, plusdetails.max_lookback_daysandearliest_since. A futuresinceis refused within_future.Dates.
created_atandsinceare when the job, or its staged batch, was created, not when it was submitted.App jobs. A key that holds
catalog:readalso sees bulk creates started in the app (source: "app", nokey_name), including a staged batch the person published from the app. A key that holds onlyetsy_drafts:writesees API jobs only.Not listed. Jobs still preparing, or that ended before submit. Read those by
job_id.With no jobs, you get
{"jobs": [], "next_cursor": null}.
Digital drafts
A digital preset makes digital Etsy drafts, staged or not. Each item can bring its own files, because a digital listing usually delivers its own file:
{ "title": "2027 Weekly Planner Printable, Minimalist PDF", "tags": ["weekly planner", "printable planner", "2027 planner"], "digital": { "deliverables": [ { "kind": "file", "asset_id": 88201 }, { "kind": "collection", "collection_id": 4522 } ], "design_files": [{ "asset_id": 88202 }] }, "client_item_id": "planner-weekly-2027" }
An item's files replace the preset's. Send every file the listing delivers. Without
digital, the item delivers the preset's files.Files and collections only. A link deliverable, the item's own or one the preset carries, is refused with
link_deliverables_unsupported.At least one file. Every item needs at least one ready file or collection (
digital_files_required).Ready and scanned. Every file, and every file inside a collection, must be ready and scanned clean (
collection_file_not_ready). An empty collection is refused (collection_empty).Limits. A listing can deliver up to 20 deliverables, design files included (
too_many_deliverables), and up to 200 files through its collections (too_many_collection_files).Old presets. A preset that still carries legacy inline files answers
legacy_preset_files. Re-save its deliverables in ListingView.Where a refusal points. A file from the preset is named
preset.digital.deliverables[k]orpreset.digital.design_files[k]. On a staged submit, an item's own file is nameditems[i].digitalFiles[k]. A not-ready file at submit is named by its file only.
What the job reports
Each digital item shows deliverables (count and kinds), delivery (pdf, raw_fallback, failed, or null while it isn't settled yet) and delivered_files.
Drafted means delivered. A digital item counts as drafted only when Etsy confirmed every delivery file. Otherwise it is failed, with its etsy_listing_id and a reason:
delivery_upload_failed: the Etsy draft exists, but none of its files reached Etsy;delivery_incomplete: the Etsy draft exists, but only some of its files reached Etsy (delivered_filessays how many did).
{ "index": 1, "client_item_id": "planner-02", "status": "failed", "retry_safe": false, "etsy_listing_id": 4459812290, "error": { "source": "listingview", "code": "ITEM_FAILED", "message": "The Etsy draft was created, but only some of its download files reached Etsy, so a buyer would not receive everything. Delete this draft, then create it again.", "reason": "delivery_incomplete" }, "deliverables": { "count": 6, "kinds": ["file"] }, "delivery": "failed", "delivered_files": 5 }
These items are always retry_safe: false. Delete the draft (see Redraft below), then draft it again.
Discard a staged batch
DELETE /etsy-drafts/staged/{draft_id} discards a batch the API staged.
{ "draft_id": "stg_48213", "deleted": true, "next_step": "Discarded: the batch is gone from Saved listings and its staged-batch slot is free. Nothing was sent to Etsy; its designs stay in your asset library." }
Nothing on Etsy changes. Your uploaded files stay in your library.
Already submitted. A submitted batch answers
CONFLICTwith the reasondraft_already_submittedand itsjob_id. Poll that job instead.Retrying is safe. A retry with the same idempotency key answers
deleted: trueagain.Test keys answer
would_discard.
How long a staged batch stays
Until you act on it. A staged batch stays until it is submitted or discarded. It never expires, and there's no timer or reminder.
Up to 18 at a time. An account can have up to 18 API-staged batches open. They sit inside the account's 20 open saved drafts, which leaves room for drafts made in the app.
When the slots are full, staging answers
CONFLICTwith the reasondraft_slots_full.details.scopesays which ran out:api_staged(the 18) oropen_drafts(the 20, app and API together). Submit or discard a batch, then stage again.Check the count first.
GET /usageshowsstaged_batches:api_stagedandopen_drafts, each withused,limit,remainingandexhausted.Paused by ListingView. If ListingView pauses Etsy drafts, staging, editing, submitting and discarding answer
SERVICE_UNAVAILABLEwith the reasonetsy_drafts_disableduntil it resumes. Listing and reading still work, a same-key replay still answers, and no staged batch is cancelled. Digital drafts can be paused on their own: a digital preset then answersFEATURE_UNAVAILABLE, while print-on-demand and physical drafts carry on.Plan changes. Below the Plus plan, edit, submit and discard answer
PLAN_INSUFFICIENT.
When a key is revoked
Revoked by you. The batch stays. Any other key on the account with
etsy_drafts:writecan edit or submit it, and the list showsstaging_key_active: false.Revoked by ListingView support, for example after a leak. That key's staged batches are discarded. If one is still found, an edit or submit discards it then and answers
CONFLICTwith the reasonstaging_key_revoked.
Redraft: delete an Etsy draft, then draft again
When drafts went to Etsy wrong, for example with the wrong preset, delete them and draft them again. A Drafting Agent key can do the whole loop.
Look the drafts up by Etsy id
POST /catalog/listings/lookup checks up to 100 of your Etsy listings in one call. It's a read and needs catalog:read.
curl -X POST https://api.listingview.io/api/v1/catalog/listings/lookup \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "shop_id": 38123456, "etsy_listing_ids": [4012345678, 4012345679] }'
{ "shop_id": 38123456, "partial": false, "retry_after": null, "etsy_calls": 0, "counts": { "requested": 2, "found": 1, "not_found": 1 }, "results": [ { "etsy_listing_id": 4012345678, "status": "found", "catalog_listing_id": 91234, "shop_id": 38123456, "state": "draft", "partial": false }, { "etsy_listing_id": 4012345679, "status": "not_found", "catalog_listing_id": null, "shop_id": null, "state": null } ] }
Without
includeyou get ListingView's syncedstate, with no Etsy call. An id that isn't a synced listing of your shop answersnot_foundand costs nothing.include: ["live_state"]adds the state on Etsy right now, in one Etsy call for the whole list. See API: Publish & reprice forpod_matrixandlive_inventory.Live parts count against your Etsy-call budget. A part that can't be read is marked on that listing with
live_unavailable_reason, and the answer haspartial: true. It never fails the call.The Etsy connection is never renewed inside a lookup. While it is being renewed, live parts read
token_refresh_pending; retry afterretry_after.With one connected shop you can leave
shop_idout.
Delete each draft
DELETE /catalog/listings/{listing_id}?shop_id= deletes one Etsy draft. listing_id is the ListingView catalog id, or etsy: plus the Etsy listing id. An idempotency key is required.
curl -X DELETE "https://api.listingview.io/api/v1/catalog/listings/etsy:4012345679?shop_id=38123456&confirm_shared_delete=true" \ -H "Authorization: Bearer $LISTINGVIEW_API_KEY" \ -H "X-Idempotency-Key: $IDEMPOTENCY_KEY"
{ "deleted": true, "already_deleted": true, "listing_id": 91235, "etsy_listing_id": 4012345679, "title": "Witchy Vibes Crewneck", "deleted_at": "2026-10-26T15:05:40.019Z", "shared_confirmed": true, "deletes_remaining_today": 37, "audit_id": 88124 }
What goes. The Etsy draft, its listing in ListingView and its own Printify product.
Already gone. A draft that's already deleted answers
200withalready_deleted: true.The seller is told. Each delete puts a notice in ListingView, titled "Etsy draft deleted by your API key". It names the key, the draft and the shop, and ends: "If you didn't expect this, revoke the key in Settings → API Keys."
A daily limit. Deletes have their own daily limit per account, shown as
etsy_draft_deletesinGET /usageand asdeletes_remaining_todayin each answer. Past it you getDATA_BUDGET_EXCEEDEDwith the reasonetsy_draft_deletes_daily. Before a redraft, check this and your Etsy-draft caps in/usage, so a redraft never stops halfway.Test keys answer
would_delete.
What the delete checks
The delete runs these checks in order and stops at the first that fails:
Check | When it fails |
|---|---|
The listing is in your shop |
|
This account's Bulk Create made the draft, through the app or the API |
|
No other ListingView account shares the listing |
|
ListingView isn't pushing or publishing that listing right now |
|
The shop's Etsy connection is current |
|
Your shop connection allows deletes |
|
You have deletes left today |
|
Etsy still shows it as a draft, read live right before the delete |
|
Shared shops. Up to 8 ListingView accounts can connect the same Etsy shop. A listing is shared when another of them also manages it. Deleting the draft removes it for all of them. Their own Printify products are left alone. The API never sends
confirm_shared_deletefor you.After the checks. If Etsy itself refuses the delete, you get
CONFLICTwith the reasonetsy_not_deletable. If Etsy deleted the draft but ListingView's cleanup failed, you getSERVICE_UNAVAILABLEwith the reasoncleanup_pending. Retry it: the retry finishes the cleanup.
The race the API can't close
Etsy has no "delete only if still a draft" call. ListingView reads the live state at most 2 seconds before it deletes, and holds the same lock its API publishing uses, so it never races itself.
One gap remains. If a person activates that same draft on Etsy.com, or publishes it from the ListingView app, in that split second, the now-live listing is deleted. This risk is accepted. Run redrafts only on drafts nobody is publishing by hand at that moment.
Draft again
Stage or create the drafts again as above, with a new idempotency key.
Conflicts and retries
A conflict (
draft_changed,preset_changed): read again, re-apply, retry. After 3 conflicts in a row, stop and tell the person.A retryable error (
isRetryable: true): waitretry_after, with some jitter. Stop after 3 tries and tell the person.Not retryable: never retry. Fix the input, or ask the person.
No clear outcome (
unknown_outcome, orretry_safe: false): the draft may or may not exist on Etsy. Check it on Etsy, or read the job or the listing, before you do anything else. Never resubmit it blindly.
The full rules are in API: Getting started.
From the CLI or an AI app
CLI:
listingview etsy-drafts create --shop-id 38123456 --preset-id 1667 --items items.json --stagestages a batch. Running the same command again returns the same batch. The staged batch has its own commands:etsy-drafts staged list,get,update,submitanddelete.listingview etsy-drafts jobs status edj_48213 --watchfollows a job. An item with an unknown outcome makes a job command exit with code 9: check the shop, and never resubmit it. See API: Command-line tool.MCP:
create_etsy_draftstakesoperation:create(the default; drafts now),stage,updateorsubmit.updateandsubmitneeddraft_id;opsandexpected_revisiongo withupdate, andexpected_revision,drop_ineligibleandaccept_preset_changeswithsubmit. An idempotency key is required on every call.MCP:
get_bulk_job_statusreads jobs and batches.list: "staged"lists staged batches anddraft_idreads one.list: "jobs"lists the job history andjob_idreads one job.MCP:
delete_preset_or_draftdeletes one thing, bykind.kind: "staged_batch"withdraft_iddiscards a batch.kind: "etsy_draft"withlisting_idoretsy_listing_id(never both), plusshop_idandconfirm_shared_delete, deletes an Etsy draft.MCP:
get_listingwithetsy_listing_idsandincluderuns the lookup.enhance_listing_with_aitakescurrent_copyanduse_custom_instructions. 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: Presets: build the preset a batch drafts from
API: Uploads: get designs and deliverables into your library
API: Publish & reprice: publish drafts with a separate Publisher key
API: Limits & budgets: draft caps, delete limits and
/usageAPI: Connect AI apps: the tools above in Claude Code or Cursor
API: Command-line tool: the same calls as terminal commands
Bulk Create: Saved listings and Bulk Create 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