ravi command is also an HTTP endpoint on the daemon and a typed method in the TypeScript, Dart and Swift SDKs. Use them to build any frontend for your Ravi: a dashboard over sessions, tasks and contacts, a mobile or desktop app that talks to your agents, a browser extension, an internal tool or a test harness. Authentication, permissions, dry-runs and audit stay in Ravi, so your app does not reimplement them.
At the end of this page you will have the gateway running on the daemon host, a scoped context key for your app, and a client in TypeScript, Dart or Swift calling Ravi and streaming its events.
Generated from the CLI, with no drift
The CLI, the gateway’s command routes, the commands in the three SDKs and the OpenAPI spec come from one source: the command registry, built from the decorated command classes insrc/cli/commands/. The six SSE streams are the exception: they come from the gateway’s stream table, are generated into the three SDKs and checked with them, and are not in the OpenAPI spec.
- The gateway builds its routes from the registry when the daemon starts, so every command in the build the daemon runs is reachable over HTTP with the same name, arguments, options, return shape and dry-run, and goes through the same permission check. The exceptions:
- About 34 CLI-only commands have no route. Some are long-running or terminal-bound, such as
ravi channels logsandravi sandbox run; others are kept off HTTP on purpose, such asravi credentials broker execandravi pages password set.GET /api/v1/_meta/registrylists every command the daemon serves. - Superadmin-scoped commands (
ravi metrics rollup,showanddates) have routes, but the gateway refuses them with 403 unless the daemon runs withRAVI_SDK_GATEWAY_ALLOW_SUPERADMIN=1. - When an option has the same name as a positional argument, only the argument can be set over HTTP. On the
ravi slackcommands that take a<channel>argument, the--channeloption (which Ravi Slack config to use) never arrives, so over HTTP they find the Slack config only when the host has a single one enabled.
- About 34 CLI-only commands have no route. Some are long-running or terminal-bound, such as
- The SDKs and the OpenAPI spec are generated code, kept in the repository next to the CLI. The pre-push hook and every CI run compare them with a fresh generation from the registry and fail on any difference (
ravi sdk client check,sdk dart check,sdk swift check,sdk openapi check). A command’s arguments, options or declared return schema cannot change without its SDK methods, types and schemas changing with it. - Return types come from the return schema each command declares. Many commands still declare only a loose object, so the SDKs type their result as an untyped map (
Record<string, unknown>in TypeScript,Map<String, RaviJson>in Dart,[String: RaviJSON]in Swift), and changes to its fields are not caught. List commands often type the page but not the items, and Dart and Swift turn union returns into raw JSON.ravi sdk returns statuscounts the commands whose return schema is still weak.
The npm package is released by hand and the pub.dev package has not been republished since 0.1.0, so both can be older than the
ravi.bot@next you run and miss commands and options added since. The sources under packages/ always match the CLI of the same commit: to get every command your daemon serves, build the SDK from them (the Dart section shows a git dependency).Turn on the gateway
1
Add the HTTP settings to ~/.ravi/.env
Open the file with The daemon reads
ravi daemon env and add:~/.ravi/.env when it starts, and values there override the shell environment. Typing RAVI_HTTP_PORT=7777 on its own line before ravi daemon start does not reach the daemon.2
Restart the daemon
3
Check that it is listening
Webhook HTTP server ready with the URL. Webhook HTTP server disabled (set RAVI_HTTP_PORT to enable) means the port was not picked up. Requests now go to http://127.0.0.1:7777/api/v1/....~/.ravi/.env:
CORS is closed by default and never answers with
Access-Control-Allow-Origin: *. chrome-extension:// origins are always allowed.
Get a context key
Most routes need a runtime context key (rctx_*) as a bearer token. Create the first admin key on the daemon host:
rctx: rctx_...), with its context ID and expiry, and saves it as the default in ~/.ravi/credentials.json (mode 0600). Pass --print-only to skip saving. The command refuses to run if a live admin context already exists; revoke that one first with ravi context revoke <id>.
Do not ship the admin key in an app. Issue a narrower child key instead:
contextKey, its contextId, expiresAt and capabilities. --ttl defaults to 1h and cannot outlive the parent key. Each --allow entry is permission:objectType:objectId, for example:
view:system:events,view:system:tasks,view:system:auditfor the event, task and audit streams;access:session:mainfor one session;view:chat:<chatId>andview:instance:<instanceId>for the chat and instance streams.
ravi context revoke <contextId>. See Permissions for the permission model.
TypeScript
zod 4 as a peer dependency, and runs anywhere fetch exists (Bun, Node 18+, browsers, Deno, edge runtimes).
wait: true, sessions.send returns the agent’s reply, as ravi sessions send -w does. If you set the transport’s timeoutMs, make it longer than the turns you wait for. Without wait, the call returns once the prompt is queued, and the reply arrives on the session stream as a ravi.session.main.response event (see Streams).
Method names follow the CLI. Groups become nested objects and kebab-case becomes camelCase: ravi instances routes add is ravi.instances.routes.add(...), ravi daemon init-admin-key is ravi.daemon.initAdminKey(). Positional arguments come first, then one options object. Contact statuses are allowed, pending, blocked and discovered.
Streams
Streams always need a valid context key.
Errors and binary responses
Every transport throws the same classes from@ravi-os/sdk/errors: RaviContractError (the gateway returned the CLI’s error envelope, with code, retryable and exitCode), RaviAuthError (401), RaviPermissionError (403), RaviValidationError (other 4xx, with issues[]), RaviInternalError (5xx) and RaviTransportError (network or timeout), all subclasses of RaviError.
artifacts.blob, give you a raw Response on success:
Scripts your agent runs
A script that an agent runs inside its session can reuse the session’s own context instead of a stored key.createInheritedClient reads RAVI_CONTEXT_KEY and the gateway address (RAVI_BASE_URL, RAVI_GATEWAY_URL, or RAVI_HTTP_HOST + RAVI_HTTP_PORT) from the environment:
Exports
@ravi-os/sdk, @ravi-os/sdk/client, @ravi-os/sdk/transport/http, @ravi-os/sdk/streaming, @ravi-os/sdk/errors, @ravi-os/sdk/types, @ravi-os/sdk/schemas, @ravi-os/sdk/inherit, @ravi-os/sdk/channel-backend, @ravi-os/sdk/channel-runtime-events and @ravi-os/sdk/native-channel-driver.
Dart
ravi_sdk is pure Dart (no Flutter dependency), HTTP-only, and works on the Dart VM and the web.
dev branch matches ravi.bot@next. ravi_sdk: ^0.1.0 from pub.dev also works, but that release is older and lacks newer commands such as pages.ship and bases.*.
RaviStreamClient, with the same six channels as TypeScript:
RaviContractError when a response does not match the declared return shape. Destructive calls take an options object with execute: true, for example client.agents.delete('support', const AgentsDeleteOptions(execute: true)).
Swift
The Swift package lives in the Ravi repository atpackages/ravi-os-swift-sdk and is not published to a registry. Add it to your app as a local package from a clone, and depend on the RaviSDK product (iOS 16+, macOS 13+).
Any other language
The daemon serves the OpenAPI 3.1 spec of its own registry atGET /api/v1/_meta/openapi.json, without a key, and ravi sdk openapi emit --out ravi-openapi.json writes the same spec from the CLI. Give it to any OpenAPI generator to get a client in another language. Each operation maps to one ravi <group> <command>, and the API tab of these docs renders the spec of the dev branch.
Wire contract
Use this if you call the gateway without an SDK. Every command is a POST:{ "args": ..., "options": ... }. For ravi instances routes add wa-main group:120363428558776322 main --priority 10:
x-ravi-sdk-version and x-ravi-registry-hash, which identify the SDK release and the registry it was generated from.
Custom channels (advanced)
The TypeScript package also carries the contracts for connecting a channel Ravi does not support natively. These are for adapter authors; the types and fixtures inpackages/ravi-os-sdk/src/generated and src/__tests__/fixtures/ are the reference.
- Channel backend (
@ravi-os/sdk/channel-backend, protocolravi.channel.backend):createChannelBackendClient(ravi).ingress(...)hands an external message to a local agent. Each request carries anidempotencyKey; a retry with the same request returnsduplicateand the original binding, and conflicting reuse fails. It needs amutate:agent:<agentId>capability. - Runtime events (
@ravi-os/sdk/channel-runtime-events, protocolravi.channel.runtime-events):createChannelRuntimeClient(ravi)givesreadback(...)to read a turn’s state after a reconnect andinterrupt(...)to stop it. Adapters receive lifecycle events (state, assistant messages, tool summaries, approvals, terminal output) through aChannelRuntimeEventSink. - Native drivers (
@ravi-os/sdk/native-channel-driver, protocolravi.channel.native-driver): a module that exportsnativeChannelDriverruns inside Ravi’s channel runner. You declare it explicitly in~/.ravi/.env:
file:/// URL you list, validates the ABI version and capabilities, and fails closed on duplicates or mismatches. A driver runs with the channel runner’s access, so installing one is a trust decision.