7 min read
stateanchor.yaml Reference
The stateanchor.yaml file is the single source of truth for your API. It declares your service identity, endpoints, data models, authentication, and output configuration. StateAnchor parses this file on every push and derives everything else from it.
Place this file in the root of your repository. The filename must be exactly stateanchor.yaml. Maximum file size: 128 KB. YAML anchors and aliases are rejected.
Don’t want to write it by hand? Use the YAML scaffolder to generate a starter file from a plain-English description of your API.
Complete example
service: payments-api
version: "2.4.0"
info:
title: Acme Payments API
description: Payment processing for the Acme platform
server:
base_url: https://api.acme.com/v2
auth:
type: bearer
models:
Charge:
id: uuid
amount: integer
currency: string
status: string
created_at: datetime
Customer:
id: uuid
email: string
name: string
endpoints:
- name: createCharge
method: POST
path: /charges
description: Create a new payment charge
request_body: Charge
responses:
- status: 201
description: Charge created
- name: getCharge
method: GET
path: /charges/{id}
description: Get a charge by ID
parameters:
- name: id
in: path
type: uuid
required: true
- name: listCharges
method: GET
path: /charges
description: List all charges with pagination
parameters:
- name: limit
in: query
type: integer
- name: offset
in: query
type: integer
- name: getHealth
method: GET
path: /health
description: Health check endpoint
auth: none
outputs:
languages:
- typescript
- python
mcp: true
docs: true
openapi: trueTop-level fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
service | string | Yes | -- | Service name. Max 100 chars. Used in generated class names and artifact metadata. Alphanumeric, hyphens, underscores only. |
version | string | Yes | -- | Spec version. Max 20 chars. Quote it to prevent YAML number coercion. Semantic versioning recommended. |
info | object | No | -- | Optional metadata block. Contains title and description. |
server | object | Yes | -- | Server configuration. Must include base_url. |
models | object | No | -- | Shared data models referenced by endpoints. Keys are model names, values are field maps. |
endpoints | array | Yes | -- | List of API endpoints. Max 50 per spec. At least one required. |
outputs | object | No | -- | Which artifacts to generate. Omit or set to [] for gate-only mode. In full pipeline mode, must enable at least one language, mcp, docs, or openapi. |
service
The service name identifies your API across StateAnchor. It appears in generated SDK class names, MCP server tool prefixes, and artifact metadata.
service: payments-apiConstraints: string, max 100 characters, alphanumeric plus hyphens and underscores. Must be unique within your StateAnchor account.
version
The spec version. StateAnchor tracks version changes across sync runs and includes the version in generated artifact provenance metadata.
version: "2.4.0"Always quote the version string. Without quotes, YAML may interpret 1.0 as a float. Max 20 characters.
info
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
info.title | string | No | -- | Human-readable API title. Max 200 chars. Used in generated documentation headers. |
info.description | string | No | -- | API description. Max 2000 chars. Used in generated documentation. |
server
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
server.base_url | string | Yes | -- | Base URL for the API. Must include the scheme (https://). Max 500 chars. Used in generated SDK base configuration and live API scanner. |
server.auth.type | enum | No | none | Default authentication type for all endpoints. One of: bearer, api_key, basic, oauth2, none. |
server:
base_url: https://api.acme.com/v2
auth:
type: bearerThe base_url is used as the default host in generated SDKs and as the target for the live API scanner when drift detection runs.
models
Models define shared data structures that endpoints reference. Each model is a named object whose keys are field names and values are type strings.
| Field type | Description |
|---|---|
string | Text value |
integer | Whole number |
number | Floating-point number |
boolean | True or false |
uuid | UUID string |
datetime | ISO 8601 timestamp |
object | Nested object |
array | Array of values |
models:
Charge:
id: uuid
amount: integer
currency: string
status: string
created_at: datetime
metadata: objectModels are referenced by name in endpoint request_body and responses fields. The gate engine tracks model changes -- removing a field from a model triggers a field_removed ERR finding. See the gate kinds reference for all 33 change kinds with examples.
endpoints
Array of endpoint objects. Each endpoint must have a unique name. The maximum number of endpoints you can track depends on your plan.
| Tier | Endpoint limit per project |
|---|---|
| Free | up to 50 endpoints |
| Pro | up to 500 endpoints |
| Team | unlimited |
Endpoints are counted from the endpoints array above. Projects with more endpoints than the tier limit will have the endpoints beyond the limit ignored by the gate. Raise the limit by upgrading the project's plan.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | -- | Unique endpoint identifier. Max 100 chars. Becomes the method name in generated SDKs. |
method | enum | Yes | -- | HTTP method: GET, POST, PUT, PATCH, DELETE. |
path | string | Yes | -- | URL path relative to base_url. Must start with /. Max 200 chars. Use {param} for path parameters. |
description | string | No | -- | Endpoint description. Max 500 chars. Used in generated documentation and SDK JSDoc/docstrings. |
auth | enum | No | Inherits from server | Per-endpoint auth override. Same values as server.auth.type. Set to none for public endpoints. |
parameters | array | No | [] | Query and path parameters. Each has name, in (query/path), type, and optional required flag. |
request_body | string | No | -- | Reference to a model name for the request body. The model must be defined in the models section. |
responses | array | No | -- | Array of response objects with status code, description, and optional schema reference. |
Parameter fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Parameter name. |
in | enum | No | query or path. Defaults to query. |
type | string | No | Parameter type (string, integer, uuid, etc.). |
required | boolean | No | Whether the parameter is required. Defaults to false for query, true for path. |
Response fields
| Field | Type | Required | Description |
|---|---|---|---|
status | integer | Yes | HTTP status code (200, 201, 400, etc.). |
description | string | No | Response description. |
schema | string | No | Reference to a model name. Use model name for single object, model name + [] for array. |
Endpoint example
endpoints:
- name: listCharges
method: GET
path: /charges
description: List all charges with pagination
auth: bearer
parameters:
- name: limit
in: query
type: integer
- name: offset
in: query
type: integer
responses:
- status: 200
description: List of charges
schema: Charge[]
- name: getHealth
method: GET
path: /health
description: Health check
auth: none
responses:
- status: 200
description: OKoutputs
Controls which artifacts StateAnchor generates on each sync run. Omit or set to [] for gate-only mode. In full pipeline mode, at least one output must be configured.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
outputs.languages | array of strings | No | [] | SDK languages to generate. Valid values: typescript, python, go, rust, php. |
outputs.mcp | boolean | No | false | Generate an MCP (Model Context Protocol) server. The MCP server is always generated in TypeScript. |
outputs.docs | boolean | No | false | Generate api-docs.md Markdown documentation from the IR. Deterministic -- no LLM call. |
outputs.openapi | boolean | No | false | Generate openapi.json OpenAPI 3.1 spec from the IR. Deterministic -- no LLM call. |
# Gate-only mode -- no outputs
outputs: []
# Or omit the outputs section entirely
# (gate enforces the contract; no artifacts generated)
# Minimal -- one language
outputs:
languages:
- typescript
# Full -- all output types
outputs:
languages:
- typescript
- python
- go
mcp: true
docs: true
openapi: trueAll configured outputs are generated in a single sync run, in parallel. Each output type produces a separate artifact with its own SHA-256 hash and provenance record.
docs and openapi are available in full pipeline mode only. In gate-only mode, set outputs: [] or omit the outputs section entirely.
exceptions (coming soon)
Declare drift exceptions inline in stateanchor.yaml. Exceptions suppress a specific finding — not the whole gate — for a bounded window. Until the YAML block lands, exceptions are created through the dashboard Exceptions page; the API path is POST /api/v1/projects/{projectId}/drift-exceptions.
| Field | Type | Required | Description |
|---|---|---|---|
change_type | string | Yes | ERR change kind from the gate classification table (e.g., endpoint_removed, field_removed, type_changed). |
endpoint | string | Yes | Affected endpoint path, matching the spec key (e.g., GET /users/{id} or a path string like /users/{id}). |
reason | string | Yes | Human-readable justification, visible to your team in the exception ledger. |
expires | date (YYYY-MM-DD) | No | Optional expiry. Maximum 90 days from creation. Omit to inherit the project default. |
exceptions:
- change_type: endpoint_removed
endpoint: /users/{id}
reason: "Deprecated endpoint removed per v2 migration plan"
expires: 2026-07-01
- change_type: field_removed
endpoint: GET /orders
reason: "Legacy 'tax' field unused by any consumer per audit #42"
expires: 2026-05-15When an exception expires, the suppressed finding re-activates and the gate may block again on the next push. Exceptions apply to exact endpoint + change_type pairs; a new breaking change on the same endpoint is not suppressed.
merkle_log (coming soon)
Controls how this project’s sync runs are recorded in the public Merkle log. Verdict and timestamp are always recorded; only the commit SHA is optional.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
merkle_log.private | boolean | No | false | Omit the commit SHA from public Merkle log entries. Verdict and timestamp are still recorded. Set to true for private repos where you do not want SHAs surfacing in the public log. |
merkle_log:
private: true # Omit commit SHA from public Merkle log entries.
# Verdict and timestamp are always recorded.Until the YAML block lands, email support@stateanchor.dev to enable private-mode logging for a project. See the Trust page for the overall Merkle log model.
Validation rules
The following constraints are enforced during Stage A (spec validation). Violations fail the sync run immediately.
- File size must not exceed 128 KB.
- YAML anchors and aliases are rejected.
serviceandversionare required strings.server.base_urlis required and must start withhttps://.- At least one endpoint is required.
- Each endpoint must have
name,method, andpath. methodmust be one of: GET, POST, PUT, PATCH, DELETE.- Endpoint
pathmust start with/. - Endpoint
namemust be unique within the spec. - Endpoints beyond the tier limit are ignored by the gate — 50 for Free, 500 for Pro, unlimited for Team. See endpoints for details.
- In full pipeline mode, at least one output must be configured. In gate-only mode,
outputs: []or omittingoutputsis valid. - Field types must be one of: string, integer, number, boolean, uuid, datetime, object, array.
Common patterns
Public + authenticated endpoints
Set the default auth at the server level, then override with auth: none on public endpoints like health checks.
server:
auth:
type: bearer
endpoints:
- name: getHealth
method: GET
path: /health
auth: none # public
- name: listUsers
method: GET
path: /users # inherits bearer from serverPath parameters
Use {param} syntax in the path. Path parameters are automatically extracted into generated SDK method signatures.
- name: getUser
method: GET
path: /users/{id}
parameters:
- name: id
in: path
type: uuid
required: trueRequest body with model reference
models:
CreateUserInput:
email: string
name: string
endpoints:
- name: createUser
method: POST
path: /users
request_body: CreateUserInputAvoiding drift-prone patterns
Four recurring spec patterns account for the majority of future gate blocks. Avoid them at spec-write time and your project ships with a lower Drift Pressure Index from day one.
1. Enum fields on critical endpoints
Enums are the single highest-leverage source of breaking changes. Every value you remove is an ERR-lane enum_value_removed; every value consumers depend on is another constraint on future evolution. Prefer open string with documented expected values on fields that may expand -- reserve real enums for values the server actually dispatches on.
2. Required response fields
Every required response field is a contract to emit that field on every response, forever. If you later move the field into an object, split it across shapes, or remove it, you trigger response_field_removed (ERR). Mark fields optional unless the consumer cannot interpret the response without them.
3. Narrow validation constraints
Tight min, max, minLength, maxLength, or regex bounds feel safe at write time but accumulate as drift. Loosening them is INFO (constraints_relaxed); tightening them later is ERR (validation_constraints_tightened). Start with the widest bound that matches your business rule, not the narrowest you can imagine.
4. oneOf / anyOf unions without discriminators
Any modification to a union shape (add / remove / rewire variant) fires oneOf_anyOf_modified (ERR). Unions are rarely worth the evolvability cost. Prefer a tagged object with an explicit type discriminator, or a flat schema with optional fields that represent the variants.
StateAnchor surfaces these four patterns on the project dashboard as an evolvability signal. Specs that avoid them stay in the Low pressure (green) DPI band longer.
Known limitations
oneOf / anyOf granularity
oneOf / anyOf compositions are classified as a single compound change kind (oneOf_anyOf_modified). StateAnchor does not yet distinguish variant_added (lower severity) from variant_removed (higher severity) -- every union change lands in the ERR lane today. Full OpenAPI 3.1 oneOf/anyOf decomposition is on the roadmap.
Workaround: use a discriminator field to structure your composition. A tagged object with an explicit type (or similar) discriminator lets the diff engine reason about each variant independently, so adding a new variant does not read as a destructive change.