Skip to main content
Every public 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 in src/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 logs and ravi sandbox run; others are kept off HTTP on purpose, such as ravi credentials broker exec and ravi pages password set. GET /api/v1/_meta/registry lists every command the daemon serves.
    • Superadmin-scoped commands (ravi metrics rollup, show and dates) have routes, but the gateway refuses them with 403 unless the daemon runs with RAVI_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 slack commands that take a <channel> argument, the --channel option (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.
  • 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 status counts 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 ravi daemon env and add:
The daemon reads ~/.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

You should see 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/....
The gateway shares its listener with Ravi’s webhook handlers. These variables control it, all in ~/.ravi/.env:
If RAVI_HTTP_HOST is not a loopback address and RAVI_GATEWAY_NETWORK_AUTHORIZED=1 is missing, the daemon refuses to start and exits. Bearer auth alone does not make a network-exposed gateway safe; keep it on loopback unless you have a reason not to.
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:
It prints the key once (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:
The JSON has the new 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:audit for the event, task and audit streams;
  • access:session:main for one session;
  • view:chat:<chatId> and view:instance:<instanceId> for the chat and instance streams.
Revoke a key you no longer need with ravi context revoke <contextId>. See Permissions for the permission model.

TypeScript

The package is ESM-only, needs zod 4 as a peer dependency, and runs anywhere fetch exists (Bun, Node 18+, browsers, Deno, edge runtimes).
With 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.
Commands that are dry-runs on the CLI are dry-runs in the SDK too. agents.delete, agents.reset, sessions.delete, sessions.reset and the other destructive calls only return a plan until you pass execute: true:

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.
Commands that return bytes, such as 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.
The 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.*.
Streams use RaviStreamClient, with the same six channels as TypeScript:
Errors are the same classes as TypeScript; Dart also throws 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 at packages/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 at GET /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:
The body is flat JSON: positional arguments and options side by side at the top level, never wrapped in { "args": ..., "options": ... }. For ravi instances routes add wa-main group:120363428558776322 main --priority 10:
The SDKs also send 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 in packages/ravi-os-sdk/src/generated and src/__tests__/fixtures/ are the reference.
  • Channel backend (@ravi-os/sdk/channel-backend, protocol ravi.channel.backend): createChannelBackendClient(ravi).ingress(...) hands an external message to a local agent. Each request carries an idempotencyKey; a retry with the same request returns duplicate and the original binding, and conflicting reuse fails. It needs a mutate:agent:<agentId> capability.
  • Runtime events (@ravi-os/sdk/channel-runtime-events, protocol ravi.channel.runtime-events): createChannelRuntimeClient(ravi) gives readback(...) to read a turn’s state after a reconnect and interrupt(...) to stop it. Adapters receive lifecycle events (state, assistant messages, tool summaries, approvals, terminal output) through a ChannelRuntimeEventSink.
  • Native drivers (@ravi-os/sdk/native-channel-driver, protocol ravi.channel.native-driver): a module that exports nativeChannelDriver runs inside Ravi’s channel runner. You declare it explicitly in ~/.ravi/.env:
Ravi never searches for or downloads driver code: it loads the installed package name or absolute 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.

Regenerating the clients

For contributors working in the Ravi repository:
Never edit generated files by hand; change the command in the registry and regenerate.