Mintlify Setup & Migration Guide for API Portals

Mintlify is a hosted documentation platform that turns an OpenAPI document and a set of MDX pages into a deployed developer portal. This guide is part of Developer Portal Frameworks & UI Setup, and it covers initializing a Mintlify project, binding an OpenAPI spec, configuring CI validation, and migrating legacy documentation with 301 redirects and a zero-downtime cutover. It does not cover self-hosting — Mintlify’s deployment model is its managed cloud, and that constraint shapes every decision below.

The hard parts of a Mintlify migration are not the editor or the theme. They are the redirect map that preserves your existing search rankings and inbound links, the spec-version mismatch that produces broken reference pages silently, and the navigation entries that must line up exactly with files on disk. Get those three right and the rest is configuration.

Mintlify migration and deploy flow Legacy docs are audited and redirected, the spec is bound in docs.json, CI checks links, and Mintlify Cloud deploys. Migration and deploy flow legacy docs audit URLs docs.json spec + redirects CI: broken-links pre-merge gate Mintlify Cloud deploy on push 301 redirects preserve inbound links

Quick reference: what transfers in a migration

Migration effort is entirely determined by which of these categories your existing docs fall into. Audit against this table before estimating:

Asset Transfers to Mintlify Effort
OpenAPI document Yes, referenced directly by path or URL None — it stays the source of truth
Plain Markdown prose Yes, renamed to .mdx Low — bulk rename, spot-check front matter
Admonitions / callouts No — dialect differs per platform Medium — scripted find-and-replace per pattern
Navigation / sidebar No — rebuilt in docs.json Medium — one entry per page, must match paths exactly
Custom React components Partially — only Mintlify’s component set High — rewrite or drop
Existing URLs Only via an explicit redirect map High — one entry per changed path, and it is not optional
Search index No — rebuilt by the platform None, but rankings need time to recover
Analytics / event wiring No — reconfigured in platform settings Low

The two rows that dominate the schedule are navigation and redirects. Everything else is either automatic or a scripted transformation; those two are page-by-page work whose size scales linearly with the portal you already have.

Prerequisites & Environment Setup

  • Node.js 18+ to run the Mintlify CLI locally.
  • A GitHub repository connected to Mintlify through the dashboard (this is how deployment is triggered).
  • An OpenAPI 3.0.x or 3.1.x document. Swagger 2.0 is not supported and must be converted.
npm install -g mint     # the CLI package was renamed from `mintlify` to `mint`
mint dev                # local preview at http://localhost:3000
mint --version

The recommended repository layout keeps the spec and config at the root so the cloud build finds them without extra configuration:

docs/
├─ docs.json            # single config file (was mint.json)
├─ quickstart.mdx
├─ reference/
│  └─ openapi.yaml      # bound spec
└─ logo.svg

If you are migrating from Swagger 2.0, convert once and commit the OpenAPI output rather than converting on every build:

npx swagger2openapi swagger.yaml -o reference/openapi.yaml

Core Configuration

docs.json is the single source of configuration: navigation, theming, the bound spec, and redirects. It must live at the repository root and be committed. Mintlify reads it on every build to construct the navigation tree and mount the OpenAPI reference at the path matching the openapi key. Each significant key is annotated inline.

{
  "$schema": "https://mintlify.com/docs.json",
  "name": "API Portal",
  "logo": { "light": "/logo.svg", "dark": "/logo-dark.svg" },
  "favicon": "/favicon.svg",
  "colors": {
    "primary": "#16306d",          // brand accent used for links and buttons
    "light": "#4c9aff",            // primary tint for dark mode
    "dark": "#0b1120"
  },
  "navigation": {
    "groups": [
      { "group": "Getting Started", "pages": ["quickstart"] },
      { "group": "API Reference", "pages": ["reference/openapi"] }
    ]
  },
  "openapi": "/reference/openapi.yaml",  // path is relative to repo root; mounts the reference
  "redirects": [
    {
      "source": "/v1/docs/:path*",       // glob with a wildcard segment
      "destination": "/reference/:path*",
      "permanent": true                   // emits a 301 so rankings transfer
    }
  ]
}

The legacy mint.json uses the same keys under a flatter shape (a top-level navigation array of { "group", "pages" } objects). When you run mint dev against a mint.json project, the CLI writes the equivalent docs.json. New work should target docs.json directly.

Every string in a pages array must correspond to an .mdx file at that path relative to the root. A mismatch produces a build error naming the missing page — this is the most frequent first-run failure.

Three things must agree exactly for a page to render: the file on disk, the navigation entry in docs.json, and the URL you publish. Mintlify does not infer any of them from the others, which is why “the page exists but 404s” is the most common first-week report. The navigation entry is the authority — a file with no entry is simply not part of the site, and an entry with no file is a build error:

The three things that must agree A page renders only when the file on disk, the navigation entry in docs.json and the published URL all describe the same path. all three must describe the same path file on disk api/invoices/list.mdx no extension in the entry docs.json entry "api/invoices/list" the authority published URL /api/invoices/list what links point at file without an entry → page is not in the site · entry without a file → build fails entry renamed without a redirect → every inbound link 404s

That last failure is the one worth automating against. A build that succeeds while silently orphaning fifty inbound links looks identical to a healthy build in CI, so add a check that diffs the published path list against the previous deploy and fails when a path disappears without a matching redirect entry.

Integration Pattern

Mintlify Cloud deploys automatically when you push to the connected branch, so the CI job’s purpose is validation, not deployment. Run a link check and a spec lint before any merge so broken navigation never reaches the cloud build. This mirrors the fail-fast validation used in the Docusaurus for API Portals pipeline.

# .github/workflows/docs-validate.yml
name: Validate Docs
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: Lint OpenAPI spec        # confirm version + validity before binding
        run: npx @redocly/cli@latest lint reference/openapi.yaml
      - name: Install Mintlify CLI
        run: npm install -g mint
      - name: Check internal links     # validates navigation against files on disk
        run: mint broken-links

Because Mintlify deploys on push, this validation job is the effective production gate: keep it required on the connected branch so a green check is a precondition for the cloud build picking up the change.

Advanced Options

Multiple specs and a version switcher

Declare one navigation group per spec, each with its own openapi reference, to host several API versions or several products side by side:

{
  "navigation": {
    "groups": [
      { "group": "API v2", "openapi": "/reference/v2.yaml", "pages": ["reference/v2/overview"] },
      { "group": "API v1", "openapi": "/reference/v1.yaml", "pages": ["reference/v1/overview"] }
    ]
  }
}

A polished version-switcher dropdown is not automatic; wire it through navigation tabs or anchors pointing at each group’s landing page.

Keeping the spec outside the docs repository

The most durable arrangement keeps the OpenAPI document in the repository that owns the API, and pulls it into the docs build rather than copying it in. A copy in the docs repo is a fork the moment someone edits it, and the edit is invisible — the reference renders fine, it just describes a slightly different API than the one running in production. Reference the spec by URL where the platform supports it, or fetch it in a pre-build step pinned to a released tag rather than to a branch:

# fetch-spec.sh — pull the released contract, never a moving branch
set -euo pipefail
VERSION="${API_SPEC_VERSION:?set API_SPEC_VERSION, e.g. v2.4.0}"
curl -fsSL \
  "https://raw.githubusercontent.com/acme/api/${VERSION}/openapi/api.yaml" \
  -o openapi/api.yaml
echo "Fetched contract ${VERSION}"

Pinning to a tag rather than main matters more than it looks. An unpinned fetch means your published documentation changes whenever someone merges to the API repository, including changes that have not shipped to production yet — so the portal starts documenting endpoints that do not exist. Bumping API_SPEC_VERSION as an explicit, reviewable commit keeps the published reference aligned with what is actually deployed, and gives you an obvious place to record which contract version each docs release describes.

Reusable MDX snippets

Factor repeated content — auth headers, rate-limit notes, status-code tables — into snippet components and import them across reference pages. This keeps the contract description consistent without duplicating prose, and pairs well with example reuse covered in Example Payload Management.

Theme parity for light and dark

Provide both logo.light and logo.dark and a colors.light tint so the reference reads correctly in both modes. The token discipline behind robust dual-theme portals is detailed in Multi-Theme & Dark Mode Support.

Cutover sequencing

The riskiest hour of a migration is the DNS switch, and most of that risk is avoidable by ordering the steps so that nothing is irreversible until the new portal has already been verified on a real hostname. Run the old and new portals in parallel on distinct hosts, validate the new one end to end, then move traffic — and keep the old origin alive behind the redirects for at least one full search-engine recrawl cycle:

Zero-downtime cutover sequence Parallel run, verification on a real host, DNS switch, redirects served from the old origin, and only then decommissioning. parallel run docs-next.example.com verify on a real host links, search, try-it DNS switch the only risky step old origin serves 301s keep for a recrawl cycle decommission everything before the amber step is reversible — do all verification there

Two details make the rollback real rather than theoretical. Lower the DNS TTL to a minute a day before the cutover, so reverting takes a minute rather than the previous TTL; and keep the old origin serving until analytics show inbound traffic to legacy URLs has dropped to near zero, because a 301 is only useful while something is still requesting the old path. A migration that deletes the old origin the same day trades a week of patience for permanently lost inbound links.

Verification & Testing

Validate locally before relying on the cloud build:

# 1. Confirm the spec parses and is the right version
npx @redocly/cli@latest lint reference/openapi.yaml   # expect no errors

# 2. Start the local preview and watch for navigation/page errors in the console
mint dev                                              # http://localhost:3000

# 3. Check that every navigation entry resolves to a real file and redirect target
mint broken-links                                     # expect "No broken links found"

A clean mint broken-links run plus a mint dev session with no missing-page warnings in the terminal means the build will succeed in the cloud. After cutover, request a handful of old URLs with curl -I and confirm each returns HTTP/1.1 301 pointing at the new path.

Troubleshooting

OpenAPI spec version mismatch — Mintlify requires OpenAPI 3.0.x or 3.1.x; a Swagger 2.0 document fails silently or renders broken reference pages. Run npx @redocly/cli lint reference/openapi.yaml to confirm the openapi: version field and overall validity, and convert with swagger2openapi before binding.

Navigation page not found at build — A string in a pages array has no matching .mdx file at that path relative to the root. The build error names the missing page; either create the file or correct the entry. Paths are case-sensitive on the cloud build even if they resolved on macOS.

Broken internal links after migration — Relative Markdown links that worked in a flat directory break under Mintlify’s nested navigation. Replace file-system links with the slugs declared in docs.json navigation entries, then re-run mint broken-links.

Old URLs return 404 after cutover — A redirect is missing from the redirects array or used "permanent": false (a 302 that does not transfer ranking). Audit the old site’s URL list, add a "permanent": true entry for every changed path, and verify each with curl -I before pointing DNS at the new portal.

Building the redirect map without guessing

Do not write the redirect map by hand from memory of the old site. Derive it from data, because the URLs that matter are not the ones you remember publishing — they are the ones people actually reach. Three sources, in priority order:

  1. The old sitemap. Every URL the site claimed to publish. This is your completeness check: every entry must appear either as a live path on the new portal or as a redirect source.
  2. Analytics, last 12 months. Sort by sessions. The top 50 URLs account for most inbound traffic, and any of them without a redirect is a visible regression on day one.
  3. Search Console external links. URLs other sites link to, which analytics under-reports because a broken link produces no session at all.

Union the three, subtract the paths that are unchanged, and what remains is the map. Then assert it:

# verify-redirects.sh — every legacy URL must answer 301 and land somewhere that exists
set -euo pipefail
while read -r old; do
  code=$(curl -s -o /dev/null -w '%{http_code}' "https://docs.example.com${old}")
  final=$(curl -sL -o /dev/null -w '%{http_code}' "https://docs.example.com${old}")
  if [ "$code" != "301" ] || [ "$final" != "200" ]; then
    echo "BROKEN ${old} (first=${code} final=${final})" >&2
    exit 1
  fi
done < legacy-urls.txt
echo "All legacy URLs redirect to a live page."

Run it against the parallel host before the DNS switch and again immediately after. The two-stage check — first response is a 301, final response is a 200 — catches both the missing redirect and the redirect that points at a page which no longer exists, which is the failure that hand-written maps produce most often.

A note on redirect chains: if the old site already contained redirects, resolve them at migration time rather than stacking a new hop on top. A request that walks three redirects before reaching content is slow, loses some link equity at each hop, and is fragile — remove one intermediate host in a year’s time and the whole chain breaks. Flatten every legacy path to a single hop directly to its final destination.

What the platform will not do for you

Two responsibilities stay with your team no matter how much the platform automates. The first is spec quality: Mintlify renders the OpenAPI document faithfully, which means a terse summary, a missing example, or an untagged operation shows up as a bad page rather than a build error. Fix these in the spec, not in the portal. The second is content accuracy over time — the platform will happily keep publishing a guide that describes an endpoint you removed two releases ago, because nothing connects prose to the contract. Schedule a review cadence tied to your release process rather than to the calendar, so every breaking change triggers a prose audit of the pages that mention the affected resource.

FAQ

Does Mintlify support multi-version API documentation?

Mintlify supports multiple OpenAPI specs by declaring separate navigation groups, each pointing at a distinct spec path. A full version-switcher UI is not automatic and requires manual navigation configuration.

How do I migrate existing Markdown files without breaking links?

Rename the files to .mdx and commit them, add redirect entries for any path that changed, then run mint broken-links before pushing to your connected branch. The redirects preserve inbound links while the check catches dead internal navigation targets.

Can I integrate Mintlify with existing CI/CD pipelines?

Yes. Mintlify deploys on push to your connected branch, so any CI step that validates content and then merges to that branch triggers a deployment. The mint broken-links command is the main pre-merge gate available outside the cloud build environment.

Should I use docs.json or mint.json?

New projects use docs.json; mint.json is the legacy filename. Running the CLI on an existing mint.json project generates an equivalent docs.json automatically, and the configuration keys are otherwise unchanged.

Whichever platform a portal ends up on, the durable asset is the OpenAPI document and the prose beside it. Keeping both in your own repository, in formats that are not platform-specific, is what makes the next decision — whenever it comes — a migration of navigation and components rather than of everything you have ever written.