I still have the screenshot of the rejection. Adobe Stock bounced a batch of 12 vectors because my CSV header had five columns and I'd given it four. No explanation, just a red error at the top of the upload page. I'd spent two days generating the art. The upload took ninety seconds to fail.
That was the moment I stopped treating my pipeline like a hobby and started treating it like a register. Every failure got written down. Over the following weeks I logged 39 distinct gaps between "I made art" and "this is a sellable product", and I closed 36 of them with four boring Python scripts. Here is what actually worked.
Metadata is a formatting test, not a creative one
The stock platforms do not read your CSVs the way a human does. Adobe expects a five column header. Vecteezy expects four. Dreamstime wants fifteen. Get one column wrong and the file is rejected before anyone looks at your art.
So the first script I wrote, make_metadata.py, does exactly one thing: it takes a folder of files and writes the exact header each platform demands, nothing else. It runs on the standard library alone, no pip install. You point it at a folder:
python make_metadata.py ./my-assets --platform adobe --out metadata.csv --ext .jpg Without a titles file it title-cases the filename, which is fine for a first pass and not fine for submission. I learned that the hard way too: assets titled after their filenames get through review less often than assets with real titles and keywords. The optional --titles titles.json flag exists because of that.
Double-posting is the fastest way to get flagged
My second real failure: I re-ran a batch upload after a timeout, posted the same 20 files twice, and spent an afternoon deleting duplicates while wondering if the account would survive it. Stock platforms are not forgiving about this.
upload_tracker.py is a ledger, also pure standard library. Three commands:
python upload_tracker.py pending items.txt python upload_tracker.py done "asset-001.jpg" python upload_tracker.py status Pending shows only what is not done yet. A re-run can never double-post because the ledger is checked before anything is queued. It is the least interesting script in the kit and the one I would least want to lose.
A document is not a product until it looks like one
I had content sitting in Markdown files for weeks before I could sell any of it, because turning Markdown into a presentable PDF felt like a separate project. It is not. md2pdf.py uses reportlab, one of only two third-party packages in the whole kit, and does headings, tables, lists and links in one command:
python md2pdf.py content.md product.pdf "My Product Title" The other package is Pillow, used by pack_product.py to render a 1280x720 cover and a 600x600 thumbnail. That script bundles the zip and writes both images into a _system folder, and you can redirect it with --dir or a PACK_ROOT environment variable instead of editing anything. A zip with a real cover sells. A bare file does not. I do not have A/B numbers to prove that, but every product I packaged properly moved, and the bare ones did not.
The whole thing ran on 1 vCPU and 2 GB of RAM
No GPU, no cloud account. That constraint shaped the pipeline more than any platform rule did. The habits that made it work:
One process at a time. Fanning out image jobs in parallel on a single core just swaps memory and slows everything down. Call gc.collect() after each asset. Measure peak RSS with resource.getrusage(RUSAGE_SELF).ru_maxrss, not average usage, because the peak is what kills a 2 GB box. Run heavy render steps one file per subprocess so memory actually returns to the OS. And render at the resolution you need: inspecting a preview at 60 dpi instead of 300 saves more time than any other single change I made.
The folder layout is part of the system
A catalogue becomes a junk drawer without a fixed structure. The kit assumes five folders: assets/ for raw output, output/ for ready-to-submit files plus the metadata CSV, packs/ for finished zips and covers, research/ for notes and sources, scripts/ for the tools.
The one I would underline is research/. I once deleted scrape logs and notes to "clean up", and later wanted the exact sources behind a product I had already shipped. Keep it forever. Those notes are raw material for the next product.
Where the numbers landed
The four scripts packaged 39 products, 856 MB of output, and closed 36 of the 39 gaps in my register. The parent pipeline is around 19,000 lines of code, the rules the scripts enforce sit on top of 300+ pages of platform research, and everything was validated on that 1 vCPU, 2 GB machine. The catalogue so far: 1,860 assets made, 1,077 ready or submitted. The first paid sale was for the system itself, Digital Asset Factory, $2.80. I am not going to pretend that is a business yet. It is proof the pipeline produces things people will pay for, which is the part that took the longest to believe.
I am not selling a dream of passive income. I am selling the packaging layer I use every week, minus the prompt engine, because that part is personal to my catalogue. If your stall point is metadata, double-posting, PDFs or packaging, these four scripts are the honest version of the fix.
Building digital products? Free guides & kits for digital product sellers.
