Tangly v0.2 ships richer code blocks, page chrome, and more — see what's new

OpenAPI

Auto-generated API reference pages from an OpenAPI spec.

~ 3 min read

OpenAPI

Point Tangly at an OpenAPI spec; every endpoint becomes a page.

Top-level setup

In docs.json:

json
{
  "api": {
    "openapi": "https://api.example.com/openapi.json",
    "viewer": "tangly",
    "auth":   { "method": "bearer", "name": "Authorization" }
  }
}

Tangly fetches the spec at build time. openapi accepts a URL, a local path (relative to project root), or an array of either.

Dev override

Set TANGLY_OPENAPI_URL to point Tangly at a different spec without editing docs.json. Useful for rendering docs against a dev/staging API while keeping prod config untouched.

bash
TANGLY_OPENAPI_URL=https://api.dev.example.com/openapi.json tangly dev

Or drop it in .env.local at the project root. tangly dev, tangly build, and tangly preview load .env.local then .env on startup and log the files they pick up. Shell-exported values still win over file values.

bash
# .env.local (gitignored)
TANGLY_OPENAPI_URL=https://api.dev.example.com/openapi.json

With taskmux, add it to the task env in taskmux.toml:

toml
[tasks.docs]
env = { TANGLY_OPENAPI_URL = "https://api.dev.example.com/openapi.json" }

Rules:

  • Unset → fallback to api.openapi in docs.json. Prod stays unchanged.
  • Global: replaces every OpenAPI source in the manifest (top-level + per-tab) with the override.
  • Mode-agnostic: applies to dev, preview, and build if the var is exported. Don’t set it in prod CI unless you mean to.
  • Tangly logs [tangly] OpenAPI source overridden via TANGLY_OPENAPI_URL=<url> on startup so the override is never silent.

Viewer choice

viewer selects the rendering engine:

ValueRender
tangly (default)Built-in compact endpoint render. Server-fetched at build time.
scalarScalar viewer, lazy-loaded from CDN.
redocReDoc viewer, lazy-loaded from CDN.
stoplightStoplight Elements, lazy-loaded from CDN.

The default tangly renderer is fastest and works without client JS. The third-party viewers add interactivity but pull bundles from CDN at runtime.

Per-endpoint pages

A page with openapi: in frontmatter auto-generates the endpoint:

md
---
title: Get network data
openapi: get /v4/data/network/{network_code}
---

The page switches to the split layout (docs left, sticky playground panel right) and renders:

  • HTTP method badge + path (color-matched to the sidebar pill)
  • Summary + description from the operation
  • Path / Query / Header / Cookie parameter sections (each row expandable)
  • Request body schema tree
  • Tabbed responses by status code, with the response schema per status
  • Right-rail panel with language tabs (cURL, TypeScript, Python by default), Send → button, and live response preview

Right-rail playground panel

Every openapi: / api: page gets a sticky panel on the right at xl+ widths. Default tabs:

TabHow it’s built
cURLGenerated from the operation. Uses --request, embeds path/query example values, includes the configured auth header, and any application/json example body.
TypeScriptawait fetch(...) snippet.
Pythonrequests.<method>(...) with explicit headers= / params=.

The set is configurable:

json
{
  "api": {
    "codeSamples": {
      "languages": ["curl", "typescript", "python", "go"],
      "prefill": true,
      "defaults": "required",
      "autogenerate": true
    }
  }
}
FieldMeaning
languagesTab order. Built-in generators: curl/bash/shell, typescript/ts, javascript/js, python/py, go. Unknown names render an “author via <RequestExample>” placeholder.
prefillPull example values from spec parameters into the snippet (default true).
defaultsrequired (only required params, default) or all.
autogenerateWhen false, only <RequestExample> overrides and x-codeSamples show.

Mintlify-flavored api.examples.* is accepted as an alias for api.codeSamples.* and normalized at parse time, so unmodified Mintlify projects keep working.

Per-endpoint code samples (<RequestExample>)

Override or add language samples by wrapping fenced code blocks in <RequestExample> inside the endpoint MDX:

mdx
---
openapi: get /v4/data/network/{network_code}
---

<RequestExample>
```ts
import { OpenElectricityClient } from "openelectricity";

const client = new OpenElectricityClient();
const { datatable } = await client.getNetworkData("NEM", ["energy"], {});
```

```python
from openelectricity import OEClient

client = OEClient()
client.get_network_data("NEM", ["energy"], {})
```
</RequestExample>

Match rule: each fenced block’s language token (ts, python, go, …) replaces the same-language panel tab. Languages not in api.codeSamples.languages are silently ignored. The inline <RequestExample> block hides itself once the panel picks up the override — no duplicate render.

<ResponseExample> mirrors the pattern for response previews.

Spec-side samples (x-codeSamples)

If your spec already carries x-codeSamples (or the legacy x-code-samples), Tangly picks them up automatically:

yaml
paths:
  /v4/data/network/{network_code}:
    get:
      x-codeSamples:
        - lang: typescript
          label: TypeScript SDK
          source: |
            import { OEClient } from "openelectricity";
            const c = new OEClient();
            await c.getNetworkData("NEM", ["energy"], {});

Precedence: <RequestExample> MDX overrides ▸ x-codeSamples ▸ autogen.

Hiding endpoints

  • x-hidden: true on an operation keeps the page reachable by URL but removes it from the sidebar.
  • x-excluded: true drops the page entirely.

Parameter pills

Surface custom OpenAPI fields as pills next to the parameter name:

json
{
  "api": {
    "params": {
      "expanded": "all",
      "post": ["readOnly", "deprecated"]
    }
  }
}

expanded: "all" opens every parameter row by default; post lists field keys to render as small pills (any truthy value on the parameter or its schema is surfaced).

Companion narrative

Add MDX content alongside the auto-rendered endpoint by setting api: instead of openapi::

md
---
title: Get network data
api: GET /v4/data/network/{network_code}
---

# Background

This endpoint returns time-series data sliced by network. Cache for at least 60s; backend regenerates the underlying view every minute.

## Common pitfalls

- Pass `interval=5m` for production dashboards.
- Time ranges over 30 days are rate-limited.

Tangly merges your narrative with the auto-generated endpoint render. Both stay in sync as the spec evolves.

Auto-generated tab

Set openapi on a tab node to materialize one page per endpoint:

json
{
  "tab": "API Reference",
  "openapi": "./openapi.json"
}

Tangly walks the spec, generates a synthetic PageEntry for each operation, and appends them to the tab’s sidebar grouped by tag.

Authentication

Configure auth at the top level so try-it-out forms include credentials:

json
{
  "api": {
    "auth": { "method": "bearer", "name": "Authorization" }
  }
}
methodBehavior
bearerAdds Authorization: Bearer <token> to requests. Token comes from a per-page input.
basicAdds Authorization: Basic <base64>.
keyAdds <name>: <value> header (or query param if the spec says so).
noneNo auth.

Multi-environment base URLs

json
{
  "api": {
    "baseUrl": ["https://api.example.com", "https://staging-api.example.com"]
  }
}

The try-it-out form gets a server selector.

Local specs

Local OpenAPI files are resolved relative to project root:

json
{ "api": { "openapi": "./openapi.json" } }

The Vite static-asset middleware doesn’t auto-serve OpenAPI files; they’re loaded server-side at build time, not via HTTP.

Validation

tangly check validates your spec via @apidevtools/swagger-parser:

bash
bun x tangly check --strict

Bad refs, missing schemas, malformed operations all surface as errors before you build.

Last updated Edit this page

Type to search…

↑↓ navigate open esc close