Several APIs, one installation.
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 makes the case; this guide is the mechanics.
Declare the specs
{
"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— mandatory 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 an explicit config error at boot.title— the selector label, needed because inactive specs are never downloaded (theirinfo.titleis unknown). Falls back toid.- Each entry takes
urlor an inlinespec. Only the active spec is downloaded, parsed and indexed — an installation with twelve APIs costs the reader one. defaultpicks the spec shown on arrival; otherwise the first entry.
A single-spec openapi.url config keeps behaving exactly as before: no selector, unprefixed routes, same storage keys.
Override per spec
The root config carries what the installation shares; an entry redeclares what is specific to it. The merge rules are the ones the configuration reference states: settings objects merge key by key (the spec wins, null included), named lists merge by identifier, hide and overlays accumulate (root first), environmentsLocked is replaced. The first key that forced this machinery is tryIt.requestCredentials: the right value depends on the target API, so it belongs on the entry.
{
"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 don’t travel, each flagged by name in the console when misplaced: history (root-only — retention is one browser storage cap for all specs), theme.custom (root-only — themes are injected once at boot), and scenarios (entry-only — a scenario references operations of one specific spec).
Isolation is the point
Environments, request history, OAuth tokens, try-it header memory, scenarios, search, a reader’s local schema patch — all namespaced by spec id. Two specs declaring a same-named scheme (bearerAuth is everywhere) never share a variable: 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
Because id prefixes every stored key, changing a spec’s id is equivalent to resetting that spec’s local data — environments, history, local scenarios. Pick ids the way you pick database names: stable, boring, never recycled.