DEV Community

Cover image for One Wrong CSV Header and You're Rejected: Building Exact Adobe, Vecteezy, and Dreamstime Uploads for 39 Products**

One Wrong CSV Header and You're Rejected: Building Exact Adobe, Vecteezy, and Dreamstime Uploads for 39 Products**

First sale on Adobe paid $0.99. That part is real and I keep it framed on my monitor. What I do not frame is the rejection two weeks before it: I uploaded a batch of 140 files and every one came back rejected in under a minute. Not because the art was bad. Because the first line of my CSV read filename,title,keywords and Adobe wanted a header with five exact columns. The whole batch died on a comma.

I had spent the weekend drawing. I lost it in four seconds to a header row.

Wrong header, instant reject

All three channels check the CSV before they look at your art.

Adobe Stock wants 5 columns. The order matters, the names matter. Vecteezy wants 4. Dreamstime wants 15 and it will not forgive a missing one.

I built make_metadata.py for exactly this. One command, one platform, the header that platform demands:

python make_metadata.py ./my-assets --platform adobe      --out metadata.csv   --ext .jpg
python make_metadata.py ./my-assets --platform vecteezy   --out vecteezy.csv   --ext .jpg
python make_metadata.py ./my-assets --platform dreamstime --out dreamstime.csv --ext .jpg
Enter fullscreen mode Exit fullscreen mode

It reads the folder, lists each file, writes the row, writes the header. Nothing clever. The point is that it never guesses. The used it on 1,700 assets. It uses the standard library alone, no install.

One honest caveat: without a titles file it title-cases the filename. That is fine for a first pass, and useless for submission. Pass --titles titles.json with real titles and keywords before you upload, or you will get rejected for weak metadata next.

A re-run should not double-post

The fastest way to get an account flagged is running the same batch twice. I did that once on a smaller channel. Uploaded 60 files, forgot, uploaded them again an hour later. Duplicate notices on my account for a week.

upload_tracker.py is a plain ledger. It remembers what you already posted.

python upload_tracker.py pending items.txt   # shows what is NOT done yet
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Enter fullscreen mode Exit fullscreen mode

No database. A text file. On the tiny server that ran this whole pipeline, that was the right call. You ask for pending, you get the list you have not touched. No memory, no second run.

The server was 1 vCPU and 2 GB of RAM

No GPU. That is where this ran, all 39 products.

A few habits made it work and none of them are clever:

  • One process at a time. Never fan out image jobs on one core.
  • gc.collect() after each asset.
  • Measure peak RSS, not average, with resource.getrusage(RUSAGE_SELF).ru_maxrss.
  • One file per subprocess for heavy render steps, so memory returns to the OS.
  • Render at the resolution you need. Inspecting a preview at 60 dpi beats rendering it at 300.

I learned the last one after a render job hit swap and killed the box. It was rendering previews nobody looked at.

Covers and packs sell, bare files do not

A zip with a real cover sells. A bare folder does not. pack_product.py zips the files and renders a 1280×720 cover plus a 600×600 thumbnail in one command:

python pack_product.py my-product "My Product" "A short subtitle" 9
Enter fullscreen mode Exit fullscreen mode

Two third-party packages in the whole kit: Pillow for the covers, reportlab for PDF output. make_metadata.py and upload_tracker.py need no install at all.

I also keep a fixed folder layout, because a catalogue turns into a junk drawer without one:

assets/    raw output
output/    ready-to-submit files + metadata.csv
packs/     finished zips + covers
research/  notes and sources
scripts/   the tools
Enter fullscreen mode Exit fullscreen mode

Keep research/ forever. I deleted a scrape log once to clean up. It was the raw material for three later products. I rebuilt it by hand.

What came out of this

The pipeline packaged 39 products, 856 MB of output. Behind it sit about 19,000 lines of pipeline code and 300+ pages of research on the rules the scripts enforce. Of 1,700 assets made, 58 are live on Adobe. First sale paid $0.99. That is the real number, not a highlight reel.

The four scripts are the packaging layer. md2pdf.py turns a Markdown doc into a clean PDF in one command, which is how the written products got made:

python md2pdf.py content.md product.pdf "My Product Title"
Enter fullscreen mode Exit fullscreen mode

None of this needed a framework or a cloud account. It needed the correct header, a ledger that stops double-posting, and a box that did not fall over.

If you are building the same thing, the whole system is in the Digital Asset Factory: the four scripts, the folder layout, and the rules they enforce, built from this exact work. No hard sell. Read it, or take the free header logic and write your own.

Get digital asset factory


Building digital products? Free guides & kits for digital product sellers.

Top comments (0)