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

The API-surface graph

Your spec already describes an entity graph — resources contain operations, operations accept and return models, models reference other models, auth schemes gate calls, endpoints stream events. But nothing in an OpenAPI document lets a tool traverse it. Answering “which operations return a Charge?”, “what events does this endpoint stream?”, or “which scopes gate this resource?” means reading generated code or prose.

The graph target projects the Glotto IR into a single machine-readable document — graph.json — that answers those questions directly. It is a derivation, not a description: an edge exists if and only if your spec says so, which is what makes it safe to hand to an agent. There is nothing in it to hallucinate.

Enable it in glotto.yml:

targets:
  graph: {}

glotto generate then writes <out>/graph/graph.json. The target takes no options — the graph is selected or it isn’t.

The envelope

Every artifact carries a format discriminator and a vocabulary version, so a consumer can refuse a document it doesn’t understand rather than mis-reading one:

{
  "format": "glotto-surface-graph",
  "version": 1,
  "nodes": [],
  "edges": []
}

version is bumped when the node/edge vocabulary changes in a breaking way; additive changes keep it stable.

Nodes

Every node carries a stable <kind>:<name> id, so a reference to it survives regeneration as long as the underlying entity keeps its name:

Kind One per Notable attributes
resource resource, recursively — subresources get dotted names
operation resource method and hoisted client method http_method, path, summary, and the pagination / streaming / polling / idempotency strategies when the operation declares them
model named model type
security_scheme declared scheme — plus a synthesized default when your API has a single scheme rather than a registry scheme
environment declared environment url
tag operation tag
channel AsyncAPI channel

Edges

Edges are typed, and each carries the attribute that makes it specific — which status returned the model, which content type accepted it, which scopes the call requires.

Structure. has_subresource (resource → resource), has_operation (resource → operation), has_model (resource → model, with the accessor name), and tagged (operation → tag).

Data flow. accepts (operation → request-body model, with content_type), returns (operation → model, one per response status, with status, resolved transitively through inline schemas), and references (parameter schema → model, with the parameter name).

Auth. secured_by (operation → security scheme) resolves exactly the way the generated SDKs do — a method’s own security if it declares one, otherwise the client default — and carries the required scopes. An operation your spec marks public (security: []) has no edge, which is itself the answer to “what can be called unauthenticated?”.

Events and pagination. streams (operation → event model, one per declared event type, with the wire event value) and paginates_over (operation → the item model your pages are made of).

Models. references (model → model, for refs reachable through fields, items, values and union members) and has_variant (union → member model, with the discriminator’s wire_value when your spec maps one). AsyncAPI channels contribute sends and receives edges to their message models.

A worked example

A two-operation petstore, with a bearer-authenticated list and create over one Pet model, emits:

{
  "format": "glotto-surface-graph",
  "version": 1,
  "nodes": [
    { "id": "environment:production", "kind": "environment", "name": "production",
      "attributes": { "url": "https://api.example.com" } },
    { "id": "model:Pet", "kind": "model", "name": "Pet", "attributes": { "type": "object" } },
    { "id": "operation:pets.createPet", "kind": "operation", "name": "pets.createPet",
      "attributes": { "http_method": "post", "path": "/pets", "summary": "Create a pet" } },
    { "id": "operation:pets.listPets", "kind": "operation", "name": "pets.listPets",
      "attributes": { "http_method": "get", "path": "/pets", "summary": "List pets" } },
    { "id": "resource:pets", "kind": "resource", "name": "pets" },
    { "id": "security_scheme:default", "kind": "security_scheme", "name": "default",
      "attributes": { "scheme": "bearer" } },
    { "id": "tag:pets", "kind": "tag", "name": "pets" }
  ],
  "edges": [
    { "kind": "accepts", "from": "operation:pets.createPet", "to": "model:Pet",
      "attributes": { "content_type": "application/json" } },
    { "kind": "has_model", "from": "resource:pets", "to": "model:Pet",
      "attributes": { "accessor": "Pet" } },
    { "kind": "has_operation", "from": "resource:pets", "to": "operation:pets.createPet" },
    { "kind": "has_operation", "from": "resource:pets", "to": "operation:pets.listPets" },
    { "kind": "returns", "from": "operation:pets.createPet", "to": "model:Pet",
      "attributes": { "status": "201" } },
    { "kind": "returns", "from": "operation:pets.listPets", "to": "model:Pet",
      "attributes": { "status": "200" } },
    { "kind": "secured_by", "from": "operation:pets.createPet", "to": "security_scheme:default" },
    { "kind": "secured_by", "from": "operation:pets.listPets", "to": "security_scheme:default" }
  ]
}

“Which operations return a Pet?” is every returns edge whose to is model:Pet. “What does creating a pet require?” is the accepts edge plus the secured_by edge. Multi-hop questions — “which resources expose an operation that streams an event carrying this model?” — are a walk, not a search.

What it’s for

The artifact is deliberately a plain document with no query engine, so anything that reads JSON can consume it:

  • Agents and MCP servers — ground a tool-using model in what your API actually exposes, with no chance of it inventing a field, a scope, or an endpoint that doesn’t exist.
  • Internal tooling — impact analysis (“what breaks if this model changes?”), coverage reports, API-surface review at a scale nobody eyeballs.
  • Retrieval — answer support and docs questions by traversing relations rather than matching text.

Kept true, not just generated

Like every Glotto artifact, the graph is a deterministic projection of your spec: nodes sorted by id, edges by a total order, duplicate edges collapsed, byte-stable across repeated emission and locked by golden tests. It is regenerated in lockstep with your SDKs, docs site and MCP server — from the same IR, so it cannot describe a surface they don’t implement — and the drift gate proves the committed copy never lags your spec.

That is the point. Drawing a graph of an API once is easy; the hard part, and the part that decays, is keeping it true forever. Add an operation, tighten a scope, add an event type — the graph is provably current on the next regeneration.