> Agent-readable docs index: /llms.txt. Download /docs.zip to grep all markdown files locally.

---
title: OpenAPI Setup
description: Generate API reference pages from an OpenAPI spec.
icon: plug
---

# OpenAPI Setup

Holocron can generate API reference pages from an OpenAPI specification file. Point a tab at your spec and Holocron creates a page per endpoint, grouped by tag.

## Basic setup

Add an OpenAPI tab to your `docs.json` navigation:

```json
{
  "navigation": {
    "tabs": [
      {
        "tab": "Documentation",
        "groups": [
          { "group": "Getting Started", "pages": ["index"] }
        ]
      },
      {
        "tab": "API Reference",
        "openapi": "openapi.json"
      }
    ]
  }
}
```

Place your `openapi.json` (or `openapi.yaml`) file at the project root. If you set `pagesDir`, Holocron checks `pagesDir` first, then falls back to the project root.

## Multiple specs

Pass an array to combine multiple spec files:

```json
{
  "tab": "API Reference",
  "openapi": ["openapi/v1.json", "openapi/v2.json"]
}
```

## Custom base path

By default, generated pages appear under `/api/` (e.g. `/api/get-users`). Change the prefix with `base`:

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

Now endpoints render at `/reference/get-users` instead.

A **leading slash is optional**: `"/reference"` and `"reference"` behave the same. Set `base` to `""` for no prefix.

<Aside>
  <Tip>
    When mounting docs inside your own app via [custom entry](/docs/custom-entry), set `base` to a `docs/`-prefixed slug like `/docs/api` so generated endpoints stay under `/docs/*` and don't collide with your real `/api` routes.
  </Tip>
</Aside>

## Mixing guides with endpoint pages

The basic setup above is **dedicated mode**: every endpoint is auto-grouped by tag. If you instead want to interleave hand-written pages (authentication, getting your API key, an overview) with specific endpoints, add a `groups` or `pages` array to the tab. This is **selective mode**.

In selective mode, each page entry is one of:

* a normal MDX slug like `guide/authentication` → renders that MDX file
* an endpoint reference like `POST /users` → renders the auto-generated endpoint page from the spec

```json
{
  "tab": "API Reference",
  "openapi": "openapi.json",
  "groups": [
    {
      "group": "Getting Started",
      "pages": [
        "api/overview",
        "api/authentication",
        "POST /auth/login",
        "GET /users"
      ]
    },
    {
      "group": "Orders",
      "pages": ["api/orders-intro", "POST /orders"]
    }
  ]
}
```

The sidebar follows the exact order you write, so your authentication guide can sit right before the endpoints it explains:

```
API Reference
├─ Getting Started
│   ├─ Overview              ← api/overview.mdx
│   ├─ Authentication        ← api/authentication.mdx
│   ├─ POST /auth/login      ← generated endpoint page
│   └─ GET /users            ← generated endpoint page
└─ Orders
    ├─ Working with Orders   ← api/orders-intro.mdx
    └─ POST /orders          ← generated endpoint page
```

An endpoint reference matches the form `METHOD /path` (case-insensitive method). To pick a specific spec when you list several, prefix it with the file name: `"v2.json POST /orders"`.

If an endpoint reference does not match any operation in the spec, the build fails with an error so typos surface immediately.

### Intro page, then all endpoints

Listing every endpoint by hand is tedious. Add the special `"..."` entry to write a short intro and then auto-include all the remaining endpoints, grouped by tag, right after it:

```json
{
  "tab": "API Reference",
  "openapi": "openapi.json",
  "pages": ["api/authentication", "..."]
}
```

This renders your `api/authentication` guide first, then expands every endpoint not listed elsewhere into **top-level tag groups** (always-visible sidebar sections, exactly like dedicated mode):

```
API Reference
├─ Authentication        ← api/authentication.mdx
├─ Users                 ← top-level group from "..." (tag: users)
│   ├─ GET  /users
│   └─ POST /users
└─ Orders                ← top-level group from "..." (tag: orders)
    └─ POST /orders
```

The tag groups are hoisted to the tab's top level so they render as separate sidebar sections, not collapsed sub-groups nested under your intro pages.

The `"..."` entry only expands endpoints you did **not** already reference explicitly, so you can pin a few important endpoints up top and let the rest fall in afterward. Only one `"..."` is allowed per tab.

## Controlling which routes appear in the spec

Holocron documents every operation in your OpenAPI spec. If your API has **internal routes** (admin endpoints, webhook receivers, internal service calls) that should not appear in the public docs, exclude them from the spec itself rather than trying to filter them in `docs.json`.

Most frameworks let you mark routes as hidden at the route definition level so they are omitted from the generated spec.

### Spiceflow

Add `hide: true` to the route's `detail` object:

```ts
import { Spiceflow } from 'spiceflow'
import { openapi } from 'spiceflow/openapi'
import { z } from 'zod'

const app = new Spiceflow()
  .use(openapi({ path: '/openapi.json' }))

  // This route appears in the spec
  .route({
    method: 'GET',
    path: '/api/v0/projects',
    detail: {
      summary: 'List projects',
      tags: ['Projects'],
    },
    response: { 200: z.object({ projects: z.array(z.string()) }) },
    async handler() {
      return { projects: [] }
    },
  })

  // This route is hidden from the spec
  .route({
    method: 'POST',
    path: '/api/v0/keys',
    detail: {
      hide: true,
      summary: 'Create API key',
      tags: ['Internal'],
    },
    request: z.object({ name: z.string() }),
    async handler() {
      return { id: '123' }
    },
  })
```

<Aside>
  <Tip>
    A good rule of thumb: if a route requires **session auth** or **internal tokens** that your API consumers never have, hide it from the spec. Only document routes that work with the auth method your users actually use (e.g. API keys).
  </Tip>
</Aside>

### Other frameworks

Most OpenAPI generators support similar exclusion mechanisms:

* **Hono** (`@hono/zod-openapi`): omit the route from the OpenAPI app, or don't register it with `createRoute`
* **FastAPI**: set `include_in_schema=False` on the route decorator
* **Express** (`swagger-jsdoc`): omit the JSDoc `@openapi` annotation from the route

The key idea is the same: your OpenAPI spec should only contain routes your API consumers can actually call. Internal, admin, or webhook routes belong in a separate spec or no spec at all.

## How pages are generated

In **dedicated mode**, Holocron reads the spec and creates one page per operation (path + method). Pages are grouped by the `tags` field on each operation. The sidebar shows tag groups with individual endpoint pages.

Each generated page shows:

* HTTP method and path
* Description from the spec
* Request parameters, headers, and body schema
* Response schemas with status codes
