If you sell digital goods across multiple marketplaces, Carousell, Shopify, and Shopee, you already know the real cost isn't writing one good listing. It's writing the same listing four different ways, in four different formats, without making a mistake that gets a listing rejected or, worse, misleads a buyer.
Here's the workflow I landed on using Claude, including a fix for a genuinely broken file that Excel itself couldn't open, and a second bug that took three separate failed attempts to catch.
The starting point: one product, four destinations
I was listing a batch of digital game codes (Xbox keys, Steam keys, a Rockstar Games Launcher code) sourced from Eneba and resold on Carousell, cross-posted to Shopify, and also uploaded in bulk to Shopee. Each platform wants the content shaped differently:
- Carousell: a punchy title, an emoji-friendly description, a redemption walkthrough buyers can screenshot
- Shopify: HTML description, product category taxonomy, vendor/type metadata, inventory and shipping flags
- Shopee: a rigid mass-upload Excel template with dozens of columns, mandatory/conditional fields, and per-channel delivery toggles
Claude handled all three from one source of truth, and the tricky part wasn't the copywriting. It was getting the Shopee template to open at all, and then, later, getting an edit to actually stick. A related image-handling workflow note: see how we use ImgBB for temporary image hosting when moving product images between platforms during listing work like this.
The Excel file that Excel couldn't open
Shopee's mass upload template downloads as a normal-looking .xlsx, but mine had a corrupted worksheet view setting. Every tool I tried, openpyxl, pandas, markitdown, failed on the same error:
ValueError: Value must be one of {'bottomRight', 'topLeft', 'topRight', 'bottomLeft'}The cause: Shopee's own export had written activePane="bottom_left" (with an underscore) into three of the worksheet XML files, instead of the valid bottomLeft. That's an invalid enum value per the OOXML spec, and it's strict enough to block every standard parser, even though Excel itself sometimes tolerates it silently.
The fix was to treat the .xlsx as what it actually is: a zip archive of XML files.
unzip -o template.xlsx -d extracted/ grep -rl "activePane" extracted/xl/worksheets/ sed -i 's/activePane="bottom_left"/activePane="bottomLeft"/' extracted/xl/worksheets/sheet*.xml cd extracted && zip -r -X ../fixed.xlsx . -x ".*"
Once patched, the file loaded cleanly in openpyxl, and every downstream step became scriptable.
Reading the template's actual structure
Shopee's basic mass upload template isn't a simple header-and-rows sheet. It has:
-
Row 1: internal field keys (
ps_product_name,ps_price, etc.), not meant for humans - Row 3: the human-readable column headers
- Row 4: whether each field is Mandatory, Optional, or Conditional Mandatory
- Rows 5 to 6: inline guidance and validation rules for each column
- Row 7 onward: actual data
Getting this wrong (for example, writing data into row 1 instead of row 7) is an easy mistake if you're filling the template by hand under deadline pressure. Having Claude parse the header rows first, then write only into the data rows, removed that whole category of error.
Matching the product's category to the delivery model
Digital codes have to be flagged correctly in Shopee's channel columns, since Shopee separates shipping channels (Doorstep Delivery, Pick Lockers, Collection Points, SPX Express Lockers) from a dedicated Virtual Goods, delivery through app/emails channel. For a digital code:
- All physical shipping channels: Off
- Virtual Goods channel: On
- Weight and dimensions: still technically mandatory fields even though nothing ships, so a nominal placeholder (0.01kg, 1x1x1cm) satisfies the schema without implying real shipping
Miss this and Shopee may either reject the listing or (worse) calculate shipping fees for a product that will never touch a courier.
The second trap: a "successful" edit that never actually happened
Weeks later, listing a physical product (a universal aircon remote), I hit a subtler bug, one that's arguably worse because nothing throws an error. A mandatory field had a placeholder string in it ("FILL: Category ID"). I wrote a script to clear it before re-upload:
ws.cell(row=7, column=1, value=None) # looks right. isn't.
Shopee's validator rejected the file again, with the exact same error, referencing the exact same placeholder text. I regenerated the file. Rejected again. Renamed it to rule out a stale-copy mix-up. Rejected a third time, byte-for-byte identical failure.
The bug wasn't the upload, it was the fix. openpyxl's cell() method has value=None as its own default parameter value. Passing value=None explicitly is indistinguishable, to the method, from not passing a value at all, so it silently left the existing cell content untouched and returned the cell as if nothing had happened. Three consecutive "fixed" files, all still broken, all looking identical in the confirmation output, because the confirmation was reading the same untouched cell.
The actual fix is direct attribute assignment, which has no such ambiguity:
ws.cell(row=7, column=1).value = None # actually clears it # or equivalently: ws['A7'].value = None
The lesson generalizes past this one library: when a script reports success, verify the claim by reloading the saved artifact from disk and reading the value back, not by trusting the in-memory object you just wrote to. The in-memory object had also silently failed to update in this case, so even that check would have needed a full save-and-reload round trip to catch it.
Where Claude helped most
The genuinely time-consuming parts of this workflow weren't the copywriting, they were:
- Diagnosing a file format bug that three separate Python libraries failed on identically, then fixing it at the raw XML level instead of giving up and asking for a re-export
- Reverse-engineering an undocumented template structure (which rows are metadata vs. data) before writing anything, to avoid corrupting the file
- Catching a silent no-op in its own edit, rather than trusting a script's apparent success after the third identical failure
- Keeping platform-specific compliance rules straight (Shopee's channel toggles, Shopify's taxonomy categories, character limits for titles and descriptions) across three very different target formats, from a single product description
If you're doing something similar, the pattern worth stealing isn't "ask an AI to write a product description." It's giving the AI the actual broken file, the actual template, and the actual platform's rules, and having it work at the file-format level when the friendly tools fail, and re-verify from disk when they claim to have succeeded.
A caveat worth stating plainly
None of this replaces checking your own store's category IDs, choosing your own price points, or verifying a redemption flow actually works before a buyer complains. Automating the mechanical, repetitive part of cross-posting frees up the time to get those judgment calls right, it doesn't replace making them. A template field marked "Optional" in Shopee's own guidance row also isn't a guarantee its backend agrees, that still needs a real upload attempt to confirm, not just the label.
Saved webpages can introduce a similar file-format trap: an MHT parser that guesses the wrong character encoding may create Unicode errors that were never in the original page. See our safe MHT analysis guide for Claude and Codex before treating replacement characters as evidence of damaged listing content.
Selling digital codes across Carousell, Shopify, and Shopee? The same workflow (Carousell export parsing, Shopify GraphQL product creation, and Shopee template automation) generalizes to any digital or physical product catalog you're maintaining across multiple marketplaces.