Skip to content Documentation index for agents (llms.txt)
Glotto Beta
Get started

glotto.yml project settings

Project settings

diagnostics

diagnostics:
  rules:
    no-error-response: error          # remap a lint rule's severity (off | warn | error)
    mutation-no-idempotency-key: off  # suppress a rule
  max_warnings: 10                    # release-gate: linting fails above this budget

Tunes spec linting without touching the rules engine: rules overrides per-rule severity (off suppresses), and max_warnings is a release-gating threshold (lint exits non-zero once warnings exceed it). Optional and additive. See the Diagnostics reference for the full rule list and behavior.

settings

settings:
  detect_breaking_changes: true

Stainless-parity toggles. detect_breaking_changes makes breaking-change detection call out every breaking change at the top of the preview comment on your pull request, rather than leaving it in a table cell. Optional and additive.

code_owners

code_owners:
  '*':
    - '@acme/sdk-team'
  'src/**':
    - '@acme/core-team'
    - '@octocat'

Declares who reviews changes to the repos Glotto manages. Optional and additive — set nothing and your output is byte-for-byte what it was.

Rules are emitted in the order you declare them, because a CODEOWNERS file is resolved last-match-wins: the rule furthest down that matches a path is the one that owns it. So the block above gives the SDK team everything and hands src/** to the core team and @octocat.

Owner syntax is per-provider, resolved from each target’s repo_provider and defaulting to github:

Provider Owners it accepts
github @login (letters, digits and single interior hyphens, ≤39 characters), @org/team-slug at exactly two segments, or an email address.
gitlab The same, plus _ and . inside a segment, and @group/subgroup/… nested to any depth.
bitbucket A workspace member’s handle, account id, uuid, or display name — resolved against your workspace when you apply it.
azure-repos An identity’s display name, unique name, or email — resolved against your organization when you apply it.

A GitHub team handle is not a GitLab one, and an owner that a forge cannot resolve makes it ignore the whole line — the path is left unowned and nothing says so. That is why Glotto rejects an owner the target’s provider could not resolve rather than emitting it (GLOTTO_CONFIG_CODE_OWNERS).

Two of the four providers read no CODEOWNERS file at all. GitHub and GitLab receive .github/CODEOWNERS and .gitlab/CODEOWNERS from glotto generate; Bitbucket configures a repo-wide default-reviewer set and Azure Repos a required-reviewers branch policy, both through their APIs — so for those two, Glotto applies the block by calling the provider rather than by writing a file into your repository.

It authenticates with the credentials you connected the repository with; see Connect your VCS. Bitbucket’s default reviewers have no path scoping, so the result names which of your patterns had to be flattened, and any owner whose directory matched nothing, rather than reporting a plain success.

A target with a repo_path — an SDK living in a subtree of a shared repo — receives no file either: GitHub and GitLab read CODEOWNERS only from the repository root, so one written into the subtree would never be opened. Every one of these cases is reported by name, by glotto generate, as GLOTTO_CONFIG_CODE_OWNERS_NOT_APPLIED.

mcp

mcp:
  package_name: "@acme/api-mcp"   # emitted package identity (default: <org>-mcp; a valid npm name)
  registry_name: io.github.acme/api  # opt-in: emit a server.json for the official MCP registry
  modes: [code, tools, dynamic]  # MCP server modes to enable (omit for all three)
  filters_enabled: true
  search_docs: true
  operations:             # per-tool overrides (canonical <resource>_<method> keys)
    pets_get: { description: "Fetch a single pet by id.", name: fetch_pet }
  permissions:            # method allow/block sets (convenience layer, NOT a security boundary)
    allow_http_gets: true          # allow every operation mapped to HTTP GET
    allowed_methods: [pets\..*]    # regexes over the qualified <resource>.<method> name
    blocked_methods: [pets\.delete]  # applied last — beats both allow keys
  experimental:
    async_tasks: false    # EXPERIMENTAL: task-capable tools for long-running operations (default off)

Configuration for the generated multi-mode MCP server. See the MCP server guide. permissions narrows which operations the emitted server can reach: patterns match the fully-qualified <resource path>.<method> name (pets.photos.add) and are fully anchored, the allow set is constrained only when allowed_methods or allow_http_gets is set, and blocked_methods is subtracted last. Denied operations are never registered in Tools/Dynamic Mode, and Code Mode’s execute refuses submitted code that references one before running it. It is a convenience layer that keeps an agent in its lane, not a security boundary — the Code Mode half is static analysis, which obfuscation defeats; use a scoped API token or the MCP Cloud gateway for the real boundary. See Method permissions. experimental.async_tasks opts the operations your spec marks long-running into the MCP async tasks capability — see Experimental: async tasks. It is experimental and off by default — and currently withheld: the final 2026-07-28 MCP spec moved tasks to the io.modelcontextprotocol/tasks extension, and until the emission is rebuilt for that shape, enabling the flag over a long-running-capable API fails generation with an actionable error.

docs

docs:
  theme: { primary: "#5b21b6" }
  authoring: { format: mdx, base_path: ./docs }
  deploy: { target: cloudflare, custom_domain: docs.acme.com }
  ask_ai: { endpoint: https://ask.acme.com }   # docs "Ask AI" widget
  analytics: { posthog_key: phc_yourkey }      # cookieless analytics (opt-in; PostHog or Plausible)
  search:                                      # rename and order the generated search facets
    facets:
      - { filter: method, label: HTTP method }
      - { filter: resource, label: API resource }
  i18n: { default_locale: en, locales: [en, de] }   # site locales (routing + lang + chrome strings)
  structured_data: {}                          # schema.org JSON-LD on every page (opt-in)
  versions: [ { slug: v2, default: true, openapi: { source: ./v2.yaml } } ]

The generated docs site: theme, authoring, deploy target (cloudflare / vercel / netlify / static / self-host), the Ask-AI widget, opt-in analytics, locales, structured data, and multi-version docs.

search.facets controls the names and display order of the generated search filter panel. Each entry’s filter is one of method, resource, language, or parameter; label is optional, and the list order is the display order. A filter you omit keeps its default name and appears after the configured filters. Omitting search preserves the default generated site unchanged.

structured_data is off unless you set it (structured_data: { enabled: false } turns it back off explicitly). With it on, every generated page carries one schema.org JSON-LD block derived from your spec and your configured site identity — an APIReference per operation with the same title, description, and canonical URL its <head> already advertises, a SoftwareSourceCode per code sample you show, breadcrumbs matching the page’s place in the navigation, and WebSite + Organization on the home page. It is re-derived on every regeneration, so it cannot drift from your spec the way hand-maintained markup does.

For the things your spec cannot know — your organization’s sameAs profiles, a HowTo on a guide, a locale-prefixed route — add your own nodes:

docs:
  structured_data:
    extra_nodes:                      # appended to every page
      - "@type": Organization
        "@id": https://acme.com/#org
        sameAs: ["https://github.com/acme"]
    extra_nodes_by_route:             # appended to one route only
      /guides/quickstart:
        - "@type": HowTo
          name: Quickstart

A node is free-form: whatever keys you write are emitted as authored, inside the same single JSON-LD document, escaped the same way the derived nodes are. What Glotto does check is the node’s shape — that it is a non-empty object, that it carries a @type a consumer can dispatch on, and that @id/@context hold the forms JSON-LD defines for them. A node that fails one of those, or that claims an @id belonging to a node Glotto derived from your spec, is reported by glotto generate (GLOTTO_CONFIG_DOCS_STRUCTURED_DATA) and left out; your other nodes still emit. This is a structural check, not a vocabulary one — a misspelled @type is emitted as you wrote it. The merge is additive — your nodes follow the derived ones and cannot replace, reorder, or remove them, so the graph Glotto guarantees stays the graph Glotto guarantees. Route keys are matched leniently (guides/quickstart, /guides/quickstart/ and /guides/quickstart are the same route), and a route that isn’t a generated page still gets its nodes, which is how you reach locale-prefixed routes. Set neither key and your site is byte-identical to the derived-only output; set enabled: false and the whole feature is off, injected nodes included.

deploy.custom_domain is the bare hostname — docs.acme.com, not a URL, and no path, port, or wildcard — because it becomes both your canonical URLs’ authority and the domain handed to your deploy provider (GLOTTO_CONFIG_DOCS_DEPLOY_DOMAIN). Each versions[].slug becomes a /<slug>/ route, so it may only use characters a URL path keeps verbatim (GLOTTO_CONFIG_DOCS_VERSIONS). Colours under og_images.template.colors are painted by the card renderer rather than a browser, so hex, comma-separated rgb()/rgba(), and CSS named colours work but hsl() and gradients do not (GLOTTO_CONFIG_DOCS_OG_TEMPLATE). glotto generate reports each of these.

Each versions entry declares its snapshot with exactly one of openapi, asyncapi, or graphql — the same three input keys the top level accepts, in the same shapes, so a GraphQL snapshot carries its own operations document:

docs:
  versions:
    - slug: v1
      graphql: { source: ./v1.graphql, operations: ./v1-operations.graphql }
    - slug: v2
      default: true
      openapi: { source: ./v2.yaml }

Versions may mix input kinds — the case a migration produces — and each version’s reference pages are built from its own snapshot. Only an openapi version co-serves an openapi.json (and so contributes a service-desc link to the site’s API catalog); the others simply serve none. Versioning is docs-only: your SDKs always come from the top-level source. analytics activates the site’s built-in cookieless analytics against your own project — PostHog by default (posthog_key is the publishable client key; optional posthog_host selects EU cloud or self-hosted ingestion) or Plausible via provider: plausible + plausible_domain (optional plausible_host for self-hosted). It counts page views plus three product events — search queries, feedback votes, and playground calls — with no cookies, Do-Not-Track honored, and nothing sent beyond each event’s named fields (never feedback comment text, parameter values, or credentials). i18n declares the site’s locales: default_locale sets the <html lang> and stays at the unprefixed routes, additional locales get /<locale>/ route prefixes for your translated pages plus a generated chrome string catalog to translate (src/lib/ui-strings.mjs).

license

license: MIT    # or Apache-2.0

The permissive license stamped onto generated SDKs and emitted templates. Defaults to MIT.

release

release:
  mode: auto    # or manual

Controls the release flow for generated SDKs. mode is the only key this block accepts. Any other is a configuration error reported against your glotto.yml before anything is generated, so a file naming branches here fails at the door rather than being silently ignored.

Absent, releases behave as auto: Glotto opens the release pull request for you, and merging it publishes. manual stops Glotto opening that pull request — you open it yourself from the branch Glotto has already pushed. Publishing on merge is an auto-only behaviour: the release webhook does not act on a repository whose project is in manual mode.

The branch names are not a glotto.yml key. To move one of them, use targets.<lang>.release, which takes branch, baseline_branch and base_branch per target:

targets:
  typescript:
    repo: acme/acme-typescript
    release:
      branch: sdk-next               # the release PR's head — the merged result
      baseline_branch: sdk-generated # the pristine generator output (the merge base)
      base_branch: trunk             # the release PR's base — released code

Unset, each defaults to Glotto’s generatednextmain vocabulary.

A project-level set of the same three names exists, but it lives in the project’s control-plane configuration (PUT /v1/projects/:id/config) rather than in your repository’s glotto.yml. Setting them once for every target from glotto.yml is not supported yet.

One of those three carries a second meaning worth knowing before you set it: the project-level base_branch is both the branch Glotto watches on your spec repository and the base of the pull request opened on a split-layout target. Moving it changes which pushes trigger a release and where the release lands — set it when a repository’s trunk is not called main, not to retarget one of the two.

The staging repositories Glotto hosts are not affected by any of these names. Their three branches are created with the repository and are always generated, next and main; every build pushes its pristine output there whatever your targets are called.

readme

readme:
  example_requests:
    headline: listPets           # operationId fronting the README quickstart
    pagination: listInvoices     # operationId fronting the Pagination section
    streaming: streamEvents      # operationId fronting the Streaming section
    # or, with example-value overrides (wire parameter names):
    # headline:
    #   operation: updatePet
    #   params: { petId: p_42, notify: true }

Configures the README.md shipped inside each generated SDK — every SDK language target ships one. example_requests.headline selects the operation fronting the README’s usage example by operationId — as a plain string, or as { operation, params } where params overrides the example values rendered in the quickstart, keyed by wire parameter name (path and query parameters and top-level request-body fields). example_requests.pagination and example_requests.streaming select, in the same two forms, the operations fronting the README’s Pagination and Streaming sections (the named operation must actually paginate / stream; a slot’s params apply only to its own section’s example). Absent slots default to the first matching operation, with example values sampled from the spec’s example/default fields. glotto generate warns when an operation or a params key doesn’t match the spec, or when a pagination/streaming operation isn’t of that kind. The README’s code blocks are the same generated snippets the docs site renders, so they always match the current client surface.

hosted

hosted:
  project: prj_9f2c81

Binds the repository to its Glotto control-plane project. Hosted mode resolves the project as --project, then GLOTTO_PROJECT_ID, then this committed value — so a checkout with only the API token in the environment can publish check-results to the hosted run. The project id is not a secret; credentials (GLOTTO_API_TOKEN) and the API URL stay environment-only and have no config key. Optional and additive, and ignored by generation entirely.