The problem
The product pipeline runs every fifteen minutes, all day. If each run asked an LLM to write copy for every product, that is thousands of calls a month for text that almost never changes, and a bill that grows with the schedule instead of the work. The easy answer was to not automate the writing at all, which is how most small companies end up with product pages that say nothing.
What I built
A description step with a free tier and a gate. A template writes a serviceable blurb for every product at no cost. The LLM is asked only for a product that is new, in stock, and has no copy yet, and its answer is stored in a sheet next to a column a human can overwrite. On machines signed in to my Claude plan, the call runs through headless Claude with the API key blanked, so it bills the subscription, not the meter.
What changed
The LLM runs a handful of times a month, once per new product, instead of ninety-six times a day. The bill stopped being open-ended: flat where a machine is signed in, a few cents on the smallest model where one isn't. Product pages ship with real copy on the same tick the product appears. And the gate turned out to be portable, so the same move now fronts three other automations.
Two gates, one picture
The first gate is about when. The second is about which door the call goes through. Almost every tick takes the top path and costs nothing.
Green is the path almost every tick takes. The purple boxes are the only places an LLM is ever invoked, and the left one is the trick: a signed-in machine with the key blanked cannot bill the meter even by accident.
The rules, in order of how often they fire
- A template goes first, and it isn't canned. With no LLM at all, every product gets a readable blurb built from the facts already in the sheet. Sentence shapes and word choices are picked by a hash of the product's name, so the same product always reads the same and its neighbours read differently.
- The model is asked once per item. Only a product that is in stock with both its cells blank qualifies. An existing blurb is never regenerated. A blurb a person has corrected is never touched. The step is safe to run every tick because on most ticks it does nothing.
- The human wins, forever. The copy lives in a hidden tab of the sheet the team already uses, with an Override column beside the Generated one. Anyone who dislikes a line types a better one and the site ships theirs from then on.
- Flat wherever possible. On any machine where I am signed in to Claude, the automation calls headless Claude and blanks the API key in that process first. That forces the subscription path, so the call is covered by a plan I already pay for and there is no way for it to land on the meter.
- Metered only where necessary, and small. The production server is a cloud VM with no sign-in, so its calls are metered, pinned to the smallest model, and still subject to the once-per-item rule. A failed call is non-fatal: the page ships the template copy that tick and the step retries next time.
- The key is a file, never an export. The API key lives in one file on disk, read directly by the few things allowed to use it. It is deliberately not a shell variable, because an exported secret leaks into transcripts and, after a rotation, keeps serving the old value to every process without any visible error. Both of those happened once.
- The model only writes. Every concrete claim, the aroma family, the potency, the appearance, is handed to it as a verified fact, and the prompt forbids inventing any. In a regulated product category the copy can't say things the lab report doesn't.
Why headless Claude is the interesting part
Claude can be run from a terminal without a conversation, a prompt in and an answer out. Run that way on a machine that is signed in to a subscription, it draws on the plan rather than the pay-per-token API, and the plan is a fixed number every month. The one thing to get right is that the same command will happily use an API key if it finds one in the environment. So the gate clears the key inside the child process before the call, every time. The metered path becomes impossible from that machine, not just avoided.
That turned the cost of automating with an LLM from an open-ended variable into a controllable one. A metered API on the loose bills for every run of every script that someone later wires up and forgets. A subscription with the meter unplugged bills the same whether the scripts run ten times or ten thousand. For a small company that is the difference between "let's automate that" and "let's not risk it".
Where the gate has gone since
The move is a few lines, so it became a module rather than a one-off. It now fronts a meeting assistant that reads a transcript and proposes calendar events and tasks, with a second headless pass that re-reads the transcript knowing what the first pass caught. It fronts a hook that turns a finished work session into searchable facts. And it fronts a lab-report extractor that reads a certificate of analysis PDF into a fixed schema, replacing a cascade of thirteen regular expressions, though that one is parked as a branch until the sheet it feeds is ready to switch over.
Where it stands
The description step has run in the 15-minute pipeline since July 2026, first against a local model on a Mac, since September against Claude on the production VM. The template tier, the once-per-item rule, and the override column are the parts that survived every backend change, which is the point of building the gate separately from the model behind it.
Stack
The cheapest LLM call is the one you don't make. The second cheapest is the one already covered by a plan you're paying for anyway.
Built in conversation
Same method as everything on this site. The gate, the template engine's variation trick, and the override column all came out of dialogue with Claude, including the part where Claude helped design the thing that stops Claude from being called too often.