By JaySeptember 2026

The One Page We Didn't Make Agent-Friendly

View as Markdown

The original article walked through making addAI.dev legible to AI agents:

The whole argument was that a site invisible to agents is quietly losing business it never gets the chance to see. We went further than most of that advice usually goes, too, carving out a specific exception in robots.txt so a live, user-directed agent like ChatGPT-User or Claude-User could complete a booking on a real visitor's behalf, not just read the page.

Then we turned around and put a CAPTCHA on our own Contact Us and Join Us forms - built to prevent abuse by spam bots. This does impact agents though, including well-intentioned ones acting on someone's behalf.

That's not a contradiction. It's a different kind of endpoint, and it's worth being explicit about why.

Two different jobs

A booking API and a contact form look similar from the outside - both take a POST request, both end in an email or a database row - but they're doing different work. Completing a booking is a task: is there an opening, yes, reserve it, done, and it doesn't much matter whether the hand on the wheel is a person's or an agent's. A submission through a contact form is the opening move in a relationship a business is about to spend real time on. Every one of them gets read by a person and replied to by a person.

A contact form submission is the opening move in a relationship a business is about to spend real time on.

Those two endpoints want opposite defaults. The booking API wants to be as reachable as possible, including by an agent acting on a real person's behalf - that's the whole point of the earlier post. The contact form wants some assurance that an actual person is behind the click, because the cost of getting that wrong isn't a wasted crawl, it's a founder spending ten minutes replying to something that was never a real inquiry.

What we actually added

Three layers, in order of how cheap they are:

The honeypot, exactly as it sits in our own Contact Us form:

<div class="hp-field" aria-hidden="true">
  <label for="company">Company</label>
  <input id="company" name="company" type="text" tabindex="-1" autocomplete="off">
</div>
contact.html

Positioned off-screen with left: -9999px, not display: none or visibility: hidden - some bots specifically check for those two properties to detect a honeypot and skip the field, so we don't use them. tabindex="-1" keeps it out of a real visitor's tab order too, in case a screen reader or keyboard user ever reaches it. The check on the way in is one function:

export function isHoneypotTripped(data) {
  return String(data.get("company") || "").trim().length > 0;
}
functions/_lib/security.js

And the short-circuit at the top of the handler, before any real validation runs:

if (isHoneypotTripped(data)) {
  return Response.json({ ok: true });
}
functions/api/contact.js

Same response shape as a real success. Nothing distinguishes it from the outside, which is the point.

The Turnstile check that runs after it, if the honeypot didn't already catch the request:

POST https://challenges.cloudflare.com/turnstile/v0/siteverify
secret=<TURNSTILE_SECRET_KEY>
response=<token from the widget>
remoteip=<visitor's IP>
siteverify request

The secret key backing that call never touches a file on disk or a git commit - it's stored directly as a Cloudflare Pages secret via wrangler pages secret put, the same pattern we already use for the internal secret between our Pages Functions and the email-sending Worker.

What Turnstile can't tell apart

Cloudflare Turnstile doesn't know the difference between a spam bot and a well-intentioned AI agent acting on a real person's explicit request. It's built to detect automation, not intent. If someone asked ChatGPT to "reach out to addAI.dev and ask about their services," the very same ChatGPT-User agent we explicitly allowed onto /booking-api/ in the original article would, in all likelihood, get challenged and blocked here.

It's the tradeoff we're choosing for this specific kind of endpoint. We'd rather lose the convenience of an agent filling out our contact form on someone's behalf than open a lead-gen inbox to anything capable of completing a form - and right now, Turnstile can't tell us which of those two things is actually happening. Until a widget exists that verifies "a real person is driving this, agent or not" rather than "no automation is involved," a contact form and an agent-assisted booking are going to keep pulling in opposite directions.

A small cosmetic tax

Turnstile's dark theme still renders the widget inside an iframe with its own default border, and that border doesn't fully disappear even with data-theme="dark" set. This turns out to be a known, reported issue, not something specific to our setup - there's a Cloudflare community thread titled exactly that, Turnstile dark theme white border, and Cloudflare's own documentation is upfront that the widget's internal rendering is outside what a parent page's CSS can reach, since it's a cross-origin iframe Cloudflare controls.

The Turnstile widget rendered in dark theme on our Contact Us form, with a light-colored border still visible around the frame
our own Contact Us form, dark theme, live

The workaround we landed on isn't a fix, it's a dodge: data-appearance="interaction-only" keeps the widget invisible until a visitor actually starts filling out the form, so the border shows up briefly rather than sitting on the page for the entire visit.

Where that leaves us

The original article's pitch was "make yourself legible to agents so you don't lose business you never see." This one's pitch is narrower: not every page on a site should optimize for that. A booking, a price list, a services page - all fair game for an agent to read or even act on for someone. A contact form asking a business to spend a person's time is a place worth keeping a little friction, on purpose.

A contact form is one place worth keeping a little friction, on purpose.

If you'd rather talk through where that line should sit on your own site, we're glad to help you think it through.

Get in touch