omi-cli is built so an LLM-driven harness can drive it without a wrapper
layer. This page documents the stable contract.
The JSON contract
Pass--json (a global flag, before the subcommand) and the CLI behaves
like a strict tool:
- stdout receives a single JSON document and only a JSON document. No spinners, no progress messages, no decorative output.
- stderr receives any error as a JSON object:
{"error": "...", "detail": "..."}. - exit code signals what happened.
Exit codes
These codes are part of the contract. They don’t change between minor
versions, so branch on them in your harness instead of parsing error text.
When Desktop returns the exact
execute_sql result
SQL Error: The local database could not complete that query., both
omi local sql and omi local call execute_sql exit with code 1, including
when Desktop wraps that result in an HTTP 200 response. With --json, this
error goes to stderr and stdout stays empty. This message match does not
classify other SQL result strings as failures. A successful query with no
rows still returns exit code 0.
Authentication: API key vs browser OAuth
The CLI supports both. For agents specifically, the API-key path is almost always the right call:
API keys are long-lived and scoped, and they don’t need a browser, so they suit
agents. The browser OAuth flow exists for humans on a laptop, uses a
loopback callback with PKCE, and stores short-lived Firebase ID tokens that
auto-refresh between calls.
omi auth login so a malformed value still fails fast with exit 1.
Retry behavior
Transient failures are retried automatically before the CLI surfaces an error:- 5xx: exponential backoff with jitter (initial 0.5s, cap 8s).
- 429: honors the server’s
Retry-Afterheader when present (capped at 60s so a misconfigured upstream can’t pin your agent forever); falls back to jitter otherwise. TheRetry-Aftercap means a 429 with a one-hour hint becomes a one-minute wait, so your agent gets exit4quickly enough to decide whether to back off itself. - Transport errors (DNS failures, dropped TCP) are retried up to 4 attempts.
detail field for rate-limit errors looks like:
dev:conversations, dev:memories, dev:memories_batch)
matches the backend’s rate-limit policy IDs so you can map them to your own
backoff strategies.
Worked example: Python harness
Handling rate limits in your harness
Tips
- One JSON document per invocation. Don’t try to stream. The CLI doesn’t emit incremental output. Run it again for the next page.
- Use
--profileto isolate environments. A staging profile + a prod profile saves you from accidentally writing to prod with a test script. - Use
--api-base http://localhost:8080for local backend testing. --verboseis safe in JSON mode. Debug output goes to stderr, stdout stays valid JSON.- Pipe stdin with
--text -foromi conversation create. This is handy when the content is generated by another tool and you don’t want to shell-escape it.