> For the complete documentation index, see [llms.txt](/llms.txt)

# 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](/docs/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`:

```yaml
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:

```json
{
  "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:

```json
{
  "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](/docs/drift-detection) 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.
