# ODP Command-Line Interface
## Overview
`openbb-cli` is a command-line interface for the [OpenBB Platform](https://docs.openbb.co/platform) and any other OpenAPI 3.x server. It runs in three modes:
* **Non-TTY one-shot** (default) — `openbb [--key value]` dispatches one command, prints a JSON line, exits. The form CI tools and agents reach for.
* **Batch** — `openbb --batch` reads NDJSON requests from stdin, fans them out concurrently, writes NDJSON responses to stdout.
* **Interactive REPL** — `openbb -i` drops into a rich-output prompt with auto-completion, menu navigation, and routine playback.
Backends are pluggable: dispatch through an in-process `obb` namespace, an `openbb-platform-api` HTTP server, or any OpenAPI 3.x server (e.g. `https://api.congress.gov`) without touching code.
The full user docs live at **[docs.openbb.co/odp/cli](https://docs.openbb.co/odp/cli)**.
## Backends
Four sources for the command surface. Three are OpenBB-aware; the fourth is the generic OpenAPI 3.x fallback.
| Source | Flag | Cold start | When to use |
|--------|------|------------|-------------|
| **In-process `obb`** | _(default)_ | slow (`import openbb` ~2s) | Local script with `openbb` already installed; no separate process to manage. |
| **OpenBB Platform server** | `--server URL` | fast after first fetch | Long-running shared server (`openbb-platform-api`) — many CLI invocations or many users amortize the import cost. |
| **OpenBB `.spec` file** | `--spec PATH` | fastest (~50ms) | Ship the spec next to scripts/agents; skips OpenAPI fetch + parse on every call. Generate with `openbb --generate-spec --server URL -o file.spec`. |
| **Arbitrary OpenAPI 3.x server** | `--server URL` (non-OpenBB host) | depends on host | Any external API with an OpenAPI document — Congress.gov, NY Fed, USDA, NWS, your own FastAPI service. |
The first three speak the same OpenBB Platform surface (the `OBBject` envelope, providers, the `obb.reference` menu tree). The fourth treats whatever the upstream publishes as the truth — same dispatcher, same parser, same auth, just no OpenBB-specific affordances.
### What's different about OpenBB upstreams
Multi-provider Pydantic models and the `OBBject` envelope are OpenBB Platform conventions, not OpenAPI ones. The CLI detects an OpenBB endpoint by the presence of a `provider` discriminator parameter on the operation and turns on extra behavior:
| Feature | OpenBB upstream | Generic OpenAPI upstream |
|---------|-----------------|--------------------------|
| Response envelope | `OBBject` — `id`, `results`, `provider`, `warnings`, `chart`, `extra` | Whatever the server returns |
| `--describe COMMAND` | Groups parameters + result schema **per provider** | Flat `{parameters, output_schema}` |
| `--describe COMMAND:PROVIDER` | Returns just that provider's slice | Suffix ignored |
| Per-provider argparse narrowing | `--provider X` filters accepted flags; passing a flag from another provider errors at parse time | Every declared flag accepted |
| Help text per provider | `(provider: …)` annotations stripped; only sections relevant to the chosen provider | Description shown verbatim |
| REPL menu tree | Mirrors `obb.reference` (router descriptions, command groupings) | Built from URL path prefixes |
| Boolean flags | `--flag` / `--no-flag` toggle (OpenBB exposes `default: true` booleans) | Same — applies to any spec |
Everything else — `--list-commands`, `--describe COMMAND`, `--batch`, headers/query auth, spec generation, REPL playback — works identically against both kinds of upstream.
## Installation
```bash
pip install openbb-cli
```
Optional extras:
```bash
pip install "openbb-cli[charting]" # Plotly + openbb-charting
pip install "openbb-cli[interactive]" # PyWry browser-based tables
pip install "openbb-cli[all]" # everything
```
## Quick start
### One-shot dispatch
```bash
# Against the local in-process obb namespace (the historical default)
openbb economy.gdp --provider oecd --limit 5
# Against any OpenAPI server — auto-detects spec layout, $ref-resolves params,
# auto-extracts HTML-embedded specs (e.g. Congress.gov)
openbb --server http://127.0.0.1:6900 equity.price.historical --symbol AAPL --provider fmp
openbb --server https://api.congress.gov bill --limit 5
```
### Spec files for instant cold-start
```bash
# Generate once; ship alongside the script that invokes the CLI
openbb --generate-spec --server https://api.congress.gov --output congress.spec
# Subsequent calls skip the OpenAPI fetch + parse on every invocation
openbb --spec congress.spec law --congress 119 --limit 5
```
### Generate an installable OpenBB extension from a spec
A `.spec` file is enough to dispatch commands directly. Going one step further, `--generate-extension` turns that spec into a full installable OpenBB Platform extension package — `Provider(...)` + `Fetcher` classes + a router that mirrors the upstream's namespace tree — that registers with `openbb-build` like any first-party extension. After install, every command shows up on the typed `obb.*` surface (auto-completion, `obb.reference`, `OBBject` envelopes, the works).
End-to-end with the NY Fed Markets API:
```bash
# 1. Snapshot the OpenAPI surface as a spec file. NY Fed publishes its
# spec at a non-default path, so point --openapi-path at the YAML.
openbb --generate-spec \
--server https://markets.newyorkfed.org \
--openapi-path /static/docs/markets-api.yml \
--output nyfed.spec
# wrote 55 commands to nyfed.spec
# 2. Generate a complete extension project from that spec
openbb --generate-extension \
--spec nyfed.spec \
--provider-name nyfed \
--output ./openbb-nyfed
# wrote project to ./openbb-nyfed/openbb-nyfed
# providers (1): nyfed
# routers (10): ambs, fxs, guidesheets, marketshare, pd, rates, rp, seclending, soma, tsy
# fetchers: 55 GET (across all providers)
# POST: 0 local-compute commands
# install: pip install -e ./openbb-nyfed/openbb-nyfed
# build: openbb-build
# 3. Install + register with the OpenBB Platform
pip install -e ./openbb-nyfed/openbb-nyfed
openbb-build
# 4. The new namespaces are live on the typed obb surface
python -c "from openbb import obb; print(obb.nyfed.rates.all.latest().to_df())"
```
(`--openapi-path` is only needed when the upstream doesn't expose `/openapi.json` directly. For servers that do — most FastAPI deployments, Congress.gov via the embedded-spec scraper — drop the flag.)
What `--generate-extension` produces:
```
openbb-nyfed/
├── pyproject.toml # PEP 621 / Hatchling — every provider + router declared as entry points
├── README.md
└── openbb_nyfed/
├── providers/
│ └── nyfed/
│ ├── __init__.py # nyfed_provider = Provider(name="nyfed", fetcher_dict={...}, credentials=[...])
│ └── models/.py # one Fetcher class per command (QueryParams + Data + Fetcher)
└── routers/
├── rates.py # @router.command(model="RatesAllLatest") — typed signature
├── ambs.py
└── ... # one router per top-level namespace; sub-routers nest via include_router(prefix="/sub")
```
Every flag is optional except `--spec`:
| Flag | Default | What it controls |
|------|---------|------------------|
| `--output PATH` | _required_ | Project root directory written to disk. |
| `--provider-name NAME` | derived from `--output` basename | Snake-case provider identifier — drives credential lookup keys (`credentials.get(f"{provider}_api_key")`). |
| `--project-name NAME` | `openbb-` | PyPI distribution name in `pyproject.toml`. Use dashes. |
| `--package-name NAME` | `openbb_` | Snake-case Python package name. |
| `--router-name NAME` | `` | Top-level router identifier. |
Bring-your-own-key APIs: parameter names that look like credentials (`api_key`, `apikey`, `app_token`, `X-API-Key`, `Authorization`, `client_secret`, `bearer_token`, …) are auto-promoted to the provider's `credentials=[...]` list. After install, `openbb` reads them through the standard user-settings flow (`OBB_USER_SETTINGS` / `~/.openbb_platform/user_settings.json`) — no per-call flag needed.
### Multi-spec — combine APIs under one CLI
Repeat `--spec NAME=PATH` to mount more than one spec at once. Each spec's commands get prefixed with its namespace, and each backend keeps its own `base_url`, headers, and query params:
```bash
openbb \
--spec congress=congress.spec \
--spec nyfed=nyfed.spec \
congress.bill --limit 5
openbb \
--spec congress=congress.spec \
--spec nyfed=nyfed.spec \
nyfed.markets.ambs --operation rates
```
Scope auth per namespace by prefixing the flag value with `:`:
```bash
openbb \
--spec congress=congress.spec \
--spec usda=usda.spec \
-Q congress:api_key="$CONGRESS_KEY" \
-Q usda:api_key="$USDA_KEY" \
-H usda:Authorization="Bearer $USDA_BEARER" \
congress.bill --limit 5
```
Tokens without a namespace prefix (`-H Authorization=...`, `-Q api_key=...`) apply to every backend; namespace-scoped tokens override on conflict for that one backend only.
The same shape works in TOML, which is the better fit when scripts reach for the same set repeatedly:
```toml
[specs.congress]
path = "/path/congress.spec"
[specs.congress.query]
api_key = "..."
[specs.usda]
path = "/path/usda.spec"
[specs.usda.headers]
Authorization = "Bearer ..."
[specs.usda.query]
api_key = "..."
```
`--list-commands` aggregates across every namespace; `--describe NAMESPACE.command[:provider]` resolves to the right backend automatically.
A single unnamed `--spec PATH` keeps the flat (unprefixed) command surface — backward-compatible with the historical single-spec form.
### Auth
Pick whichever the upstream API uses:
```bash
# Headers (Authorization, X-API-Key, ...)
openbb --server URL -H "Authorization: Bearer xxx" -H "X-Tenant: acme" cmd
# Query-string params (e.g. ?api_key=... like Congress.gov, USDA, NWS)
openbb --server URL -Q api_key=xxx cmd
# or via env — OPENBB_HTTP_QUERY_ auto-injected
OPENBB_HTTP_QUERY_API_KEY=xxx openbb --server URL cmd
```
#### Auth hooks (RBAC, token refresh, dynamic credentials)
Static headers and query params cover the common case. For RBAC, expiring tokens, or per-user credentials sourced from a vault, register an importable callable as an auth hook. Configured in TOML by `module:attribute` path — global, or per `[specs.]`:
```toml
auth-hook = "myapp.auth:default_hook" # applies to every backend
[specs.congress]
path = "/path/congress.spec"
auth-hook = "myapp.auth:congress_hook" # overrides the global for this one
[specs.internal]
path = "/path/internal.spec"
auth-hook = "myapp.auth:rbac_hook"
```
The hook receives an `AuthContext` (namespace, command, params, method) and returns an `AuthDecision`:
```python
# myapp/auth.py
from openbb_cli.auth import AuthContext, AuthDecision
from myapp.identity import current_user, get_token
def rbac_hook(ctx: AuthContext) -> AuthDecision:
user = current_user()
if not user.can_access(ctx.namespace, ctx.command):
return AuthDecision(
allow=False, deny_reason=f"{user.role} cannot call {ctx.command}"
)
return AuthDecision(headers={"Authorization": f"Bearer {get_token(user)}"})
```
Hooks may be sync or async; coroutines are awaited. Returned headers and query params merge on top of the dispatcher's static auth (hook wins on conflict). `allow=False` short-circuits the dispatch with an `AccessDenied` error response — no network call is made.
Introspection is gated by the same hook: `--list-commands` invokes it for every command and silently drops denied entries from the listing, and `--describe COMMAND` returns `AccessDenied` when the hook denies. RBAC implementations that hide endpoints therefore hide them everywhere — discovery, schema, and dispatch all see the same surface.
### Batch
NDJSON request/response over stdin/stdout, concurrent dispatch:
```bash
cat <<'EOF' | openbb --spec congress.spec --batch
{"id":"a","command":"bill","params":{"limit":5,"format":"json"}}
{"id":"b","command":"law","params":{"congress":119,"format":"json"}}
{"id":"c","command":"__commands__"}
EOF
```
### Introspection
```bash
openbb --spec congress.spec --list-commands # every command + short description
openbb --spec congress.spec --describe bill # full schema (params + response)
```
The same calls work as reserved commands in batch mode (`__commands__`, `__schema__`).
### Interactive REPL
```bash
openbb -i # in-process obb backend
openbb -i --spec congress.spec # spec-driven; no obb install required
openbb -i --server URL # live OpenAPI fetch
```
The REPL adds menu navigation (`bill`, `..`, `home`, `exit`), tab/auto-completion against parser flags, `--help` per command, the OBBject registry, and routine playback (`exe --file ...`).
## Configuration
Layered, all optional. Resolution order (lowest → highest priority):
1. Built-in defaults
2. `[tool.openbb-cli]` in nearest ancestor `pyproject.toml`
3. `~/.openbb_platform/openbb.toml` (user-global; same dir as `user_settings.json`)
4. `./openbb.toml` (project-local, walks up from CWD)
5. `--config PATH` (or `OPENBB_CLI_CONFIG`)
6. `~/.openbb_platform/.env` and `--env-file PATH`
7. `OPENBB_*` shell exports
8. CLI flags
Bootstrap a documented template:
```bash
openbb --print-config-template > ~/.openbb_platform/openbb.toml
```
Inspect what's currently active across all layers:
```bash
openbb --show-config
```
Schema:
```toml
# Backend / dispatch — pick one of these forms
server = "https://api.congress.gov" # live OpenAPI fetch
spec = "/path/to/api.spec" # single spec (flat surface)
batch-concurrency = 16
# Multi-spec: each table mounts under its own namespace
[specs.congress]
path = "/path/congress.spec"
[specs.congress.query]
api_key = "..."
[specs.usda]
path = "/path/usda.spec"
[specs.usda.headers]
Authorization = "Bearer ..."
[specs.usda.query]
api_key = "..."
# REPL display preferences (top-level shortcuts)
output-mode = "rich" # rich | json | tsv | html
flair = ":fox_face"
timezone = "America/New_York"
rich-style = "dark"
[headers] # global — applied to every backend
Authorization = "Bearer ..."
[query] # global — applied to every backend
api_key = "..."
[settings]
# Every other Settings field — same surface as the /settings/ REPL menu
allowed-number-of-rows = 50
use-prompt-toolkit = true
toolbar-hint = false
```
## Documentation
Full user documentation: **[docs.openbb.co/odp/cli](https://docs.openbb.co/odp/cli)**
API reference: [docs.openbb.co/platform](https://docs.openbb.co/platform)