# PyAI CLI integration guide for coding agents

Use this guide when building an application against PyAI, automating a file
workflow, or adding PyAI to an existing repository. It is readable as plain
Markdown and does not require the marketing website renderer.

## Availability and sources of truth

The CLI is included in `@pyai/sdk` 0.5.0. Install it with
`npm install -g @pyai/sdk@0.5.0`. Browser login supports existing PyAI accounts;
unattended agents should use an authorized environment key. Inspect the
installed command schema before acting.

Use these sources in this order for the question they answer:

1. `pyai schema [COMMAND OR GROUP] --json`: syntax supported by the installed CLI.
2. `pyai schema --openapi --json`: API request/response contract on the configured deployment.
3. https://docs.pyai.com: canonical guides and realtime protocols.
4. https://pyai.com/cli.md: complete CLI handbook and troubleshooting.
5. https://api.pyai.com/llms.txt: product and integration index.
6. https://pyai.com/cli-schema.json: downloadable CLI schema snapshot for this
   release. Your installed CLI schema wins if it differs.

The production API contract is https://api.pyai.com/openapi.json. If a CLI
shortcut is absent from the installed build, use a documented full command or
build the intended source revision. If a route is absent from the deployment,
do not invent an endpoint or present the feature as deployed.

## Setup without hidden side effects

Install the published CLI with Node.js 22 or newer recommended:

```bash
npm install -g @pyai/sdk@0.5.0
pyai --version
pyai schema --json
```

For development from a source checkout, build with Node.js 22 or newer:

```bash
cd sdk/typescript
npm ci
npm run build
node dist/cli.js schema --json
npm install -g .
```

Alternatively install a supplied build with
`npm install -g /path/to/pyai-sdk-0.5.0.tgz`. The compiled executable supports
Node.js 18 or newer. Use `node dist/cli.js` explicitly when another package's
`pyai` shadows this executable.

Use `PYAI_API_KEY` supplied by the user's environment or secret store. Do not
print it, interpolate it into a prompt, commit it, or put it in a browser URL.
Keys are opaque strings; do not parse their components.

```bash
pyai whoami --json
pyai schema speak --json
pyai schema agents create --json
pyai schema --openapi --json > openapi.json
```

`whoami` is an authenticated network call. The local schema and recipes are
offline. Fetching OpenAPI is read-only and does not require a key. Inspect
`whoami` for the selected organization, environment, and scopes before changing
resources. An exported `PYAI_API_KEY` overrides profile credentials even if
`--profile` is present; do not assume changing the profile changes the key.

If a new isolated sandbox is appropriate for the requested task:

```bash
pyai auth sandbox --profile sandbox --json
```

This creates a new organization and persists a private local credential.
Reuse it instead of repeatedly minting new tenants. Review returned scopes and
expiry. Sandbox scope sets do not include every API; inspect for `dub:render`
before attempting dubbing. Existing account access uses an environment key or,
on a deployment supporting it, `pyai login --no-browser` with human approval.
Unattended automation should not wait on browser consent.

## A reliable build loop

1. Discover the exact command with filtered `schema`; fetch OpenAPI for API fields.
2. Read current catalogs and resource state needed for the task.
3. Write API payloads to reviewable JSON files. Keep secrets out of source control.
4. Run `--dry-run --json` for intended mutations and inspect the plan.
5. Execute the authorized mutation once, preserving its returned resource/job ID.
6. Poll an existing job with a deadline; download output to a new path.
7. Verify the artifact and report the result, command, and any deployment limitation.

`--dry-run` reads and validates local input and prints the planned operation.
It does not validate all server-side schema rules, permissions, billing, or
runtime capabilities. It is not a substitute for reading OpenAPI. Explicit
`delete` and `cancel` commands do not prompt for terminal confirmation.

## Task-to-command map

| Task | Command pattern |
| --- | --- |
| Identify active key | `pyai whoami --json` |
| Browse voices | `pyai voices --language en --json` |
| Render text | `pyai speak "Your appointment is confirmed." -o prompt.wav --json` |
| Render a script | `pyai speak --text-file script.txt --format mp3 -o narration.mp3 --json` |
| Transcribe a file | `pyai transcribe call.wav --json` |
| Export plain text | `pyai hear call.wav --text-only` |
| Transcribe a hosted file | `pyai transcribe --url URL --wait --wait-timeout 300 --json` |
| Discover Dub languages | `pyai request GET /healthz/dub --json` |
| Dub a supported pair | `pyai dub input.wav --from SOURCE --to TARGET -o dubbed.wav --wait-timeout 600 --json` |
| Configure an Agent | `pyai agents create --data @agent.json --json` |
| Patch an Agent | `pyai agents update AGENT_ID --data @changes.json --json` |
| Inspect expressive voice options | `pyai cast capabilities --json` |
| Render a directed script | `pyai cast render --data @render.json --json` |
| Inspect a call | `pyai recap get CALL_ID --json` or `pyai trace get INTERACTION_ID --json` |
| Discover a missing dedicated command | `pyai schema --openapi --json`, then `pyai request METHOD /PATH` |
| Scaffold a new integration | `pyai init voice-project --template agent` |
| Read offline examples | `pyai recipes agent --json` |

Uppercase tokens are placeholders. Replace voice IDs, resource IDs, and
language codes with values from the target deployment. Do not fabricate a
voice ID or assume an output language is enabled. The `say` alias exists, but
`speak` is the primary speech command in examples and generated integrations.

## Inputs, artifacts, and pipelines

- `speak` accepts one positional text string, `--text`, or `--text-file`. With
  no explicit input it reads piped UTF-8 stdin; missing input on a terminal
  returns an error. Quote text containing spaces or shell metacharacters.
- Local transcription uses a positional path or `--file`. A positional input
  beginning with `http://` or `https://` selects a hosted URL and asynchronous
  job. Use `--url` to select hosted input explicitly.
- `--data` accepts inline JSON, `@file.json`, or `@-`. Dedicated JSON mutations
  require objects; `request` allows other JSON values when the API does.
- File uploads accept `--file -` for binary stdin and `--filename` for its name.
  Do not consume stdin simultaneously for audio and `--data @-`.
- The CLI bounds stdin at 128 MiB. Server limits are independent and documented
  in OpenAPI. Prefer files or hosted URLs for larger inputs.
- Audio output uses `--out PATH` and a receipt with `path`, `bytes`, and
  `content_type`. It does not embed audio in JSON. The parent directory must
  exist. Existing output files are refused unless `--force` is explicit.
- `--out -` writes raw bytes to stdout, requires a pipe, and conflicts with
  `--json`. `--text-only` emits transcript text and conflicts with `--json`.
  URL jobs require `--wait` for text-only output; large jobs may return only
  `result_url`, so preserve normal JSON when consuming full results.
- Shell redirection has its own overwrite semantics. `> file.txt` is not
  protected by the CLI's no-overwrite file handling.

```bash
printf '%s\n' 'The build is ready.' | pyai speak -o build.wav --json
pyai hear build.wav --text-only > build.txt
pyai agents update AGENT_ID --data @changes.json --dry-run --json
```

Speak defaults to `pyai-speak.wav`. `--format` chooses actual bytes: WAV, MP3,
Opus, AAC, FLAC, PCM, `g711_ulaw`, or `g711_alaw`. Changing the extension alone
never transcodes a response. G.711 is 8 kHz; its default extension is `.raw`.

## Async lifecycle and retries

A submission response is not evidence that a job completed. Record its ID and
inspect the appropriate terminal status:

| Family | Success | Failure |
| --- | --- | --- |
| `jobs wait ID` | `completed` | `failed`, `cancelled` |
| `design wait ID` | `completed` | `failed` |
| `cast wait ID` | `done` | `error` |
| `dub wait ID` | `done` | `error` |

The top-level `dub INPUT --from LANG --to LANG --out PATH` performs submission,
wait, and audio download. Its JSON receipt includes `job_id`, `status`, `path`,
`bytes`, and `content_type`. Explicit `dub create`, `dub wait`, and `dub audio`
separate those stages. Cast uses `cast render`, `cast wait`, and `cast audio`.

HTTP `--timeout` defaults to 30 seconds, including response downloads.
`--wait-timeout` defaults to 120 seconds and `--poll-interval` to 2 seconds.
A polling timeout does not cancel a remote job. Error details include the job
path when available; inspect it before resubmitting. For long-running work,
resume with the relevant `wait` command.

Automatic retries apply only to reads. Mutations are not retried automatically.
Some mutations accept `--idempotency-key`; the endpoint must implement it for
it to be effective. Reuse an idempotency key only for the same intended request
and body. After an ambiguous network failure, reconcile remote state rather
than immediately creating another resource.

Paginated list commands return one page and accept `--limit 1..100` and
`--cursor`. Continue with the response's `next_cursor`; do not assume the first
page contains all resources. For additional filters use documented
`request --query name=value` options.

## Machine-output contract

Use `--json` or `-j` for structured output. Normal success is one JSON result
on stdout, with no progress mixed in. Result shapes follow the API, except
local operations and artifact receipts. Do not assume all commands return a
`data` array or the same envelope.

Errors are JSON on stderr:

```json
{"error":{"code":"unauthorized","message":"...","status":401}}
```

`status`, `request_id`, and other detail fields are conditional. Branch on
`error.code` and process exit status, not the error's wording. Unknown response
fields may be added. Diagnostics return a JSON result with checks on stdout
and a nonzero exit when checks fail.

| Exit | Meaning | Handling |
| --- | --- | --- |
| `0` | Success | Consume result |
| `1` | API/job/operational failure | Inspect API code and remote state |
| `2` | Local input/configuration error | Correct flags, JSON, or filesystem paths |
| `3` | Authentication/permission failure | Fix key, scope, or membership |
| `4` | Network failure/timeout | Reconcile state before retrying a mutation |
| `130` | Interrupted | Resume existing work if needed |

Do not retry authorization failures. `402` requires checking account credit,
plan, or budget. `429` requires respecting delays and capacity/daily limits.
The CLI normalizes supported API error envelopes; preserve `request_id` in a
redacted issue report when available.

Browser login emits newline-delimited public JSON events on stderr, including
`authorization_required` and possibly `browser_unavailable`, before a final
stdout success receipt. Do not treat every stderr line during login as a fatal
error. The CLI saves a 30-day credential after explicit owner/admin consent;
logout only removes it locally, and revocation is performed in the console.

Terminal JSON redacts known credentials and secret-shaped fields. If an API
returns a token the application must retain, `request ... --out response.json`
saves the original response privately and prints only an artifact receipt.
Treat that file as sensitive; never commit it or paste it into a model prompt.

## New projects and existing repositories

```bash
pyai init voice-project
pyai init voice-ts --template typescript
pyai init voice-python --template python
pyai init voice-project --template agent --dry-run --json
```

`agent` is the default. The destination must not exist and its parent must be
present. Scaffolding is offline; it creates `PYAI.md` and starter assets without
installing packages, minting credentials, or making API calls. Every template includes `README.md`, `PYAI.md`, `.env.example`, and
`.gitignore`. The agent template adds `agent.json`, `speech.json`, and
`job.json`; TypeScript adds `main.ts` and `package.json`; Python adds `main.py`.
Follow the created README. JSON output includes `directory`, `template`, `files`,
`created`, and `next_steps`.

For an existing repository, link this guide from its agent instructions and
add task-specific constraints. Keep application-specific instructions separate
from assumptions about the live API; fetch current contracts when implementing.

Suggested repository instruction:

> Use PyAI CLI for file workflows and REST resource setup. Before writing API
> payloads, inspect `pyai schema COMMAND --json` and the deployment's OpenAPI.
> Use PYAI_API_KEY from the environment without printing or committing it.
> Discover voice and language capabilities rather than guessing. Preview
> mutations, retain resource IDs, bound waits, and handle stable error codes.
> Use the official SDK for application code and realtime audio. Explain what
> was verified locally and what still requires deployment.

## When to use the SDK or MCP

The CLI covers files, REST operations, configuration, and job lifecycles.
It does not capture live microphone input or run realtime Hear, Omni, or AMD
streams. Use the official TypeScript or Python SDK for that transport:
https://docs.pyai.com/guides/sdks. The MCP server is another integration when
an agent host works best with tools:
https://docs.pyai.com/guides/use-pyai-in-cursor.

For Omni, follow the canonical protocol rather than deriving it from CLI
commands: https://docs.pyai.com/realtime/omni-protocol. Managing an Agent profile
is separate from opening a realtime conversation. Do not infer unsupported
socket behavior, scopes, or endpoints from a REST resource name.

## Verification checklist for a completed integration

- The installed CLI supports the commands used in scripts.
- API bodies and permissions match the target deployment's OpenAPI.
- Catalog-derived IDs and enabled language pairs are used.
- Secrets are absent from prompts, tracked files, and output logs.
- Artifacts are complete, in the intended format, and written to deliberate paths.
- Jobs are verified in their successful terminal state before outputs are used.
- Error handling covers local input, permission, network, and API/job failures.
- The handoff includes a runnable command and distinguishes local implementation
  from published packages and deployed browser authentication.

`pyai doctor` and `pyai smoke` make real synthesis/transcription calls and
consume applicable usage quota. Use `--dry-run` to inspect their request plans,
and run them when that verification is appropriate to the task.
