Spec0docs
CLIGuides

For AI agents

The `spec0` CLI is designed so agents can use it without hardcoded knowledge. This guide describes how.

The spec0 CLI is designed so agents can use it without hardcoded knowledge. This guide describes how.

Set SPEC0_MODE=agent

One env var flips every agent-friendly default in lockstep:

export SPEC0_MODE=agent

With it set, every invocation:

  • defaults --output to json (no need to pass it explicitly),
  • suppresses colour and spinners in both stdout and stderr,
  • skips the "new version available" update banner,
  • behaves as if isTTY were false regardless of the real terminal.

Explicit flags still override — spec0 doctor --output=text works even with SPEC0_MODE=agent.

The contract

Three things you can rely on forever:

  1. Every command has a stable name, stable flags, and a stable exit code. Deprecations go through a one-minor warning window. Breaking changes bump the CLI major version.
  2. --output=json is available on every structured command. Progress goes to stderr, the final result is exactly one JSON document on stdout.
  3. Exit codes are categorical. 0 success; 2 usage; 3 auth; 4 permission; 5 not found; 6 conflict; 7 validation; 8 rate limit; 9 server; 10 network. See spec0 commands --output=json for the current table.

You don't need to remember the command list. Ask the binary.

Step 1 — discover capabilities

spec0 commands --output=json

Returns a capability manifest:

{
  "version": "0.1.0",
  "commands": [
    {
      "name": "publish",
      "description": "Publish an API spec to the public registry (org-scoped, shareable URL, no team required)",
      "usage": "spec0 publish [spec-file] [options]",
      "args": [{ "name": "spec-file", "required": false }],
      "flags": [
        { "flags": "--name <slug>", "description": "URL-safe API slug ..." },
        { "flags": "--semver", "description": "Auto-compute next version ..." }
      ]
    }
  ],
  "exitCodes": {
    "0": "success",
    "3": "not authenticated",
    ...
  }
}

Re-read this manifest at the start of each session. It's always in lockstep with the binary — no version drift.

Narrow the result with a pattern:

spec0 commands mock --output=json        # only mock commands
spec0 commands api --output=json         # only API management commands

Step 2 — map task to command

The command surface groups semantically:

If the user wants to…Use
authenticatespec0 auth login (interactive) or env vars
confirm auth is wiredspec0 doctor or spec0 whoami
push a spec privatelyspec0 push <file> --team <team>
publish a spec to the registryspec0 publish <file> --semver
check if publish would be a no-opspec0 sync-status <api>
list APIs in the orgspec0 api list
inspect one APIspec0 api show <ref>
diff published versionsspec0 api changelog <ref>
diff local vs publishedspec0 diff <file> <org>/<api>
download a published specspec0 pull <org>/<api>[@tag]
find APIs by descriptionspec0 search "<query>"
lint a local specspec0 lint <file> --min-score N
upload a shared rulesetspec0 lint --save-ruleset <file>
spin up a mockspec0 mock create --api <name>
locate a mock's URLspec0 mock url <api> (one line)
generate a CI workflowspec0 ci generate github

When unsure, fall back to spec0 commands <keyword> and read the descriptions.

Step 3 — invoke

Always pass --output=json (or yaml) when parsing the result. Example:

spec0 api list --output=json | jq '.[] | select(.status == "ACTIVE")'
spec0 mock list --output=json | jq -r '.[].mockUrl'
spec0 sync-status my-api --output=json | jq -r '.needsPublish'

Pass credentials via env, never on the command line:

SPEC0_TOKEN=... SPEC0_ORG_ID=... spec0 publish openapi.yaml --semver --output=json

Step 4 — parse structured errors

When a command fails with --output=json (or yaml), the error arrives on stdout as a structured payload — the same pipe contract as the success case. This keeps your parser simple: one JSON document per invocation.

{
  "error": {
    "code": "AUTH_MISSING",
    "message": "Not authenticated. …",
    "hint": "Set SPEC0_TOKEN + SPEC0_ORG_ID, or run 'spec0 auth login'."
  }
}

The code field uses the symbolic exit-code name (AUTH_MISSING, NOT_FOUND, USAGE, etc.) so you don't need to memorise the numeric mapping. The numeric exit code is still set — both are reliable signals.

In text mode (no --output flag), errors stay where humans expect them: plain message on stderr, empty stdout.

Step 5 — react to exit codes

Branch on the exit code, not on stderr substring matching:

ExitAgent action
0Success. Parse stdout for the structured result.
2You sent bad flags. Re-read the manifest for this command.
3Auth missing. Ask the user for SPEC0_TOKEN + SPEC0_ORG_ID, or run spec0 auth login.
4User is authenticated but lacks permission. Ask them to switch org or request access.
5Resource doesn't exist. Offer to create it (publish / mock create) or suggest spec0 api list to disambiguate.
6Name clash. Suggest a different name or pass --api-id to target the existing one.
7Validation failed — usually a spec quality issue. Surface the stderr; consider spec0 lint <file> for details.
8Rate limited. Back off (exponential, 30 s+) and retry.
9Platform server error. One retry is fine; two is noise.
10Network unreachable. Retry with backoff; if persistent, the platform is probably down.

Step 6 — close the loop

After a mutation, verify:

  • After publish, call spec0 log <ref> to confirm the version landed.
  • After mock create, call spec0 mock show <api> for the URL + provisioning state.
  • After push, call spec0 api show <ref> to confirm metadata.

This is cheap (one GET) and avoids declaring success on a failed mutation.

Semantic search via MCP

The Spec0 platform exposes an MCP server for semantic search over the org's APIs — useful when the user's request is fuzzy ("find the API that handles refunds") rather than exact. Get the URL with:

spec0 mcp url

Wire the URL into your MCP client config. The CLI itself does not need to proxy these calls.

What the CLI won't do for you

  • It won't pick an API name. If the user says "publish my spec", read info.title from the spec or ask.
  • It won't break without warning. Deprecated flags print a one-minor warning on stderr — read those and update.
  • It won't hide errors under 0. Any non-zero exit means you should not assume success.
  • It won't guess credentials. Missing env → 3, every time.

When things go wrong

Start with spec0 doctor. It reports the resolved source for every setting (env var name, config path, or default). Ninety-percent of "authed but not working" cases are a wrong URL or stale token visible there in two seconds.

If a command fails with an exit code the table above doesn't cover (e.g. 1), it's an unclassified failure — treat it like 9 (maybe-transient, one retry allowed), and escalate to the human if the retry fails.

On this page