robots.txt and llms.txt: control what crawlers and models are shown

Embed
Preview of the card
<a href="https://forumulate.com/guides/robots-and-llms"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/6c8e4e84-2d53-4971-ae55-07a19eb31fdb/social-card.svg?v=3e7ae33eec8b" alt="robots.txt and llms.txt: control what crawlers and models are shown" width="600" height="315"></a>

Every site writes robots.txt (for crawlers), llms.txt, llms-full.txt and llms-guides.txt (for models) from what its spaces publish. You shape them with two settings on the site: robots and llms. Edit them in the console under the site's Crawlers and AI files (with a live preview), over the API at GET/PUT/PATCH /orgs/{orgId}/sites/{siteId}/robots and …/llms (both take ?dryRun=true), or in forumulate.yaml under sites[].config. Changes reach the files within seconds, on the platform address, on custom domains and on site mounts.

robots

Merge order, from the strongest: the generated rules for deindexed and private spaces are always kept, in every group. Then a crawler's own AI policy, then the global AI default, then extraRules, disallowPaths, sitemaps, and last your raw block.

Key

What it does

aiCrawlers

disallow refuses every AI crawler below; allow leaves them to each space's AI-ingestion setting.

aiCrawlerPolicy

Per bot: allow, disallow or default. Beats aiCrawlers. A bot allowed here is not named in the file; it reads the everyone group. Unknown bot names are refused.

disallowPaths

Paths added to the everyone (*) group: /drafts/, /*.pdf$.

extraRules

Your own groups: userAgent, allow[], disallow[], crawlDelay. A group joins the generated one for the same crawler. An allow cannot re-open a generated private path.

sitemaps

auto (default) names the site's sitemap; off leaves it out.

extraSitemaps

More Sitemap: URLs.

raw

Appended last, verbatim: directives and # comments only, at most 200 lines / 8000 characters, starting with a User-agent line.

The AI crawlers: GPTBot, ChatGPT-User, OAI-SearchBot, ClaudeBot, Claude-User, Claude-SearchBot, anthropic-ai, PerplexityBot, Perplexity-User, Google-Extended, Applebot-Extended, CCBot, Bytespider, Meta-ExternalAgent, Amazonbot, cohere-ai, DuckAssistBot, YouBot. If every space is closed to indexing the file is a flat refusal and nothing here can open it.

sites:
  - slug: main
    config:
      robots:
        aiCrawlers: disallow
        aiCrawlerPolicy: { ClaudeBot: allow, GPTBot: allow }
        disallowPaths: ["/drafts/"]
        extraRules:
          - { userAgent: bingbot, crawlDelay: 5 }
        extraSitemaps: ["https://example.com/news.xml"]

Previewing a robots.txt before saving it

POST /orgs/{orgId}/sites/{siteId}/robots/preview takes a draft robots object (the same shape as the PUT body) and answers { "text": "…" }, which is exactly the file the site would serve, merged with the generated rules. Nothing is stored. The console's live preview uses it.

curl -X POST "https://forumulate.com/api/orgs/$ORG/sites/$SITE/robots/preview" \
  -H "Authorization: Bearer $FORUMULATE_TOKEN" -H "Forumulate-Version: 2027-03-01" -H "Content-Type: application/json" \
  -d '{"aiCrawlers":"disallow","aiCrawlerPolicy":{"ClaudeBot":"allow"}}'

llms

Key

What it does

enabled

false withdraws every llms file (404).

title, summary

The # title and the blockquote (defaults: site name, tagline).

intro

Markdown before the first section: how a model should use the site.

include, exclude

spaces (slugs), collections (ids), pages (globs over space/page, or a bare slug glob). With include, only what it names is listed; exclude wins. They apply to llms.txt, llms-full.txt, the guides parts and the member spaces' own llms files.

sections

Order of the spaces, with an optional heading and description each; unlisted spaces follow.

grouping

space (a heading per space) or flat.

optional

The llmstxt.org "Optional" section: title and links[].

full

enabled: false withdraws llms-full.txt and the guides; maxBytes stops at a whole page.

A single page opts out with llms: false in its metadata or front matter. The guides split into as many llms-guides-<n>.txt parts as needed (each at most 2 MB), llms.txt links every part, and a part past the last is a 404.

Where each file is served

File

Address

What it holds

The site's index

<site root>/llms.txt

Every member space, per the llms settings above.

The site's full text

<site root>/llms-full.txt

Every page of every member space.

The site's guides

<site root>/llms-guides.txt, then llms-guides-2.txt, …

Every page except generated API reference pages, in parts of at most 2 MB.

A space's index

<space root>/llms.txt

The space's pages, then a Full text section linking the three files below.

A space's full text

<space root>/llms-full.txt

Every page of the space.

A space's guides

<space root>/llms-full.txt?part=guides

Every page except the API reference.

A space's API reference

<space root>/llms-full.txt?part=reference

Only the generated reference pages.

The site files sit at the root of the host the site owns: https://docs.example.com/llms-guides.txt when the site is the whole domain, or /s/{org}/{site}/llms-guides.txt on our address. When your own app serves the host root and forwards only the space paths (/docs, /guides) to us, the root files are your app's to forward or serve. A space's files always answer under its path, so https://example.com/docs/llms-full.txt?part=guides is the guides-only file for that space.

sites:
  - slug: main
    config:
      llms:
        title: Acme for models
        summary: Docs and API reference for Acme.
        exclude: { pages: ["internal-*"] }
        sections: [{ space: docs, title: Documentation }]
        full: { maxBytes: 4000000 }

llms-quickstart.txt: the short file

llms.txt indexes every guide, which is the right file for a crawler and too long for an agent that wants to answer one question. /llms-quickstart.txt, beside it on the site's address, is the page to read first: your title, summary and intro, the first six pages of each space (your sidebar order is the order of importance, and llms: false pages and include/exclude are already applied), and the two ways to go further: the site search (GET /public/orgs/{orgId}/sites/{slug}/search?q=, anonymous, JSON hits with absolute URLs; see Site search) and the full llms.txt. It goes with the rest when enabled: false.

Files are served with Cache-Control and a strong ETag (a matching If-None-Match answers 304), and are refreshed when a page or the site changes.

IndexNow: telling search engines a page changed

robots.txt and the sitemap wait for a crawler to come back; IndexNow tells Bing, Yandex, Seznam and Naver at once. Switch it on per space — studio → Settings → Search engines (IndexNow), or PUT /orgs/{orgId}/spaces/{spaceId}/publish-pings with {"indexNow": true} (PATCH works too), or settings: { publishPings: { indexNow: true } } on the space in forumulate.yaml (any space type, the same key). Off until you switch it on.

Space type

What is pinged

Docs, FAQ, compare, custom, courses

The page's address on publish, on an edit that changes what readers see, on a rename (old and new address) and on delete.

Legal

The document's address (…/privacy) and the register.

Blog, changelog

Each post or release as it is published.

About

The page, its press kit and its people hub.

Careers

Each role as it opens, changes or closes (…/careers/indexing/indexnow is the same switch).

Forum, status, contact, events, roadmap and content spaces do not ping: indexNow: true on one answers 422 validation.failed with details.indexNow, rather than storing a switch that never fires.

Only what the sitemap would list is pinged. Nothing goes out for a private space, one behind reader sign-in, or one set to noindex, nor for a page set to noindex or limited to an audience. A page edited in quick succession is pinged at most once every ten minutes; a rename or a delete always goes out.

The key file is served at <space root>/indexnow.txt while the switch is on, and answers 404 otherwise. That is /p/{org}/{space}/indexnow.txt on our address, https://docs.yourcompany.com/indexnow.txt on a domain of its own, and https://yourcompany.com/docs/indexnow.txt under a path mount (the IndexNow spec accepts a key in a folder for the addresses under that folder). The API answers the same key at GET /public/orgs/{orgId}/spaces/{spaceId}/indexnow-key and …/indexnow.txt. The key is derived from the space and never changes, and it is the one pings are signed with, so you cannot supply your own.

In the API

Every operation below is also reachable from the other surfaces without a dedicated command. forumulate api <Operation> calls it from the CLI, the JavaScript SDK has a typed method for each one (the last column names it; the Python and Go SDKs are generated from the same spec), and an agent reaches the organization-scoped ones through the MCP agent server — its api_* family tools, or call_api. Where a CLI command or a hand-made MCP tool exists too, the last column names it. Operation names link to the API reference.

Public Site

Operation

Request

What it does

Shortcuts

PublicSite_overview

GET /public/orgs/{orgId}/sites/{slug}

Get the site (public reader)

client.sites.public.overview()

PublicSite_search

GET /public/orgs/{orgId}/sites/{slug}/search

Search a published site

client.sites.public.search()

PublicSite_ask

POST /public/orgs/{orgId}/sites/{slug}/ask

Ask a question on a site

client.sites.public.ask()

Site Machine Files

Operation

Request

What it does

Shortcuts

SiteMachineFiles_getRobots

GET /orgs/{orgId}/sites/{siteId}/robots

Get the site’s robots.txt rules

client.sites.machineFiles.getRobots()

SiteMachineFiles_setRobots

PUT /orgs/{orgId}/sites/{siteId}/robots

Set the site’s robots.txt rules

client.sites.machineFiles.setRobots()

SiteMachineFiles_patchRobots

PATCH /orgs/{orgId}/sites/{siteId}/robots

Change part of the site’s robots.txt rules

client.sites.machineFiles.patchRobots()

SiteMachineFiles_getLlms

GET /orgs/{orgId}/sites/{siteId}/llms

Get the site’s llms.txt rules

client.sites.machineFiles.getLlms()

SiteMachineFiles_setLlms

PUT /orgs/{orgId}/sites/{siteId}/llms

Set the site’s llms.txt rules

client.sites.machineFiles.setLlms()

SiteMachineFiles_patchLlms

PATCH /orgs/{orgId}/sites/{siteId}/llms

Change part of the site’s llms.txt rules

client.sites.machineFiles.patchLlms()

Site Robots Preview

Operation

Request

What it does

Shortcuts

SiteRobotsPreview_preview

POST /orgs/{orgId}/sites/{siteId}/robots/preview

Preview the site’s robots.txt

client.sites.robotsPreview.preview()

The Forumulate teamUpdated