OpenAPI Commands

Four commands under leed site make up the OpenAPI import workflow: create-page-type roots a page set at a URL, generate writes it to disk, import uploads it, and list recovers a page type id you mislaid. This page is the flag-by-flag reference for all four. The order to run them in, the guards between them and what to do when one refuses are on importing an OpenAPI spec; read that first if this is your first import.

All four resolve a target environment and a session before doing anything, so all four must be run from your site folder (the one holding leed.config.json) or from raw-content/. Only generate works without a session.

CommandHTTP callLocal file it writes
leed site create-page-typePOST /api/pagetypessrc/_data/pageTypeList.json
leed site generatenone — entirely localsrc/<page type path>/, src/_data/pageTypeList.json, src/_data/menu.json, the import manifest
leed site importPOST /api/page/import/docs, then POST /api/page/import/docs/specthe manifest (updated, then deleted on full success)
leed site listGET /api/pagetypesnone

leed site create-page-type

Creates a page type in the CMS and registers it in your repository so the later commands can find it by id alone.

leed site create-page-type --name "API 3.7" --path /docs/api/3.7.0/ --type api
FlagTypeRequiredDefaultValid valuesWhat it does
--name <name>stringyes—anyDisplay name the CMS stores and shows in its page-type list
--path <path>stringyes—one or more /-separated segmentsThe path the page type is rooted at. Leading and trailing slashes are trimmed, so /docs/api/3.7.0/ is stored as docs/api/3.7.0. Nested segments are preserved, which is what makes a versioned set possible
--type <type>stringnoapidocumentation, api, postsThe kind of page type to create

An empty --path — --path / or --path " " — is refused before the request is sent, with --path must contain a non-empty slug (got "<value>").

The command prints the identifier every later step needs, the file it registered the page type in, and the three commands that follow:

	Created page type: pt_9f2c41

  name:       API 3.7
  slug:       docs/api/3.7.0
  pageTypeId: pt_9f2c41

  Registered "pt_9f2c41" locally in:
    /home/you/leed/example.com/raw-content/src/_data/pageTypeList.json

  Next:
    1. leed site generate --openapi <spec> --page-type-id pt_9f2c41
    2. leed site build   (review the output)
    3. leed site import --page-type-id pt_9f2c41

That local entry carries the page type’s slug and the server-assigned navigation menu id. generate reads both from it, which is why you never have to retype the path and why the generated menu lands on the menu the CMS already has rather than a new one.

--type posts is accepted here and creates a page type, but the workflow cannot be completed with it — the CMS bulk-import endpoint answers a posts page type with 400 Bulk import is only supported for documentation/api page types at step four. What each kind changes about a page is on page types and configuring a page type.

leed site generate

Reads a spec and writes a reviewable page set into your repository. Nothing is uploaded and no session is required.

leed site generate --openapi ./openapi.yaml --page-type-id pt_9f2c41
FlagTypeRequiredDefaultValid valuesWhat it does
--openapi <path>pathyes—a readable file ending .json, .yaml or .ymlThe spec to generate from
--page-type-id <id>stringyes—a pageTypeId from create-page-type or listWhich page type the set belongs to
--page-type-path <slug>stringnothe registered page type’s sluga /-separated slugNames the path by hand. Only load-bearing when no local registration exists
--base-url <url>urlnohttps://<your configured domain>/any absolute URLBase for links in the generated pages. A trailing / is added if you omit one

The extension check runs in the command’s pre-action, before the engine opens anything: a spec named api.txt fails with --openapi file must end with one of .json, .yaml, .yml: <path>, and a missing file or a directory fail with their own messages in the same place.

Everything the command writes happens after every guard has passed. In order: the spec is processed, the page set is composed in memory, every generated spec file is measured against the size ceiling, the set is checked for internal integrity, and only then are the preview files written, the pageTypeList.json entry injected and the manifest saved.

Re-running generate for the same page type clears any recorded upload progress (entries, specStagedIds and menuApplied are reset) because a fresh generation invalidates it, but it deliberately keeps the original pre-first-inject pageTypeList.json backup. Without that, a second generate would capture the already-injected file as the “original” and the eventual restore would be a no-op.

Each endpoint page the generator writes carries a request-snippet tab strip bound to the reserved api-languages tab group — cURL, TypeScript, Python, Go. It is emitted by the generator from documentationConfiguration.openAPI.snippetLanguages, repaired automatically, and filtered out of the page editor’s Tab Group menu. Read it, do not edit it; API reference pages shows what it looks like to a reader.

leed site import

Uploads a set that generate already produced and a build has already rendered.

leed site import --page-type-id pt_9f2c41
FlagTypeRequiredDefaultValid valuesWhat it does
--page-type-id <id>stringyes—the id the set was generated forSelects the manifest, and so the set, to upload

There is no --openapi here and no --base-url. Both were recorded at generation time and are re-read from the manifest; the CLI accepts unknown options silently, so passing --base-url to import changes nothing at all rather than erroring.

Two guards run before the first request, in this order:

  1. The spec must be unchanged. import re-derives its payloads from the spec file on disk rather than reading what generate wrote, so a spec that moved or changed would upload something nobody reviewed. Its SHA-256 must still match the digest recorded at generation. It fails with “The OpenAPI spec changed or is missing since you generated.”
  2. A recorded build must be newer than the generate. The message names which of the two cases you are in and prints both timestamps, and its hint says outright that --debug builds are not recorded — the recording rule on leed site build is the reason a reader who always builds with -d is told to build forever.

Only after both pass does the command revalidate your session and upload. It reports three counts and a menu line:

	Uploading 42 page(s) to https://app.leed.ai...
	Import summary: 40 imported, 2 skipped, 0 failed (of 42).
	Menu applied.

skipped is not a failure: the import endpoint is create-only, so a page the CMS already holds is acknowledged and never overwritten. That is what makes a resume safe, and it also means import cannot push an edit to an existing page.

Anything less than complete leaves the run resumable and exits non-zero. If pages failed, the message names the manifest to resume from; if only the menu is outstanding, the message is Menu not yet applied. The manifest, the preview files and the injected pageTypeList.json entry all stay in place, and re-running the same command picks up where it stopped.

On a fully successful run the CLI restores pageTypeList.json from the generation-time backup, prunes the generated entries from src/_data/menu.json, deletes the local preview directory and deletes the manifest. From that point the CMS is authoritative and there is nothing local left to resume.

leed site list

Lists the page types the CMS holds, filtered to one kind.

leed site list --type api
FlagTypeRequiredDefaultValid valuesWhat it does
--type <type>stringnoapidocumentation, api, postsWhich kind of page type to list

The filter is applied on your machine, not by the server: the command fetches every page type and keeps the ones whose type matches. Page types stored before the kind was recorded have no type at all and therefore match nothing — if a page type you know exists is missing from every filter, that is why.

  pageTypeId    name             slug
  u05nxr        Documentation    docs
  pt_9f2c41     API 3.7          docs/api/3.7.0

An empty result is a success, not a failure: No page types of type "api" found.

Where the manifest lives

generate and import share one file per page type:

${XDG_CACHE_HOME:-~/.cache/leed}/<environment>/<companyId>/imports/<pageTypeId>.json

It sits in your cache directory rather than in the repository on purpose — raw-content/ is a git working tree, and a stray git add there would commit your import state. Keying by companyId matters because one machine can hold sessions for more than one company, and keying by environment keeps a staging import from resuming into production.

The file is deleted on a fully successful import. A manifest the CLI cannot parse is treated as “start over” rather than as an error, which is safe precisely because the import endpoint only ever creates. Note the path caveat: when XDG_CACHE_HOME is set the leed segment is dropped, so the files land directly beneath it. Every path the CLI reads or writes is cataloged on leed.config.json, files and environment.

JSON output

All four commands accept --json and answer with one envelope on stdout, as site.create-page-type, site.generate, site.import and site.list. Their data objects, generated from leed schema --json at CLI 4.106.0:

{
  "site.create-page-type": { "pageTypeId": "…", "name": "…", "slug": "…", "type": "api", "pageTypeListPath": "…" },
  "site.generate": { "pageTypeId": "…", "pageTypePath": "…", "pageCount": 42, "localPath": "…", "specPath": "…", "manifestPath": "…" },
  "site.import": { "pageTypeId": "…", "cmsUrl": "…", "pageCount": 42, "imported": 40, "skipped": 2, "failed": 0, "menuApplied": true, "specsStaged": true, "cleanedUp": true },
  "site.list": { "type": "api", "pageTypes": [{ "pageTypeId": "…", "name": "…", "slug": "…", "type": "api" }] }
}

Failures here carry not_a_site_repo, not_authenticated, auth_expired, permission_denied, not_found, validation_failed, upload_failed or network_error. None of the four is destructive, so none of them ever refuses for confirmation. The envelope itself, and what to do with each code, are on machine-readable output (--json).

Every leed command and every global option is indexed at CLI command reference. All four commands here revalidate your session before their first request, which is why one of them can drop into a login mid-run — CLI authentication explains what that revalidation is doing. When one of them stops with a message you do not recognize, it is in the catalog on CLI troubleshooting and exit codes.

ESC