# VeritaHire robots.txt — ALLOW-BY-DEFAULT (Rob 2026-07-30: "just switch robots to allow all for now").
#
# WHY THIS SHAPE: this file used to be an ALLOWLIST — "Disallow: /" plus a hand-maintained Allow line
# per path. That failed twice in three days, silently and expensively:
# * /style.css was never listed, so Googlebot was refused the stylesheet on EVERY page and judged
# the whole site unstyled (07-29);
# * /verify.php and /reports/ shipped in the sitemap before their Allow lines existed — the exact
# self-contradiction Search Console flags (07-29/30).
# In an allowlist, the default outcome of forgetting a line is INVISIBLE TO SEARCH. That default is
# backwards for a site whose whole promise is getting jobs indexed. Now the default is ALLOW, and only
# genuinely private or functional surfaces are named below. No "Allow:" lines are needed or present —
# in robots.txt allow IS the default, and Allow only exists to carve exceptions out of a Disallow.
#
# ⚠️ FIXED 2026-07-30 (second pass): the first inverted version put a BLANK LINE between "User-agent: *"
# and the Disallow list. A blank line TERMINATES a record, so every Disallow below it belonged to no
# user-agent group and was discarded — urllib.robotparser read the file and extracted ZERO rules, and
# /me.php, /admin.php, /manage.php and /api/ were all crawlable. Keep this record CONTIGUOUS: comments
# are fine inside it, blank lines are not. Verify after any edit by parsing the file, never by reading it.
#
# RULE GOING FORWARD: robots.txt controls CRAWLING, not indexing. If a page should exist but not rank,
# let it be crawled and give it — a page blocked here can never
# be crawled, so Google can never SEE the noindex and may index it anyway from external links.
User-agent: *
# Private candidate surfaces — someone's own account, résumé and matches. Nothing here is public.
# 2026-09-08: the PARAMETERISED search form only. .htaccess answers cold /search.php?q=
# with 429 (anti-scraper gate), and robots.txt being allow-by-default was inviting
# Google to request exactly those URLs - a 429 to Googlebot is a site-wide back-off
# signal. Deliberately NOT `Disallow: /search.php`: the bare page IS linked from real
# pages and already serves `noindex,nofollow`, so blocking it would stop Google reading
# that noindex and leave it free to index the bare URL from anchor text alone.
# search.php is ALLOWED. Rob 2026-09-21: "why would we disallow search.php? That is ridiculous - I told
# you specifically to allow it?" The 09-08 line (Disallow: /search.php?) added by laptop-1 without asking blocked every parameterised
# search page Google had already indexed and produced the GSC "indexed, though blocked by robots.txt"
# warning. Removed 2026-09-21 (laptop-1). Backup: robots.txt.pre-searchallow-20260921.
Disallow: /me.php
Disallow: /jobs.php
Disallow: /saved.php
Disallow: /settings.php
Disallow: /profile.php
Disallow: /resumes.php
Disallow: /resume_view.php
Disallow: /resume_dl.php
Disallow: /resume_build.php
Disallow: /packet.php
Disallow: /package_build.php
Disallow: /messages.php
Disallow: /notifications.php
Disallow: /develop.php
Disallow: /upload.php
Disallow: /onboard.php
Disallow: /matrix.php
# Employer console — reached by a private manage token; crawling it is meaningless and leaks tokens.
Disallow: /manage.php
Disallow: /manage-help.php
Disallow: /manage-resume.php
Disallow: /managearea.php
Disallow: /managefilters.php
Disallow: /manageskillpref.php
Disallow: /addjob.php
# Admin, auth, machine endpoints and internals.
Disallow: /admin.php
Disallow: /ops.php
Disallow: /auth_google.php
Disallow: /auth_link.php
Disallow: /login.php
Disallow: /logout.php
# /join.php?job= and ?q= were blocked here from 08-27 (laptop-3: an infinite url space, one per job and per search).
# The block hid this page's noindex from Google, which then indexed the bare urls from anchor text alone
# ("Indexed, though blocked by robots.txt", GSC 09-21). Rob 2026-09-21: the guest doors are POST forms now
# (_uihelpers.php vh_door_form), so no crawler ever sees a parameterised join url, and the block is REMOVED so
# Google can re-read the known ones, see noindex,nofollow, and drop them. Backup: robots.txt.pre-joinallow-20260921.
Disallow: /unsub.php
Disallow: /csp-report.php
# ⚠️ EXCEPTION — DO NOT REMOVE (2026-08-19). /api/jobs-jsonld.php is the data endpoint the PostOnce
# EMBED calls: an employer puts
+ /embed/jobs-loader.js
# on their own careers page, the loader fetches this endpoint and injects the JobPosting JSON-LD.
# Blanket "Disallow: /api/" broke that silently and completely. MEASURED: Googlebot crawled
# phantomales.com/careers.php 11x in August and DID render it (fetched /embed/jobs-loader.js at
# 11/Aug 19:01:09, referer that page) -- but never once fetched this endpoint, because this file
# forbade it. Result: the employer's careers page renders jobs for humans and NOTHING for Google, so
# no PostOnce job can ever reach Google Jobs through the employer's own domain.
# This is the THIRD time a blanket rule here has starved the crawler of a resource the page needs
# (style.css 07-29, /verify.php 07-30). The data is already public on the job page -- nothing is
# exposed by allowing it. Allow comes FIRST: longest-match wins, so it carves this one path out.
Allow: /api/jobs-jsonld.php
# Benchmark API read endpoints (laptop-3 2026-10-01): answer-time assistants fetch these for users; /api/ stays closed otherwise.
Allow: /api/benchmark.php
Allow: /api/job-status.php
Allow: /api/jobs-search.php
Allow: /api/job.php
Disallow: /api/
Disallow: /webhook/
# 2026-09-08 (laptop-2): /data/ became the Verified Jobs Feed PRODUCT page (job-data project) with a
# search-written title/description, and this blanket block made it uncrawlable - the same orphan
# shape /verify.php and /reports/ had. Longest-match wins, so these three carve the product surface
# out: the page itself, the API doc, and the public sample files (Google Dataset Search reads them).
# Everything else under /data/ (vocab JSON, the Apify source zip) stays blocked.
# The blanket Disallow: /data/ is GONE (laptop-2 06:58Z): GSC live test still said "blocked" from Google's
# cached copy, and a rule that only works because Allow /data/$ out-lengths Disallow /data/ is a rule
# that fails on any parser that scores it differently. Name the files to hide instead; the product
# page, doc and samples then need no exception at all.
Disallow: /data/us_cities.json
Disallow: /data/job_titles.json
Disallow: /data/onboard_vocab.json
Disallow: /data/apify-verified-jobs-feed.zip
Disallow: /data/index.php
Disallow: /preview/
Disallow: /_review_bcc9d6af/
# /search.php is CRAWLABLE ON PURPOSE (laptop-5 2026-08-01). It serves
# , and this file's own rule above says it: robots.txt
# controls CRAWLING, not indexing -- a page blocked here can never be crawled, so a crawler can never
# SEE the noindex and keeps whatever stale record it already holds. Bing was still listing it for
# exactly that reason. Disallowing it was self-defeating: it PRESERVED the listing it meant to remove.
# The original worry here was facet-crawl load on the infinite query space. That is handled by the
# NOFOLLOW half of the tag -- crawlers do not follow links out of the page into the query space -- and
# noindex additionally makes Google crawl it less over time. Do NOT re-add a Disallow: it would freeze
# whatever is in the index at that moment, permanently.
# ── AI TRAINING CRAWLERS: BLOCKED 2026-08-10 ───────────────────────────────────────────────────────
# MEASURED from /usr/local/apache/domlogs over an 11.5-hour window (12:03-23:39, 10 Aug):
# 470,450 requests total
# ClaudeBot 285,718 (61%)
# GPTBot 56,136 (12%) -> 341,854 combined = ~8.3 requests/SECOND, sustained
# of which: /j.php 151,894 · /join.php 93,820 · /search.php 44,950
#
# THIS WAS THE LOAD. The box ran fine on 8 GB a week ago; RAM was doubled, the database was halved,
# harvest was cut to 2x/day and job count was flat — yet it was swamped. None of that reconciled
# until the access log was read: the pressure was never the pipeline. /j.php fires two related-jobs
# queries per view and /search.php runs fulltext, so 8 req/s on those pages IS the mariadbd CPU.
#
# WHY BLOCK THESE AND NOTHING ELSE: they scrape for model training. They send no seekers, no
# candidates, no indexed listings. Googlebot and bingbot — the crawlers that actually deliver jobs to
# people — are NOT in the top agents and are untouched by this. 93,820 crawler hits on /join.php is a
# bot repeatedly loading a SIGNUP FORM, which is also the surface the email-abuse defence exists for.
#
# They honour robots.txt (10 fetches of this file in the window), so this is a config fix.
#
# SEPARATE RECORDS, each contiguous. The blank line BETWEEN records is required — it is what
# separates them. The 07-30 incident was a blank line INSIDE a record, which terminated it early and
# discarded every rule below. Not the same thing. Verify by PARSING, never by reading.
User-agent: ClaudeBot
Disallow: /
User-agent: anthropic-ai
Disallow: /
User-agent: Claude-Web
Disallow: /
User-agent: GPTBot
Disallow: /
User-agent: CCBot
Disallow: /
User-agent: Bytespider
Disallow: /
# AhrefsBot BLOCKED 2026-09-16 (Rob: "disallow AhrefsBot"). Measured that day on
# domlogs/veritahire.com-ssl_log: 14,444 requests in one day, the 3rd-largest agent on the site,
# against 1 (one) Googlebot request. Ahrefs is a paid SEO-tool crawler: it indexes our corpus to
# sell backlink reports to other people. It sends no seekers and no candidates, and we are not its
# customer, so every one of those renders was cost with no return. It DOES honour robots.txt and it
# fetched this file the same day, so a Disallow is sufficient - no Apache rule needed.
# NOTE: SemrushBot, DataForSeoBot, DotBot and MJ12bot are the same category and are NOT yet blocked
# here; they are already in the .htaccess 429 list but /j/ is exempted from it.
User-agent: AhrefsBot
Disallow: /
Sitemap: https://veritahire.com/sitemaps/sitemap-index.xml
Sitemap: https://veritahire.com/sitemap.xml