# Several APIs, one installation.

> Document several APIs with one ApiGlow installation: declare openapi.specs, override config per spec with clear merge rules, rely on total isolation.
>
> Source: https://apiglow.dev/docs/multi-spec/index.md


`openapi.specs` turns one installation into a portal: a selector in the header, one schema active at a time, and everything the reader accumulates kept separate per spec. The [feature page](/features/portal/) makes the case; this guide covers the mechanics.

## Declare the specs

```json
{
  "openapi": {
    "specs": [
      { "id": "payments", "title": "Payments API", "url": "https://example.com/payments.json" },
      { "id": "accounts", "title": "Accounts API", "url": "https://example.com/accounts.yaml" }
    ],
    "default": "payments"
  }
}
```

- `id` — required, a stable slug (`[a-z0-9-]`), unique in the array. It is both a route segment (`#/s/{id}/…`) and a storage prefix. A duplicate or invalid id is a config error at boot.
- `title` — the selector label. It is needed because inactive specs are never downloaded, so their `info.title` is unknown. Falls back to `id`.
- Each entry takes `url` or an inline `spec`. Only the active spec is downloaded, parsed and indexed, so an installation with twelve APIs costs the reader one.
- `default` picks the spec shown on arrival. Without it, the first entry is used.

A single-spec `openapi.url` config behaves exactly as before: no selector, unprefixed routes, same storage keys.

## Override per spec

The root config carries what the installation shares, and an entry redeclares what is specific to it. The merge rules are the ones in the [configuration reference](/docs/configuration/#per-spec-overrides-multi-spec): settings objects merge key by key with the spec winning, named lists merge by identifier, `hide` and `overlays` accumulate with the root first, and `environmentsLocked` is replaced.

`tryIt.requestCredentials` is the clearest case: the right value depends on the target API, so it belongs on the entry.

```json {title="Overrides in practice"}
{
  "openapi": {
    "specs": [
      { "id": "payments", "title": "Payments API", "url": "https://example.com/payments.json",
        "branding": { "productName": "Payments" },
        "theme": { "default": "corporate", "available": ["corporate", "dark"] } },
      { "id": "accounts", "title": "Accounts API", "url": "https://example.com/accounts.yaml",
        "tryIt": { "requestCredentials": "include" },
        "environmentsLocked": true,
        "features": { "scenarios": false } }
    ]
  }
}
```

Three keys do not travel:

- `history` is root-only — retention is one browser storage cap for all specs.
- `theme.custom` is root-only — themes are injected once at boot.
- `scenarios` are entry-only — a scenario references operations of one specific spec.

Each case is named in the console when a config misplaces it.

## What isolation covers

Environments, request history, OAuth tokens, try-it header memory, scenarios, search and a reader's [local schema patch](/docs/schema-support/#when-the-reader-is-the-one-who-is-stuck) are all namespaced by spec id.

Two specs declaring a same-named scheme — `bearerAuth` is everywhere — never share a variable, so a token obtained on Payments cannot be injected into a request against Accounts. Deep links carry the prefix (`#/s/payments/op/…`), so a shared link lands on the right API; a prefix-less link opens on the active spec.

## The id is the storage key

Because `id` prefixes every stored key, **changing a spec's id resets that spec's local data** — environments, history, local scenarios. Choose ids that are stable and never recycled.

