DEV Community

Cover image for Why Wrong CSV Headers Get Your Vector Uploads Rejected, And The Script That Fixes All Three Platforms

Why Wrong CSV Headers Get Your Vector Uploads Rejected, And The Script That Fixes All Three Platforms

Last March I lost a whole afternoon to a header row.

I had 60 vectors ready for Adobe Stock. I built the CSV by hand, copied the column names from a blog post, uploaded, and got the rejection email within minutes. The problem was not the art. The problem was one column name that did not match exactly what Adobe's parser expects. Their uploader does not warn you, does not guess, does not partially import. A wrong header row means the whole batch bounces, and you find out only after the upload finishes.

That day I stopped hand-building CSVs and wrote a script instead. Here is what I learned about why the rejections happen, and how the fix works.

The three platforms do not agree on anything

Adobe Stock wants a 5-column CSV. Vecteezy wants 4 columns. Dreamstime wants 15. Same files, three different schemas, and none of them publish their templates in an easy-to-find place.

The columns are not just different in count. The names differ too, the required fields differ, and the order matters. Adobe's parser is strict about the header row matching its expected schema. Vecteezy is the same. Dreamstime, with 15 columns, has the most room for a silent mistake: fill one optional column wrong and the upload still goes through, but the metadata is dead on arrival and your file sits unreviewable.

The failure mode is always the same. The platform reads the header row first. If it does not match, it stops. It does not try to salvage the rest of the file. So one typo in column 3 costs you the entire batch, and on a slow connection that is a real cost in time.

Why hand-editing fails at scale

At 10 files you can eyeball a spreadsheet. At 60 files across 3 platforms you are maintaining 3 hand-edited CSVs that drift apart. I changed a title in the Adobe sheet and forgot to mirror it in the Vecteezy sheet. Two platforms, same asset, different titles. That is not just sloppy, it looks automated in a bad way to a reviewer.

The fix is boring and it works: one source of truth, three renderers. I keep a folder of files with a JSON file of titles and keywords. A script reads the folder and writes each platform's CSV with the exact header that platform demands, and nothing else. No extra columns, no "helpful" additions, no BOM weirdness that some parsers choke on.

The command looks like this:

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
Enter fullscreen mode Exit fullscreen mode

Same folder, three outputs, three correct headers. If a title is missing from the JSON, the script title-cases the filename as a placeholder. That is fine for a first pass, but I replace placeholder titles before submitting, because generic filenames read as low effort to reviewers.

The second rejection reason nobody warns you about

Wrong headers get you rejected once. Double-posting gets your account flagged.

My first batch script had no memory. I fixed a bug, re-ran it, and posted 40 of the same files twice. Duplicates across submissions are one of the fastest ways to draw a reviewer's attention, and once someone is looking at your account, every future upload gets more scrutiny.

So the metadata script got a companion: a plain ledger. Before posting anything I run:

python upload_tracker.py pending items.txt
Enter fullscreen mode Exit fullscreen mode

It shows only what has not been done yet. After each successful upload I mark it done. A re-run now skips everything already posted. It is a text file and a few dozen lines of standard library Python, and it has caught my own mistakes more times than I want to admit.

The rest of the pipeline runs on almost nothing

None of this needs a real machine. The whole thing, metadata generation, tracking, PDF rendering, packaging, ran on a 1 vCPU server with 2 GB RAM and no GPU. The habits that make that possible:

One process at a time. Never fan out image jobs in parallel on a single core, it just thrashes. Call gc.collect() after each asset to free memory. Measure peak RSS with resource.getrusage(RUSAGE_SELF).ru_maxrss, not average usage, because the peak is what kills a 2 GB box. For heavy render steps, run one file per subprocess so memory returns to the OS. And render at the resolution you actually need: inspecting a preview at 60 dpi instead of 300 saves an enormous amount of RAM.

The kit is deliberately small on dependencies. The metadata script and the tracker use only the Python standard library, no pip install at all. The PDF tool needs reportlab, the packaging tool needs Pillow. Two packages total.

What this bought me

Since building this, the same prompt engine and pipeline turned out 39 finished products, about 856 MB of output, with roughly 19,000 lines of pipeline code behind it and 300+ pages of research feeding the rules the scripts enforce. The metadata part is the least glamorous piece and the one that saved the most time, because every rejection avoided is an upload cycle and an afternoon recovered.

I packaged the four scripts, make_metadata.py, upload_tracker.py, md2pdf.py, and pack_product.py, into a small kit with the folder layout and the RAM notes included. It is the exact tooling I use, not a rewrite for sale. If you are hitting the same CSV wall, it will get you past it in one command per platform.

See the kit


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

Top comments (0)