How to Automatically Reject Bad Ad Creatives Before They Go Live
By Dino S. · July 19, 2026 · 8 min read
Once you’re generating ad creative at scale — a few variants per product, a dozen hooks per angle, a batch job that runs overnight — manual review stops being an option. Somebody has to look at every ad before it spends money, and at generation volume, nobody can. What you actually need is a gate: a step in the pipeline that automatically rejects the bad creative and only lets the good stuff through.
This guide is a build doc, not a product tour. It walks through the four steps to wire an automatic reject gate into a creative pipeline — defining what “reject” means, getting a verdict you can trust, routing on it, and handling the edge cases that break batch jobs. If you want the broader picture of integrating Spendict into an AI agent workflow, that’s covered in How to Gate Ad Spend in Your AI Agent Workflow. This post is narrower: it’s specifically about building the reject gate itself.
Why a prompt-based self-check isn't a gate
The cheapest version of this — the one most teams try first — is a review prompt bolted onto the same agent that generated the creative: “Before you finish, check if this ad is good enough to run.” It feels like a gate. It isn’t.
A model asked to review its own output is structurally biased toward approving it. It just generated the copy, it has no incentive to contradict itself, and there’s no enforcement behind the check — the model can talk itself into “this is fine” on a weak hook or a compliance issue just as easily as it talked itself into writing the ad in the first place. Run the same prompt twice and you can get two different judgments. That’s optimism, not a gate.
A real reject gate needs two properties a self-check can’t give you: it has to be enforced (the ad literally cannot proceed without a passing verdict) and it has to be deterministic(the same ad always gets the same verdict, so you can trust the gate and audit it later). That means the decision has to live outside the model that generated the creative — on a server, applying fixed rules. That’s the design this guide walks through building.
Step 1: Define what "reject" means
Before writing any integration code, decide what your pipeline does with each possible outcome. Spendict’s assess_ad_creative scores a creative across seven dimensions — hook, angle, clarity, audience resonance, platform fit, CTA, and compliance — and returns one of three verdicts in launch_recommendation: run, fix_first, or kill. Map those to pipeline actions up front:
run→ approve. The creative moves to the publishing queue as-is.fix_first→ revise and recheck. The creative has a specific, named problem worth fixing rather than discarding — send it back to generation with the failure mode attached, then re-score the revision.kill→ reject. The creative fails on fundamentals a revision is unlikely to fix. Discard it and move to the next variant.
There’s a fourth case worth calling out separately: hard compliance rejection.If the compliance dimension flags a platform-policy violation, treat that as an automatic reject regardless of how the other six dimensions scored — a hook can be great and a compliance violation still gets the whole account flagged. Don’t let a strong overall score paper over a compliance failure; check that dimension explicitly in your routing logic.
Writing this mapping down before you touch the API matters because it turns an ambiguous instruction (“reject bad ads”) into three or four concrete branches your pipeline can execute without a human in the loop.
Step 2: Get a deterministic verdict
With the routing map defined, the next step is calling the scoring engine. The REST interface is the most direct way to wire this into a custom pipeline or backend job — create a key in the dashboard and call:
POST https://www.spendict.com/api/v1/assess
Authorization: Bearer spd_live_YOUR_KEY
Content-Type: application/json
{
"ad_copy": { "primary_text": "Your ad copy here" },
"platform": "meta",
"product_context": "what you sell, the offer",
"target_audience": "DTC skincare buyers, 25-40F"
}platform accepts meta, tiktok, google, linkedin, or youtube — pass the platform the creative is actually headed to, since compliance and platform-fit scoring depend on it. The response comes back with dimension scores, the single predicted failure mode, and the field your gate actually reads: launch_recommendation.
That field is the whole point. It’s not a suggestion buried in prose that you have to parse or interpret — it’s a fixed value your gate can branch on directly, because it was recomputed server-side using fixed gating rules rather than generated by the model as free text. Score the same creative twice and launch_recommendation comes back identical both times.
If you’d rather test this manually before wiring it into code, the CLI gives you the same engine from the terminal:
npm i -g spendict spendict login spendict assess \ --platform meta \ --product "your offer" \ --text "your ad copy"
It’s also available over MCP (https://www.spendict.com/api/mcp, OAuth — no key to manage) if your pipeline runs inside an MCP-compatible agent, or as a drop-in agent Skill via npx skills add spendict/skills. All three surfaces call the same scoring engine and return the same verdicts — pick whichever fits how your pipeline is already built.
Step 3: Route automatically
With a verdict in hand, the gate itself is just a conditional in your pipeline code — the mapping you wrote down in Step 1, now wired to live API responses:
- Read
launch_recommendationfrom the response. - If it’s
runand the compliance dimension is clean, push the creative to your approved-for-publishing queue. - If it’s
fix_first, pass the creative and the named failure mode back to your generation step, ask for a targeted revision, and callassess_ad_creativeagain on the result. Cap this loop — two or three revision attempts is usually enough; if a variant still isn’t arunafter that, treat it like akilland move on. - If it’s
kill, discard the variant and move to the next one. No revision attempt — the engine is telling you the fundamentals are wrong, not that the copy needs a polish pass.
Because scoring is deterministic, this routing logic is safe to run unattended. You don’t need a human to spot-check the gate’s decisions before trusting it in a nightly batch job — the same creative always produces the same verdict, so once you’ve validated the routing map on a handful of test cases, it holds.
One practical note on scale: if your pipeline supports concurrent requests, fire the scoring calls in parallel across a batch of variants. Scoring twenty creatives concurrently takes roughly the same wall-clock time as scoring one, which matters when the gate sits between generation and a publishing deadline.
Step 4: Handle quota and failures
A gate that silently breaks mid-batch is worse than no gate — creative starts flowing through unscored, or the job hangs. Two things make this predictable to handle.
First, the quota check runs before inference. If a key is out of calls for the month, the request returns a structured error immediately — the engine never triggers a model call it can’t bill for cleanly. That means a maxed-out key fails fast and fails the same way every time, which is exactly what you want in an automated batch: your pipeline can detect the quota error and stop the run cleanly instead of half-processing a batch and leaving some variants scored and others not.
Second, failed inference calls are automatically refunded. If a scoring call errors out for reasons unrelated to quota — a malformed request, a transient failure — you aren’t charged for a call that didn’t produce a verdict. Combined with the quota-before-inference ordering, this means your gate never produces partial charges: a batch either completes, or it stops cleanly at the point it ran out of quota, with a clear error your pipeline can catch and alert on.
Practically, this means your gate’s error handling only needs two branches: catch the quota error and halt the batch (or queue the remainder for next month/next key), and catch any other error and retry or skip that single variant without failing the whole run.
Doing it without code
If you’d rather not write and maintain the pipeline yourself, there’s a ready-made version of this exact reject gate as an n8n template — the ad-factory template on the n8n gallery. It implements the same four steps covered above — score, read the verdict, route to approve/revise/reject, handle quota — as a visual workflow you connect to your own generation source. More detail on the setup is on the n8n ads automation page. It’s a reasonable starting point even if you plan to eventually move the gate into custom code — you can see the routing logic working before you commit to building it yourself.
Wrapping up
The pattern underneath all four steps is the same: separate the part of your pipeline that generates creative from the part that decides whether it’s allowed to spend money. Generation can be as prolific and experimental as you want, because the gate downstream is deterministic, enforced, and doesn’t get talked out of a verdict. That separation is what makes it safe to scale creative volume in the first place — without it, more generation just means more bad ads reaching spend faster.
Start with the CLI to see verdicts against a handful of your existing ads, confirm the routing map from Step 1 matches how you want to handle each outcome, then wire the REST call into your pipeline. If you’re integrating this into a broader AI agent workflow rather than a standalone batch job, the agent workflow guide covers MCP and Skill integration in more depth.
Frequently asked questions
How do I auto-reject creatives without a human reviewer?
Call assess_ad_creative for each variant and read the launch_recommendation field in the response. Map run to approve, fix_first to a revise-and-recheck loop, and kill to an automatic discard. Because the verdict is computed server-side with fixed rules rather than generated by the model, it's safe to route on unattended — the same creative always produces the same verdict.
What's the difference between fix_first and kill for automation?
fix_first means the creative has one specific, named, addressable problem — send it back to your generation step with that failure mode attached, then re-score the revision. kill means the creative fails on fundamentals a targeted revision is unlikely to fix; discard it and move to the next variant rather than spending a revision cycle on it.
Is the reject decision consistent and auditable?
Yes. The verdict engine runs server-side using fixed gating rules, so the same creative scored twice returns the same launch_recommendation both times. That determinism is what makes it safe to build an unattended reject gate on top of it, and it gives you a consistent record of why each creative was rejected.
Can I build this reject gate in n8n without writing code?
Yes. There's a ready-made ad-factory template on the n8n gallery (n8n.io/workflows/17064) that implements the score-route-handle-quota pattern as a visual workflow — connect it to your own creative source and it applies the same run/fix_first/kill routing described in this guide.
What happens when I run out of quota mid-batch?
The quota check fires before inference, so a maxed-out key returns a structured error immediately without triggering a model call, and any failed calls are automatically refunded. Your batch stops cleanly at the point it ran out rather than producing partial results or surprise charges.
Should a compliance flag override a strong overall score?
Yes. Treat a compliance-dimension flag as a hard reject regardless of how the other six dimensions scored. A high-scoring hook and CTA don't offset a platform-policy violation — check the compliance dimension explicitly in your routing logic rather than relying on the overall verdict alone.