# Ravi > Ravi gives AI agents their own identity (email inbox, real phone, encrypted vault) so they can sign up for services, receive verification codes, and keep the passwords they create. For teams whose agents have to act on the web, not just talk. Same identity fills the form, waits on signup OTP / magic link, handles returning-login 2FA, and stores the password across worktrees. Free $0 (1 inbox, 1 inbound-only phone). Docs: https://docs.ravi.app # Ravi Pricing > Ravi gives AI agents their own identity — phone numbers, email addresses, and credential storage. This is the current pricing. Every paid plan includes unlimited identities. Start free, no credit card required. Phone numbers are an optional add-on on paid plans. Prices in USD. Annual billing is charged once per year and costs the equivalent of ten months (two months free versus monthly). Sign up (all self-serve plans): https://dashboard.ravi.app/dashboard/login/ Enterprise or custom needs: email raunak@ravi.id ## How agents sign up Agents onboard over the API with no dashboard: start the OAuth 2.0 device flow, have your human approve one link, and poll to receive a durable management_key ("ravi_mgmt_..."). A new account starts empty — use that key to create your first identity, which provisions its email inbox and free phone. Full step-by-step guide (API, CLI, and dashboard): https://docs.ravi.app/getting-started/sign-up/ Human or dashboard signup: https://dashboard.ravi.app/dashboard/login/ ## Free — $0 forever For trying Ravi. - Unlimited identities - 1 inbox - 1 inbound-only phone How to start: Start free at https://dashboard.ravi.app/dashboard/login/ ## Developer For building and shipping. - Email only: $19/mo or $190/year - Email + Phones: $79/mo or $790/year - 10 inboxes - 10k emails / mo - 10 phone numbers (with the phones add-on) How to start: Start free at https://dashboard.ravi.app/dashboard/login/ ## Team For teams at scale. - Email only: $179/mo or $1,790/year - Email + Phones: $599/mo or $5,990/year - 150 inboxes - 150k emails / mo - 150 phone numbers (with the phones add-on) How to start: Start free at https://dashboard.ravi.app/dashboard/login/ ## Enterprise — Talk to us For custom needs. - Custom inboxes & phone numbers - Custom domains - Compliance & security review - Dedicated support How to start: email raunak@ravi.id ## Introduction ## What is Ravi? Ravi gives AI agents isolated communication and credential surfaces: identities, email inboxes, phone numbers, contacts, API keys, webhooks, passwords, and secrets. The important product boundary is simple: - The backend API runs at `https://api.ravi.app`. - The dashboard runs separately from this docs site. - Public API docs live here on `docs.ravi.app`. - Machine-readable API reference is published at `https://docs.ravi.app/openapi.json`. ## Start Here | Goal | Read | | --- | --- | | Sign up and create an identity | [Sign Up](/getting-started/sign-up/) | | Your agent hit a login wall | [Your agent hit a login wall](/getting-started/login-wall/) | | When an agent needs its own identity | [When to Use](/getting-started/when-to-use/) | | Signup OTP / magic link | [Signup OTP](/getting-started/signup-otp/) | | Give the agent an email so it can sign up | [Give the agent an email so it can sign up](/getting-started/agent-email-for-signup/) | | The site texted a code and the agent has no phone | [The site texted a code and the agent has no phone](/getting-started/agent-phone-code/) | | Returning-login 2FA | [Returning Login 2FA](/getting-started/returning-login-2fa/) | | Passwords across worktrees | [Credentials Across Worktrees](/getting-started/credentials-across-worktrees/) | | The agent made a password yesterday and this chat does not have it | [The agent made a password yesterday and this chat does not have it](/getting-started/lost-password-next-worktree/) | | How an agent signs up for a website | [How an agent signs up for a website](/getting-started/sign-up-for-a-service/) | | Understand auth | [Authentication](/getting-started/authentication/) | | See the API shape | [API Overview](/api/overview/) | | Find endpoint groups | [API Endpoints](/api/endpoints/) | | Use the OpenAPI contract | [API Reference](/api/reference/) | | Use a first-party client | [SDK Overview](/sdk/overview/) | | Use email | [Email](/core-concepts/email/) | | Use phone and SMS | [Phone & SMS](/core-concepts/phone-and-sms/) | | Store passwords and secrets | [Credential Vault](/core-concepts/credential-vault/) | | Install the CLI | [Installation](/getting-started/installation/) | ## Core Concepts An **Identity** is the scoped persona an agent operates as. Identity-scoped API keys and requests can only operate on the resources available to that identity. An identity can own communication resources: ```text Identity ├── Email inbox ├── Phone number ├── Contacts ├── Password entries └── Secret entries ``` Ravi also supports management-level keys for account or workspace operations, plus identity-level keys for agent runtime work. The CLI is one identity per machine (`~/.ravi/config.json` is not a multi-agent runtime). Multiple agents on one host use the HTTP API with per-identity `ravi_id_...` keys, or the Cursor Connect plugin. ## Machine-Readable Docs Use these text endpoints when handing Ravi docs to an agent: ```bash curl https://docs.ravi.app/llms.txt curl https://docs.ravi.app/llms-full.txt ``` `/llms.txt` is a compact index. `/llms-full.txt` includes the full markdown body for every public docs page. Every docs HTML page also has a plain-text sibling. For example: ```text https://docs.ravi.app/api/reference.txt https://docs.ravi.app/sdk/typescript.txt https://docs.ravi.app/sdk/python.txt https://docs.ravi.app/core-concepts/identities.txt ``` ## Your agent hit a login wall The agent hits a signup or login and has to hand the human the browser. If it had its own Ravi identity (inbox, phone, vault), it would not. Same identity fills the form, waits on the code, and stores the password. That identity is the agent's `@ravi.app` inbox, phone, and vault. Nobody gives an agent inbox + phone + vault as one identity. An agent can browse and call APIs, then die at “enter email” or “we texted a code.” That is the moment to give it its own identity. One persona: inbox, real phone, encrypted vault. Same identity fills the form, waits on the code, and stores the password so the next worktree does not ask a human again. Start at [Sign Up](/getting-started/sign-up/) (`https://docs.ravi.app/getting-started/sign-up.txt`) and [Authentication](/getting-started/authentication/) (`https://docs.ravi.app/getting-started/authentication.txt`). ## Next Steps - [How an agent signs up for a website](/getting-started/sign-up-for-a-service/) - [Sign Up](/getting-started/sign-up/) - [Authentication](/getting-started/authentication/) - [Status](https://ravi.app/status) — API and docs availability ## When to Use A personal-agent setup (many agents, one human) where the agent hits a login and has to stop and hand the human the browser. If each agent had its own Ravi identity (inbox, phone, vault), it would not. Same identity fills the form, waits on the signup OTP, handles returning-login 2FA, and stores the password for the next worktree. Ravi gives AI agents their own identity (email inbox, real phone, encrypted vault) so they can sign up for services, receive verification codes, and keep the passwords they create. For teams whose agents have to act on the web, not just talk. Creating that identity: [Sign Up](/getting-started/sign-up/) (`https://docs.ravi.app/getting-started/sign-up.txt`). Keys and device-code login: [Authentication](/getting-started/authentication/) (`https://docs.ravi.app/getting-started/authentication.txt`). ## Chat vs identity A chat-only agent answers questions. It does not own an email address, a phone number, or a password store. That is enough for research and drafting. It is not enough when the agent has to **act on the web**: create an account, confirm a signup email, complete a returning login, or reuse a password it created in another session. Give the agent its own **identity** when it must: - Fill a signup form as a real person-shaped account - Wait on the signup OTP or magic link the service sends - Handle returning-login 2FA by receiving the second factor - Store the password it created so a later worktree can use it An identity is the persona the agent operates as. It owns an email inbox, a phone number, and an encrypted vault. Create one with a management key (`ravi_mgmt_...`) after signup, then mint an identity-scoped key (`ravi_id_...`) for the runtime. Device-code login (`ravi auth login`) against `https://api.ravi.app` is the local CLI path. The CLI is one identity per machine (`~/.ravi/config.json` is not a multi-agent runtime). Multiple agents on one host use the HTTP API with per-identity `ravi_id_...` keys. ## Next Steps - [Your agent hit a login wall](/getting-started/login-wall/) (`https://docs.ravi.app/getting-started/login-wall.txt`) - [How an agent signs up for a website](/getting-started/sign-up-for-a-service/) (`https://docs.ravi.app/getting-started/sign-up-for-a-service.txt`) - [Sign Up](/getting-started/sign-up/) — account, first identity, and keys (`https://docs.ravi.app/getting-started/sign-up.txt`) - [Authentication](/getting-started/authentication/) — `ravi_mgmt_...` / `ravi_id_...` and device-code login (`https://docs.ravi.app/getting-started/authentication.txt`) - [Status](https://ravi.app/status) — API and docs availability ## Sign Up Ravi is built for autonomous agents. An agent can onboard end to end by itself and receive its API keys back programmatically. The only human step is a one-time OAuth approval. There is no dashboard requirement. A new account starts empty — **no identity is provisioned automatically.** After signing up you use your management key to create the first identity, which provisions its email inbox and free phone number at that moment. ## 1. Sign Up Pick whichever path fits the runtime. All three create the same account and yield the same keys. ### Over the API (device authorization grant) The agent-native path. Uses the OAuth 2.0 Device Authorization Grant (RFC 8628): the agent starts the flow, hands its human one link to approve, and polls for the result. ```bash # 1. Start the flow. curl -X POST https://api.ravi.app/api/auth/device/ # → { "device_code": "...", "user_code": "ABCD-1234", # "verification_uri": "https://api.ravi.app/api/auth/device/verify/", # "expires_in": 600, "interval": 5 } ``` Hand your human the `verification_uri` and `user_code` from that response. They open the link and approve with Google. **First-time approval creates the account automatically.** ```bash # 2. Poll for the result. `wait` long-polls up to 120s. curl -X POST https://api.ravi.app/api/auth/device/token/ \ -H "Content-Type: application/json" \ -d '{"device_code": "...", "wait": true}' # Pending: { "error": "authorization_pending" } # Success: { "access": "...", "refresh": "...", # "management_key": "ravi_mgmt_...", "user": {...} } ``` Store the `management_key` (`ravi_mgmt_...`) in your secret manager. It is long-lived and revocable. That is all you need to continue. ### With the CLI `ravi auth login` runs the same device flow and opens the browser for you. The CLI stores the long-lived management key in `~/.ravi/config.json`. ```bash ravi auth login ``` See [Installation](/getting-started/installation/) to get the CLI. ### In the dashboard (human) A human can sign in with Google at `https://dashboard.ravi.app/dashboard/login/` and create keys at `https://dashboard.ravi.app/dashboard/api-keys/`. ## 2. Create Your First Identity An **identity** is the persona your agent operates as — it owns an email inbox, a phone number, contacts, and credentials. Create one with the management key. This is where first value happens: the inbox and free phone are provisioned now. ```bash export RAVI_MGMT_KEY="ravi_mgmt_..." curl -X POST https://api.ravi.app/api/identities/ \ -H "Authorization: Bearer $RAVI_MGMT_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "research-agent", "email_identifier": "research", "provision_phone": true }' # → identity with uuid, email address, and phone number ``` | Field | Meaning | | --- | --- | | `name` | Label for the identity (required) | | `email_identifier` | Email local part; omit to auto-generate | | `provision_phone` | Also provision a phone number | With the CLI (one identity per machine — this sets the shared `~/.ravi/config.json` active identity, and is not how you run many agents): ```bash ravi identity create --name "research-agent" ravi identity use ``` Multiple agents on one host must use the HTTP API with per-identity `ravi_id_...` keys (or the Cursor Connect plugin). See [Authentication](/getting-started/authentication/). ## 3. Scope a Runtime With an Identity Key (recommended) For an autonomous agent, prefer an **identity-scoped key** (`ravi_id_...`): losing one key does not expose the whole account. Mint one from the management key: ```bash curl -X POST https://api.ravi.app/api/auth/keys/identity/ \ -H "Authorization: Bearer $RAVI_MGMT_KEY" \ -H "Content-Type: application/json" \ -d '{"identity": "", "label": "research-runtime"}' # → the full ravi_id_... key, shown once ``` | Key prefix | Scope | Use | | --- | --- | --- | | `ravi_mgmt_...` | Account + all identities | Create identities, mint identity keys, manage the account | | `ravi_id_...` | One identity | Give an agent runtime access to exactly one identity | The key is an **auth fence** — it defines what you may touch; the caller chooses the identity. A management key targets an identity with `?identity=`; an identity key is already fenced to its identity. ## 4. Use the Identity ```bash export RAVI_API_KEY="ravi_id_..." # or the management key export IDENTITY_UUID="" # Confirm who the key is. curl https://api.ravi.app/api/health/whoami/ \ -H "Authorization: Bearer $RAVI_API_KEY" # Read the identity's email and SMS inboxes. curl "https://api.ravi.app/api/email-inbox/?identity=$IDENTITY_UUID" \ -H "Authorization: Bearer $RAVI_API_KEY" curl "https://api.ravi.app/api/sms-inbox/?identity=$IDENTITY_UUID" \ -H "Authorization: Bearer $RAVI_API_KEY" ``` With the CLI, on this machine's active identity: ```bash ravi get email # the identity's email address ravi get phone # the identity's phone number ``` ## Next Steps - [Quickstart](/getting-started/quickstart/) — make a first authenticated request - [Authentication](/getting-started/authentication/) — key types and device-code login - [Signup OTP](/getting-started/signup-otp/) — identity inbox and phone receive the signup code - [How an agent signs up for a website](/getting-started/sign-up-for-a-service/) — fill a service form, wait on the code, store the password - [API Endpoints](/api/endpoints/) — the full route map - [Email](/core-concepts/email/) and [Phone & SMS](/core-concepts/phone-and-sms/) — receive verification codes and act ## Installation ## Install The CLI On macOS or Linux, install the CLI from the Ravi Homebrew tap: ```bash brew install ravi-hq/tap/ravi ``` Prebuilt release archives are published from the CLI repository: ```text https://github.com/ravi-hq/cli/releases ``` ## Build From Source ```bash git clone https://github.com/ravi-hq/cli.git cd cli make build API_URL=https://api.ravi.app ``` The built binary is `./ravi`. ## Verify The Backend Before debugging local auth or CLI state, verify that the production API is reachable: ```bash curl https://api.ravi.app/api/health/ping/ ``` ## Authenticate For local CLI use: ```bash ravi auth login ``` The CLI is one identity per machine. Shared `~/.ravi/config.json` is not a multi-agent runtime. Multiple agents on one host must use the HTTP API with per-identity `ravi_id_...` keys (or the Cursor Connect plugin). For automated use, prefer an API key created for the narrowest useful scope: - `ravi_id_...` for one identity - `ravi_mgmt_...` for broader management workflows Pass API keys as bearer tokens when calling the REST API directly: ```bash curl https://api.ravi.app/api/health/whoami/ \ -H "Authorization: Bearer ravi_mgmt_..." ``` ## Next Steps - [Authentication](/getting-started/authentication/) - [API Overview](/api/overview/) - [API Endpoints](/api/endpoints/) ## Quickstart ## 1. Check The Backend ```bash curl https://api.ravi.app/api/health/ping/ ``` You should receive a JSON response from the production Django backend. ## 2. Choose Authentication For agent runtimes, use the narrowest API key that works: - `ravi_id_...` for one identity - `ravi_mgmt_...` for management operations Pass the key as a bearer token: ```bash export RAVI_API_KEY="ravi_id_..." curl https://api.ravi.app/api/health/whoami/ \ -H "Authorization: Bearer $RAVI_API_KEY" ``` For local CLI login, use: ```bash ravi auth login ``` The CLI is one identity per machine. Shared `~/.ravi/config.json` is not a multi-agent runtime. Multiple agents on one host must use the HTTP API with per-identity `ravi_id_...` keys (or the Cursor Connect plugin). ## 3. Inspect Available Resources Once authenticated, list the resources you need: ```bash # Account-level: list the identities the key can reach. curl https://api.ravi.app/api/identities/ \ -H "Authorization: Bearer $RAVI_API_KEY" # Per-identity: the caller chooses the identity via ?identity=. curl "https://api.ravi.app/api/email-inbox/?identity=$IDENTITY_UUID" \ -H "Authorization: Bearer $RAVI_API_KEY" curl "https://api.ravi.app/api/sms-inbox/?identity=$IDENTITY_UUID" \ -H "Authorization: Bearer $RAVI_API_KEY" ``` The API key is an auth fence only — the caller picks the identity. A management key with `?identity=` targets one identity; an identity-scoped key is already fenced to its identity, and `?identity=` must match it. ## 4. Use The API Reference Use [API Endpoints](/api/endpoints/) as the route map and `https://docs.ravi.app/openapi.json` for generated request and response schemas. ## Next Steps - [Authentication](/getting-started/authentication/) - [API Overview](/api/overview/) - [API Endpoints](/api/endpoints/) ## Authentication ## Overview Ravi supports two practical authentication paths: 1. **API-key auth** for programmatic callers and agent runtimes. 2. **Device-code login** for the CLI and local setup. API keys are the better default for backend services and agent runtimes. Device-code login is useful for CLI and local user setup. See [Sign Up](/getting-started/sign-up/) for the agent-native device flow. ## API Key Types | Key prefix | Scope | Use | | --- | --- | --- | | `ravi_mgmt_...` | Account management | Managing resources for a user/account | | `ravi_id_...` | One identity | Agent runtime operations scoped to one identity | Pass either key as a bearer token: ```http Authorization: Bearer ravi_id_... ``` Identity-scoped keys should be preferred for autonomous agents because losing one key does not expose the whole account. The key is an auth fence only — in the SDKs, the caller chooses an identity and works on its `identity.email` and `identity.phone` channels, which send `?identity=` on every request. ## Creating API Keys The simplest path is the dashboard: ```text https://dashboard.ravi.app/dashboard/api-keys/ ``` You can also manage keys through authenticated backend routes: | Method | Endpoint | | --- | --- | | `GET` / `POST` | `/api/auth/keys/management/` | | `GET` / `DELETE` | `/api/auth/keys/management//` | | `GET` / `POST` | `/api/auth/keys/identity/` | | `GET` / `DELETE` | `/api/auth/keys/identity//` | Key create responses reveal the full key once. Store it immediately in your secret manager. ## Device-Code Login `ravi auth login` runs the OAuth 2.0 Device Authorization Grant (RFC 8628) against `https://api.ravi.app`. The CLI auth commands are `login`, `logout`, and `status` only. ```bash ravi auth login ``` The API returns a verification URL at `https://api.ravi.app/api/auth/device/verify/`. A public front door may be `https://ravi.id/device`. Do not open `https://ravi.app/api/auth/device/verify/`. On success the CLI stores long-lived `ravi_mgmt_...` / `ravi_id_...` keys in `~/.ravi/config.json`. The CLI is one identity per machine. Shared `~/.ravi/config.json` is not a multi-agent runtime. Do not run many agents by switching the CLI identity. Multiple agents on one host must use the HTTP API with per-identity `ravi_id_...` keys (or the Cursor Connect plugin). The backend routes behind that flow are: | Method | Endpoint | Description | | --- | --- | --- | | `POST` | `/api/auth/device/` | Create a device code | | `POST` | `/api/auth/device/token/` | Poll for completion | | `GET` / `POST` | `/api/auth/device/verify/` | Browser verification on `api.ravi.app` | | `GET` | `/api/auth/device/callback/` | OAuth callback | ## Base URL Use the production API host: ```text https://api.ravi.app ``` Example: ```bash curl https://api.ravi.app/api/health/whoami/ \ -H "Authorization: Bearer ravi_mgmt_..." ``` ## Next Steps - [Sign Up](/getting-started/sign-up/) - [Returning Login 2FA](/getting-started/returning-login-2fa/) - [Credentials Across Worktrees](/getting-started/credentials-across-worktrees/) - [API Overview](/api/overview/) - [API Endpoints](/api/endpoints/) - [Security Model](/security/security-model/) ## Signup OTP Ravi gives AI agents their own identity (email inbox, real phone, encrypted vault) so they can sign up for services, receive verification codes, and keep the passwords they create. For teams whose agents have to act on the web, not just talk. ## Wait on the signup OTP or magic link Same identity fills the form, then waits on the signup OTP or magic link. The service sends that message to the identity's email inbox or phone number. The agent reads it there. This is the verification the service already sends, delivered to an inbox the agent owns. Create the identity first, then use an identity-scoped key (`ravi_id_...`) or a management key (`ravi_mgmt_...`) with `?identity=`. Device-code login (`ravi auth login`) against `https://api.ravi.app` stores keys in `~/.ravi/config.json` for local CLI use. The CLI is one identity per machine. ```bash export RAVI_API_KEY="ravi_id_..." export IDENTITY_UUID="" # Email: magic links and email OTPs land here. curl "https://api.ravi.app/api/email-inbox/?identity=$IDENTITY_UUID" \ -H "Authorization: Bearer $RAVI_API_KEY" # SMS: phone OTPs land here. curl "https://api.ravi.app/api/sms-inbox/?identity=$IDENTITY_UUID" \ -H "Authorization: Bearer $RAVI_API_KEY" ``` Poll until the message arrives, then complete signup with the code or link the service sent to that identity. Signup and first identity: [Sign Up](/getting-started/sign-up/) (`https://docs.ravi.app/getting-started/sign-up.txt`). Key types and device-code login: [Authentication](/getting-started/authentication/) (`https://docs.ravi.app/getting-started/authentication.txt`). ## Next Steps - [Give the agent an email so it can sign up](/getting-started/agent-email-for-signup/) - [The site texted a code and the agent has no phone](/getting-started/agent-phone-code/) - [Sign Up](/getting-started/sign-up/) - [Authentication](/getting-started/authentication/) - [Status](https://ravi.app/status) — API and docs availability ## Returning Login 2FA Ravi gives AI agents their own identity (email inbox, real phone, encrypted vault) so they can sign up for services, receive verification codes, and keep the passwords they create. For teams whose agents have to act on the web, not just talk. ## The identity receives the second factor Same identity handles returning-login 2FA. When the agent signs back in, the service sends a second factor to the phone number or email address on the account — the identity's own phone and inbox. The agent reads that message and submits the code. The agent owns a real phone and email, so it can receive the legitimate 2FA or OTP the service sends. Use the same identity-scoped key (`ravi_id_...`) you used at signup, or a management key (`ravi_mgmt_...`) with `?identity=`. Device-code login (`ravi auth login`) against `https://api.ravi.app` is the local CLI path. The CLI is one identity per machine (`~/.ravi/config.json` is not a multi-agent runtime). ```bash export RAVI_API_KEY="ravi_id_..." export IDENTITY_UUID="" # SMS second factors land on the identity's phone. curl "https://api.ravi.app/api/sms-inbox/?identity=$IDENTITY_UUID" \ -H "Authorization: Bearer $RAVI_API_KEY" # Email second factors land on the identity's inbox. curl "https://api.ravi.app/api/email-inbox/?identity=$IDENTITY_UUID" \ -H "Authorization: Bearer $RAVI_API_KEY" ``` Poll until the second factor arrives, then complete the login the service challenged. Keys and device-code login: [Authentication](/getting-started/authentication/) (`https://docs.ravi.app/getting-started/authentication.txt`). Creating that identity: [Sign Up](/getting-started/sign-up/) (`https://docs.ravi.app/getting-started/sign-up.txt`). ## Next Steps - [The site texted a code and the agent has no phone](/getting-started/agent-phone-code/) - [Authentication](/getting-started/authentication/) - [Sign Up](/getting-started/sign-up/) - [Status](https://ravi.app/status) — API and docs availability ## Credentials Across Worktrees Ravi gives AI agents their own identity (email inbox, real phone, encrypted vault) so they can sign up for services, receive verification codes, and keep the passwords they create. For teams whose agents have to act on the web, not just talk. ## Store the password across worktrees Same identity stores the password it created in the encrypted vault, so the next worktree can retrieve it instead of signing up again. The vault lives on the identity in the Ravi API, not in the git worktree. A new checkout that authenticates as that identity reads the same password. After signup, mint an identity-scoped key (`ravi_id_...`) and pass it into each runtime. A management key (`ravi_mgmt_...`) can also target the identity with `?identity=`. Device-code login (`ravi auth login`) against `https://api.ravi.app` stores keys in `~/.ravi/config.json` for local CLI use. The CLI is one identity per machine — shared `~/.ravi/config.json` is not a multi-agent runtime. Multiple agents on one host use the HTTP API with per-identity `ravi_id_...` keys. ```bash export RAVI_API_KEY="ravi_id_..." export IDENTITY_UUID="" # Save the password the agent just created. curl -X POST "https://api.ravi.app/api/passwords/?identity=$IDENTITY_UUID" \ -H "Authorization: Bearer $RAVI_API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "acme.com", "username": "agent", "password": "..."}' # In another worktree, with the same identity key, read it back. curl "https://api.ravi.app/api/passwords/?identity=$IDENTITY_UUID" \ -H "Authorization: Bearer $RAVI_API_KEY" ``` Do not leave the password in the worktree. Store it on the identity, then authenticate the next runtime with the same `ravi_id_...` key. Signup and first identity: [Sign Up](/getting-started/sign-up/) (`https://docs.ravi.app/getting-started/sign-up.txt`). Key types and device-code login: [Authentication](/getting-started/authentication/) (`https://docs.ravi.app/getting-started/authentication.txt`). ## Next Steps - [The agent made a password yesterday and this chat does not have it](/getting-started/lost-password-next-worktree/) - [Sign Up](/getting-started/sign-up/) - [Authentication](/getting-started/authentication/) - [Status](https://ravi.app/status) — API and docs availability ## Give the agent an email so it can sign up The agent hits a signup or login and has to hand the human the browser. If it had its own Ravi identity (inbox, phone, vault), it would not. Same identity fills the form, waits on the code, and stores the password. That identity is the agent's `@ravi.app` inbox, phone, and vault. If a site asks for an email and the agent does not have one, it has to stop and ask you. Ravi gives the agent a real inbox. Use that `@ravi.app` address on the form. Read the verification mail from the same identity. Do not use a human’s inbox for agent signup. Creating that identity: [Sign Up](/getting-started/sign-up/) (`https://docs.ravi.app/getting-started/sign-up.txt`). Waiting on the verification mail: [Signup OTP](/getting-started/signup-otp/) (`https://docs.ravi.app/getting-started/signup-otp.txt`). Keys: [Authentication](/getting-started/authentication/) (`https://docs.ravi.app/getting-started/authentication.txt`). ## Next Steps - [Signup OTP](/getting-started/signup-otp/) - [How an agent signs up for a website](/getting-started/sign-up-for-a-service/) - [Sign Up](/getting-started/sign-up/) - [Authentication](/getting-started/authentication/) - [Status](https://ravi.app/status) — API and docs availability ## The site texted a code and the agent has no phone The agent hits a signup or login and has to hand the human the browser. If it had its own Ravi identity (inbox, phone, vault), it would not. Same identity fills the form, waits on the code, and stores the password. That identity is the agent's `@ravi.app` inbox, phone, and vault. Phone-gated signup is the wall after email. The agent needs a real number on the same identity as the inbox, then it reads the SMS and finishes. US numbers. Creating that identity: [Sign Up](/getting-started/sign-up/) (`https://docs.ravi.app/getting-started/sign-up.txt`). Returning-login codes on the same phone: [Returning Login 2FA](/getting-started/returning-login-2fa/) (`https://docs.ravi.app/getting-started/returning-login-2fa.txt`). Keys: [Authentication](/getting-started/authentication/) (`https://docs.ravi.app/getting-started/authentication.txt`). ## Next Steps - [Returning Login 2FA](/getting-started/returning-login-2fa/) - [Sign Up](/getting-started/sign-up/) - [Authentication](/getting-started/authentication/) - [Status](https://ravi.app/status) — API and docs availability ## The agent made a password yesterday and this chat does not have it The agent hits a signup or login and has to hand the human the browser. If it had its own Ravi identity (inbox, phone, vault), it would not. Same identity fills the form, waits on the code, and stores the password. That identity is the agent's `@ravi.app` inbox, phone, and vault. A new worktree or a new agent session does not remember what the last one typed. Put the password in the vault on the same identity that owns the inbox and phone. Next session logs in without a human. How the vault spans worktrees: [Credentials Across Worktrees](/getting-started/credentials-across-worktrees/) (`https://docs.ravi.app/getting-started/credentials-across-worktrees.txt`). Keys: [Authentication](/getting-started/authentication/) (`https://docs.ravi.app/getting-started/authentication.txt`). ## Next Steps - [Credentials Across Worktrees](/getting-started/credentials-across-worktrees/) - [Authentication](/getting-started/authentication/) - [Status](https://ravi.app/status) — API and docs availability ## How an agent signs up for a website The agent hits a signup or login and has to hand the human the browser. If it had its own Ravi identity (inbox, phone, vault), it would not. Same identity fills the form, waits on the code, and stores the password. That identity is the agent's `@ravi.app` inbox, phone, and vault. 1. Get an identity (inbox, phone, vault). 2. Open the site. 3. Fill email from the identity (`@ravi.app`). 4. Wait on the OTP or magic link in that inbox, or the SMS on that phone. 5. Store the password in the vault. 6. Next time, use the same identity. [Sign Up](/getting-started/sign-up/) (`https://docs.ravi.app/getting-started/sign-up.txt`). [Authentication](/getting-started/authentication/) (`https://docs.ravi.app/getting-started/authentication.txt`). [Signup OTP](/getting-started/signup-otp/) (`https://docs.ravi.app/getting-started/signup-otp.txt`). [Returning Login 2FA](/getting-started/returning-login-2fa/) (`https://docs.ravi.app/getting-started/returning-login-2fa.txt`). ## Next Steps - [Your agent hit a login wall](/getting-started/login-wall/) - [Give the agent an email so it can sign up](/getting-started/agent-email-for-signup/) - [The site texted a code and the agent has no phone](/getting-started/agent-phone-code/) - [Sign Up](/getting-started/sign-up/) - [Authentication](/getting-started/authentication/) - [Status](https://ravi.app/status) — API and docs availability ## Troubleshooting ## Backend Reachability Use: ```bash curl https://api.ravi.app/api/health/ping/ ``` to verify the production backend is reachable. ## Route Not Found Use [API Endpoints](/api/endpoints/) for supported route families and `https://docs.ravi.app/openapi.json` for exact request and response schemas. ## 401 Unauthorized Check that the request includes a bearer token or API key: ```http Authorization: Bearer ravi_id_... ``` Management keys start with `ravi_mgmt_...`; identity-scoped keys start with `ravi_id_...`. ## Wrong Host Use `https://api.ravi.app` for API calls and `https://docs.ravi.app` for documentation. `docs.ravi.app` is a static docs site. It does not serve API requests. ## Identities An identity is the root object in Ravi. Each one bundles an email inbox, an optional phone number, a contact book, and a credential vault into a single persona your agent owns and operates as — over the CLI, the Python SDK, and the TypeScript SDK. Start here: create an identity, then everything else (email, phone, contacts, vault) hangs off it. You create and list identities through the account-level client, so the examples below show that setup once with your API key. ## Create an identity Give it a name and an email handle. Add `provision_phone` to get a real phone number too. You get back an identity with `identity.email.address` set, and `identity.phone.number` when you provisioned one. CLI: ```bash ravi identity create \ --name "Support Agent" \ --email-identifier support \ --provision-phone ``` Python: ```python from ravi import Ravi ravi = Ravi(api_key="ravi_mgmt_...") identity = ravi.identities.create( name="Support Agent", email_identifier="support", provision_phone=True, ) print(identity.email.address) # support@ravi.app print(identity.phone.number) # +15551234567 ``` TypeScript: ```typescript import { Ravi } from "@ravi-hq/sdk"; const ravi = new Ravi({ apiKey: process.env.RAVI_API_KEY! }); const identity = await ravi.identities.create({ name: "Support Agent", email_identifier: "support", provision_phone: true, }); console.log(identity.email.address); // support@ravi.app console.log(identity.phone?.number); // +15551234567 ``` ## List identities See every identity on your account. CLI: ```bash ravi identity list ``` Python: ```python ravi = Ravi(api_key="ravi_mgmt_...") for identity in ravi.identities.list(): print(identity.name, identity.email.address) ``` TypeScript: ```typescript const ravi = new Ravi({ apiKey: process.env.RAVI_API_KEY! }); const identities = await ravi.identities.list(); for (const identity of identities) { console.log(identity.name, identity.email.address); } ``` ## Get one identity Fetch a single identity by its uuid. On the CLI, this sets the active identity so later commands operate on it. CLI: ```bash ravi identity use ``` Python: ```python identity = ravi.identities.get("") print(identity.email.address) ``` TypeScript: ```typescript const identity = await ravi.identities.get(""); console.log(identity.email.address); ``` ## Rename an identity Update the display name. (Python and TypeScript only.) Python: ```python identity = ravi.identities.update("", name="Sales Agent") ``` TypeScript: ```typescript const identity = await ravi.identities.update("", { name: "Sales Agent", }); ``` ## Add a phone number later Created an identity without a phone? Provision one at any time and `identity.phone.number` fills in. (Python and TypeScript only.) Python: ```python identity = ravi.identities.provision_phone("") print(identity.phone.number) ``` TypeScript: ```typescript const identity = await ravi.identities.provisionPhone(""); console.log(identity.phone?.number); ``` --- Once you have an identity, put it to work: give it an [email inbox](/core-concepts/email/) or a [phone number](/core-concepts/phone-and-sms/). Looking for exact request and response fields? See the full [API reference](/api/reference/). ## Email Every identity comes with a real email inbox it fully controls. Your agent can send mail from it, read what arrives, reply in-thread, and block until a verification email lands — the same inbox works over the CLI, the Python SDK, and the TypeScript SDK. Every example below runs against one identity. Grab it once (or set it as the active identity in the CLI) and the rest follow. Need one first? See [Identities](/core-concepts/identities/). CLI: ```bash ravi identity use ``` Python: ```python from ravi import Ravi ravi = Ravi(api_key="ravi_id_...") identity = ravi.identities.get("") ``` TypeScript: ```typescript import { Ravi } from "@ravi-hq/sdk"; const ravi = new Ravi({ apiKey: process.env.RAVI_API_KEY! }); const identity = await ravi.identities.get(""); ``` ## Send an email Compose and send from the identity's address. Plain text or HTML, with optional cc, bcc, and attachments. CLI: ```bash ravi email compose \ --to user@example.com \ --subject "Hello" \ --body "Hi from my agent" ``` Python: ```python msg = identity.email.send( to="user@example.com", subject="Hello", body="Hi from my agent", ) ``` TypeScript: ```typescript const msg = await identity.email.send( "user@example.com", "Hello", "Hi from my agent", ); ``` ### Attach files Pass local file paths — Ravi uploads each one (up to 10 MB) and attaches it for you. Works the same on send, reply, and forward. CLI: ```bash ravi email compose \ --to user@example.com \ --subject "Invoice" \ --body "Invoice attached." \ --attach ./invoice.pdf ``` Python: ```python msg = identity.email.send( to="user@example.com", subject="Invoice", body="Invoice attached.", attachments=["./invoice.pdf"], ) ``` TypeScript: ```typescript const msg = await identity.email.send( "user@example.com", "Invoice", "Invoice attached.", { attachments: ["./invoice.pdf"] }, ); ``` ## Read the inbox List messages as a flat feed — filter to unread to see only what needs attention. CLI: ```bash ravi message email --unread ``` Python: ```python inbox = identity.email.inbox(unread=True) for msg in inbox: print(msg.subject, msg.from_email) ``` TypeScript: ```typescript const inbox = await identity.email.inbox({ unread: true }); for (const msg of inbox) { console.log(msg.subject, msg.from_email); } ``` Prefer conversations grouped by subject? List threads instead. CLI: ```bash ravi inbox email ``` Python: ```python threads = identity.email.threads() ``` TypeScript: ```typescript const threads = await identity.email.threads(); ``` ## Read one message Fetch a single message by id to see its full body and metadata. CLI: ```bash ravi message email ``` Python: ```python msg = identity.email.get("") print(msg.subject) print(msg.body) ``` TypeScript: ```typescript const msg = await identity.email.get(""); console.log(msg.subject); console.log(msg.body); ``` ## Reply and forward Messages carry their own actions — reply to the sender, reply to everyone, or forward. Threading is handled for you. CLI: ```bash ravi email reply --body "Thanks — got it!" ``` Python: ```python msg = identity.email.get("") msg.reply(body="Thanks — got it!") ``` TypeScript: ```typescript const msg = await identity.email.get(""); await msg.reply("Thanks — got it!"); ``` CLI: ```bash ravi email reply-all --body "Thanks, all!" ``` Python: ```python msg.reply_all(body="Thanks, all!") ``` TypeScript: ```typescript await msg.replyAll("Thanks, all!"); ``` CLI: ```bash ravi email forward --to ops@acme.com --body "FYI" ``` Python: ```python msg.forward(to="ops@acme.com") ``` TypeScript: ```typescript await msg.forward("ops@acme.com"); ``` ## Wait for a verification email The move that makes agents work: trigger a signup somewhere, then block until the confirmation email arrives. `wait_for` polls the inbox and returns the first message that matches. Python: ```python # Kick off a signup elsewhere, then wait for the email to land. msg = identity.email.wait_for( lambda m: "verify" in m.subject.lower(), timeout=120, ) print(msg.body) ``` TypeScript: ```typescript // Kick off a signup elsewhere, then wait for the email to land. const msg = await identity.email.waitFor( (m) => m.subject.toLowerCase().includes("verify"), { timeoutMs: 120_000 }, ); console.log(msg.body); ``` ## Mark as read Python: ```python msg = identity.email.get("") msg.mark_read() ``` TypeScript: ```typescript const msg = await identity.email.get(""); await msg.markRead(); ``` --- Looking for exact request and response fields? See the full [API reference](/api/reference/). ## Phone An identity can own a real phone number for both SMS and voice. Your agent can text from it, read what arrives, block until a one-time code lands, and place calls — the same number works over the CLI, the Python SDK, and the TypeScript SDK. Every example below runs against one identity. Grab it once (or set it as the active identity in the CLI) and the rest follow. Need one first? See [Identities](/core-concepts/identities/). CLI: ```bash ravi identity use ``` Python: ```python from ravi import Ravi ravi = Ravi(api_key="ravi_id_...") identity = ravi.identities.get("") ``` TypeScript: ```typescript import { Ravi } from "@ravi-hq/sdk"; const ravi = new Ravi({ apiKey: process.env.RAVI_API_KEY! }); const identity = await ravi.identities.get(""); ``` The phone is optional — `identity.phone` may be null until you provision one. Add a number with `provision_phone` (see [Identities](/core-concepts/identities/)). In TypeScript the field is nullable, so the examples use `identity.phone?.…`. ## Send an SMS Text any number from the identity's own line. CLI: ```bash ravi sms send --to +15551234567 --body "Hi from my agent" ``` Python: ```python sms = identity.phone.send( to="+15551234567", body="Hi from my agent", ) ``` TypeScript: ```typescript const sms = await identity.phone?.send( "+15551234567", "Hi from my agent", ); ``` ## Read the SMS inbox List messages as a flat feed — filter to unread to see only what needs attention. CLI: ```bash ravi message sms --unread ``` Python: ```python inbox = identity.phone.inbox(unread=True) for sms in inbox: print(sms.from_number, sms.body) ``` TypeScript: ```typescript const inbox = await identity.phone?.inbox({ unread: true }); for (const sms of inbox) { console.log(sms.from_number, sms.body); } ``` ## List conversations Prefer messages grouped by the other party? List conversations instead. CLI: ```bash ravi inbox sms ``` Python: ```python conversations = identity.phone.conversations() ``` TypeScript: ```typescript const conversations = await identity.phone?.conversations(); ``` ## Read one message Fetch a single message by id to see its full body and sender. CLI: ```bash ravi message sms ``` Python: ```python sms = identity.phone.get("") print(sms.from_number) print(sms.body) ``` TypeScript: ```typescript const sms = await identity.phone?.get(""); console.log(sms.from_number); console.log(sms.body); ``` ## Wait for a one-time code The move that makes agents work: trigger a login somewhere, then block until the SMS code arrives. `wait_for` polls the inbox and returns the first message that matches. Python: ```python # Kick off a login elsewhere, then wait for the code to land. sms = identity.phone.wait_for( lambda m: "code" in m.body.lower(), timeout=120, ) print(sms.body) ``` TypeScript: ```typescript // Kick off a login elsewhere, then wait for the code to land. const sms = await identity.phone?.waitFor( (m) => m.body.toLowerCase().includes("code"), { timeoutMs: 120_000 }, ); console.log(sms.body); ``` ## Reply to an SMS Messages carry their own actions — reply straight to the sender. Python: ```python sms = identity.phone.get("") sms.reply(body="Got it, thanks!") ``` TypeScript: ```typescript const sms = await identity.phone?.get(""); await sms.reply("Got it, thanks!"); ``` ## Place a voice call Dial any number from the identity's line. CLI: ```bash ravi call --to +15551234567 ``` Python: ```python call = identity.phone.call(to="+15551234567") print(call.uuid, call.status) ``` TypeScript: ```typescript const call = await identity.phone?.call("+15551234567"); console.log(call.uuid, call.status); ``` ## Get a call transcript Pull the transcript once a call has ended. CLI: ```bash ravi call transcript ``` Python: ```python call = identity.phone.call(to="+15551234567") transcript = call.transcript() ``` TypeScript: ```typescript const call = await identity.phone?.call("+15551234567"); const transcript = await call.transcript(); ``` ## Hang up a call End a call that's still in progress. CLI: ```bash ravi call hangup ``` Python: ```python call.hangup() ``` TypeScript: ```typescript await call.hangup(); ``` --- Looking for exact request and response fields? See the full [API reference](/api/reference/). ## Contacts Every identity keeps its own contact directory — an address book your agent can lean on. Before sending an email or a text, resolve a name like "Alice" to a real email or phone number, then send with confidence. The same directory works over the CLI, the Python SDK, and the TypeScript SDK. Every example below runs against one identity. Grab it once (or set it as the active identity in the CLI) and the rest follow. Need one first? See [Identities](/core-concepts/identities/). CLI: ```bash ravi identity use ``` Python: ```python from ravi import Ravi ravi = Ravi(api_key="ravi_id_...") identity = ravi.identities.get("") ``` TypeScript: ```typescript import { Ravi } from "@ravi-hq/sdk"; const ravi = new Ravi({ apiKey: process.env.RAVI_API_KEY! }); const identity = await ravi.identities.get(""); ``` ## Add a contact Save someone to the directory. A contact carries an `email`, `phone_number`, `display_name`, `nickname`, and an `is_trusted` flag. CLI: ```bash ravi contacts create \ --email user@example.com \ --display-name "Alice Example" ``` Python: ```python contact = identity.contacts.create( email="user@example.com", display_name="Alice Example", ) ``` TypeScript: ```typescript const contact = await identity.contacts.create({ email: "user@example.com", display_name: "Alice Example", }); ``` ## List contacts Browse everyone in the directory. CLI: ```bash ravi contacts list ``` Python: ```python contacts = identity.contacts.list() for c in contacts: print(c.display_name, c.email) ``` TypeScript: ```typescript const contacts = await identity.contacts.list(); for (const c of contacts) { console.log(c.display_name, c.email); } ``` ## Search contacts Fuzzy-match by name, nickname, email, or phone — the move to make right before sending, when you only know "Alice". CLI: ```bash ravi contacts search "alice" ``` Python: ```python matches = identity.contacts.search("alice") ``` TypeScript: ```typescript const matches = await identity.contacts.search("alice"); ``` ## Find a contact Look up an exact match by email or phone number when you already know the identifier. Python: ```python contact = identity.contacts.find(email="user@example.com") ``` TypeScript: ```typescript const contact = await identity.contacts.find({ email: "user@example.com" }); ``` ## Get one contact Fetch a single contact by id to see all its fields. CLI: ```bash ravi contacts get ``` Python: ```python contact = identity.contacts.get("") print(contact.display_name, contact.phone_number) ``` TypeScript: ```typescript const contact = await identity.contacts.get(""); console.log(contact.display_name, contact.phone_number); ``` ## Update a contact Change any field — here, set a friendlier `nickname`. CLI: ```bash ravi contacts update --nickname "Ali" ``` Python: ```python contact = identity.contacts.update("", nickname="Ali") ``` TypeScript: ```typescript const contact = await identity.contacts.update("", { nickname: "Ali" }); ``` ## Delete a contact Remove someone from the directory. CLI: ```bash ravi contacts delete ``` Python: ```python identity.contacts.delete("") ``` TypeScript: ```typescript await identity.contacts.delete(""); ``` --- Looking for exact request and response fields? See the full [API reference](/api/reference/). ## Vault Every identity has an encrypted vault for the credentials it needs to work — website passwords (domain, username, password entries) and key-value secrets (API keys, tokens, and other env-style values). Everything is encrypted at rest and reachable over the CLI, the Python SDK, and the TypeScript SDK. Every example below runs against one identity. Grab it once (or set it as the active identity in the CLI) and the rest follow. Need one first? See [Identities](/core-concepts/identities/). CLI: ```bash ravi identity use ``` Python: ```python from ravi import Ravi ravi = Ravi(api_key="ravi_id_...") identity = ravi.identities.get("") ``` TypeScript: ```typescript import { Ravi } from "@ravi-hq/sdk"; const ravi = new Ravi({ apiKey: process.env.RAVI_API_KEY! }); const identity = await ravi.identities.get(""); ``` ## Passwords ### Store a password Save a login for a site — domain, username, and password, with optional notes. CLI: ```bash ravi passwords create acme.com --username agent --generate ``` Python: ```python entry = identity.vault.passwords.create( domain="acme.com", username="agent", password="s3cr3t-pass", notes="Signup for the ops dashboard", ) ``` TypeScript: ```typescript const entry = await identity.vault.passwords.create({ domain: "acme.com", username: "agent", password: "s3cr3t-pass", }); ``` ### List passwords See every stored login for this identity. CLI: ```bash ravi passwords list ``` Python: ```python entries = identity.vault.passwords.list() ``` TypeScript: ```typescript const entries = await identity.vault.passwords.list(); ``` ### Get one Fetch a single entry by id to read its username and password. CLI: ```bash ravi passwords get ``` Python: ```python entry = identity.vault.passwords.get("") ``` TypeScript: ```typescript const entry = await identity.vault.passwords.get(""); ``` ### Update a password Rotate the password (or change any other field) on an existing entry. CLI: ```bash ravi passwords update --password "new-s3cr3t" ``` Python: ```python entry = identity.vault.passwords.update( "", password="new-s3cr3t", ) ``` TypeScript: ```typescript const entry = await identity.vault.passwords.update("", { password: "new-s3cr3t", }); ``` ### Delete a password Remove a login you no longer need. CLI: ```bash ravi passwords delete ``` Python: ```python identity.vault.passwords.delete("") ``` TypeScript: ```typescript await identity.vault.passwords.delete(""); ``` ### Generate a strong password Get a random, strong password — useful right before creating an entry. CLI: ```bash ravi passwords generate --length 24 ``` Python: ```python password = identity.vault.passwords.generate() ``` TypeScript: ```typescript const password = await identity.vault.passwords.generate(); ``` ## Secrets ### Store a secret Save a key-value secret — an API key, token, or any env-style value. CLI: ```bash ravi secrets set OPENAI_API_KEY "sk-…" ``` Python: ```python secret = identity.vault.secrets.create( key="OPENAI_API_KEY", value="sk-…", notes="Prod key for the summarizer", ) ``` TypeScript: ```typescript const secret = await identity.vault.secrets.create({ key: "OPENAI_API_KEY", value: "sk-…", }); ``` ### List secrets See every secret stored for this identity. CLI: ```bash ravi secrets list ``` Python: ```python secrets = identity.vault.secrets.list() ``` TypeScript: ```typescript const secrets = await identity.vault.secrets.list(); ``` ### Get a secret Fetch a single secret to read its value. CLI: ```bash ravi secrets get OPENAI_API_KEY ``` Python: ```python secret = identity.vault.secrets.get("") ``` TypeScript: ```typescript const secret = await identity.vault.secrets.get(""); ``` ### Update a secret Rotate the value of an existing secret. CLI: ```bash ravi secrets set OPENAI_API_KEY "sk-new…" ``` Python: ```python secret = identity.vault.secrets.update( "", value="sk-new…", ) ``` TypeScript: ```typescript const secret = await identity.vault.secrets.update("", { value: "sk-new…", }); ``` ### Delete a secret Remove a secret you no longer need. CLI: ```bash ravi secrets delete ``` Python: ```python identity.vault.secrets.delete("") ``` TypeScript: ```typescript await identity.vault.secrets.delete(""); ``` --- Looking for exact request and response fields? See the full [API reference](/api/reference/). ## CLI Overview The Ravi CLI is the terminal client for local setup and agent workflows. ## Install ```bash brew install ravi-hq/tap/ravi ``` See [Installation](/getting-started/installation/) for source builds and release archives. ## Configuration The CLI stores local state under `~/.ravi/` by default. | File | Purpose | | --- | --- | | `config.json` | Long-lived `ravi_mgmt_...` / `ravi_id_...` keys and this machine's active identity | The CLI is one identity per machine via `~/.ravi/config.json`. Shared CLI state is not a multi-agent runtime. Multiple agents on one host must call the HTTP API with per-identity `ravi_id_...` keys (or the Cursor Connect plugin). Do not flip a global CLI identity or use multiple config directories as the multi-agent path. ## Command Groups | Group | Purpose | | --- | --- | | `ravi auth` | Login, logout, and auth status | | `ravi identity` | Create, list, and select identities | | `ravi get` | Fetch active identity resources | | `ravi inbox` | Read email and SMS inboxes | | `ravi message` | Work with individual messages | | `ravi email` | Compose and reply to email | | `ravi sms` | Send SMS from the identity's number | | `ravi call` | Place calls, list them, fetch transcripts, hang up | | `ravi passwords` | Password entry operations | | `ravi secrets` | Secret entry operations | | `ravi feedback` | Send feedback | The API key is an auth fence — it defines what a caller may touch, not which identity it acts as. Local CLI commands use this machine's active identity. `--identity ` targets one identity for a single command; it is not how you run many agents. ## Output JSON is the default output format for scripts and agents. Use `--human` only when you want human-readable tables or messages: ```bash ravi auth status ravi identity list ravi identity list --human ``` ## API Relationship The CLI talks to the same backend documented in [API Overview](/api/overview/). For service-to-service code, an API key and direct HTTP calls may be simpler than shelling out to the CLI. ## Next Steps - [Command Reference](/cli/commands/) - [Authentication](/getting-started/authentication/) - [API Endpoints](/api/endpoints/) ## Command Reference ## Authentication | Command | Description | |---------|-------------| | `ravi auth login` | Device-code login (RFC 8628) against `https://api.ravi.app` | | `ravi auth logout` | Clear stored credentials | | `ravi auth status` | Show current authentication status | ```bash ravi auth status ``` ## Identity management | Command | Description | |---------|-------------| | `ravi identity list` | List all Identities | | `ravi identity create --name "X"` | Create a new Identity | | `ravi identity use ` | Set this machine's active identity | ```bash # Create an identity and set it as this machine's active identity ravi identity create --name "research-agent" ravi identity use ``` The CLI is one identity per machine. `ravi identity use` writes the shared `~/.ravi/config.json` active identity. It is not how you run many agents. Multiple agents on one host must use the HTTP API with per-identity `ravi_id_...` keys (or the Cursor Connect plugin). ## Identity info | Command | Description | |---------|-------------| | `ravi get email` | Get the active Identity's email address | | `ravi get phone` | Get the active Identity's phone number | ```bash EMAIL=$(ravi get email | jq -r '.email') PHONE=$(ravi get phone | jq -r '.phone_number') ``` ## Inbox (grouped) Messages grouped by conversation (SMS) or thread (email). | Command | Description | |---------|-------------| | `ravi inbox email` | List email threads | | `ravi inbox email ` | View a specific email thread | | `ravi inbox email --unread` | Email threads with unread messages | | `ravi inbox sms` | List SMS conversations | | `ravi inbox sms ` | View a specific SMS conversation | | `ravi inbox sms --unread` | SMS conversations with unread messages | ```bash # Get unread SMS previews ravi inbox sms --unread | jq -r '.[].preview' # Read a specific email thread ravi inbox email | jq -r '.messages[].text_content' ``` ## Messages (flat) Individual messages without grouping. | Command | Description | |---------|-------------| | `ravi message email` | List all email messages | | `ravi message email ` | View a specific email message | | `ravi message email --unread` | Unread email messages only | | `ravi message sms` | List all SMS messages | | `ravi message sms ` | View a specific SMS message | | `ravi message sms --unread` | Unread SMS messages only | ## SMS sending | Command | Description | |---------|-------------| | `ravi sms send` | Send an SMS from the active Identity's phone number | **Send flags:** | Flag | Required | Description | |------|----------|-------------| | `--to` | Yes | Recipient phone number in E.164 format (e.g. `+15551234567`) | | `--body` | Yes | Message text (plain text only, max 1600 characters) | ```bash # Send a one-time passcode via SMS ravi sms send --to "+15551234567" --body "Your code is 482910" # Notify a human approver from a setup script ravi sms send \ --to "${APPROVER_PHONE}" \ --body "Agent workspace ${WORKSPACE_NAME} is ready at ${WORKSPACE_URL}" ``` :::note `ravi sms send` uses this machine's **active identity** phone number. `ravi identity use` changes that single active identity. For multiple agents on one host, use the HTTP API with per-identity `ravi_id_...` keys (or the Cursor Connect plugin). ::: ## Calls | Command | Description | |---------|-------------| | `ravi call --to ` | Place an outbound call from the active Identity's phone number | | `ravi call list` | List calls | | `ravi call transcript ` | Show a call's transcript | | `ravi call hangup ` | Hang up an active call | ```bash # Place a call and capture the call id CALL_ID=$(ravi call --to "+15551234567" | jq -r '.uuid') # Fetch the transcript once the call ends ravi call transcript "$CALL_ID" ``` ## Email sending | Command | Description | |---------|-------------| | `ravi email compose` | Compose and send a new email | | `ravi email reply ` | Reply to sender only | | `ravi email reply-all ` | Reply to all recipients | | `ravi email forward ` | Forward an email | **Compose flags:** | Flag | Required | Description | |------|----------|-------------| | `--to` | Yes | Recipient email address | | `--subject` | Yes | Email subject line | | `--body` | Yes | Email body (HTML supported) | | `--cc` | No | CC recipients (comma-separated) | | `--bcc` | No | BCC recipients (comma-separated) | | `--attach` | No | File to attach (repeatable) | ```bash ravi email compose \ --to "user@example.com" \ --subject "Report" \ --body "

See attached.

" \ --attach report.pdf ``` **Reply flags:** `--body` (required), `--cc`, `--bcc`, `--attach` (optional) ## Passwords | Command | Description | |---------|-------------| | `ravi passwords list` | List all stored passwords | | `ravi passwords get ` | Show a password entry | | `ravi passwords create ` | Create a new entry | | `ravi passwords update ` | Update a password entry | | `ravi passwords delete ` | Delete a password entry | | `ravi passwords generate` | Generate a random password without storing | **Create flags:** `--username`, `--password`, `--generate`, `--length` (default: 16), `--no-special`, `--no-digits`, `--exclude-chars`, `--notes` ```bash # Auto-generate a password for a domain ravi passwords create github.com --username "agent@in.ravi.app" # Generate without storing ravi passwords generate --length 32 ``` ## Secrets | Command | Description | |---------|-------------| | `ravi secrets set ` | Store a secret (creates or updates) | | `ravi secrets get ` | Retrieve a secret by key | | `ravi secrets list` | List all secret keys | | `ravi secrets delete ` | Delete a secret by UUID | ```bash ravi secrets set OPENAI_API_KEY "sk-abc123..." ravi secrets get OPENAI_API_KEY | jq -r '.value' ravi secrets list | jq -r '.[].key' ``` ## Feedback | Command | Description | |---------|-------------| | `ravi feedback ""` | Send feedback to the Ravi team | ```bash ravi feedback "Love the CLI, but would like custom domains" ``` ## Global flags | Flag | Description | |------|-------------| | `--human` | Output in human-readable format. JSON is the default. | | `--identity ` | Target one identity for a single command (messages, contacts, passwords, secrets, calls, events). When omitted, this machine's active identity scopes the request. Not a multi-agent runtime. | | `--help` | Show help for any command | | `--version` | Show version information | The API key is an **auth fence** — it defines what you're allowed to touch. The *caller* chooses the identity. A management key with `--identity ` targets one identity; an identity-scoped key is already fenced to its identity, and `--identity` must match it. ## API Overview ## Base URL ```text https://api.ravi.app ``` Production backend endpoints are mounted under `/api/`. Use [API Endpoints](/api/endpoints/) for the current route families and [API Reference](/api/reference/) for the generated OpenAPI document. ## Authentication Ravi supports bearer authentication. ```http Authorization: Bearer ``` The backend has two API-key families: | Prefix | Scope | | --- | --- | | `ravi_mgmt_...` | Management/account-level operations | | `ravi_id_...` | Identity-scoped operations | ## Identity Scoping Some resources are naturally scoped to an identity: email, phone, calls, passwords, secrets, contacts, and events. The API key is an **auth fence only** — it never selects the identity. The *caller* chooses one and passes it explicitly. Every per-identity request carries `?identity=`: ```bash curl "https://api.ravi.app/api/messages/?identity=" \ -H "Authorization: Bearer $RAVI_API_KEY" ``` | Key family | Behaviour of `?identity=` | | --- | --- | | `ravi_id_...` | The param must match the key's fenced identity; a mismatch returns an empty result. | | `ravi_mgmt_...` | The param targets one identity. Omit it for account-wide behaviour. | The param is **additive** — the SDKs send it automatically from the identity object, so you never assemble it by hand. ## Response Format Most API responses are JSON and use normal HTTP status codes. | Status | Meaning | | --- | --- | | `200` | Success | | `201` | Created | | `204` | Deleted or empty success | | `400` | Validation error | | `401` | Missing or invalid authentication | | `403` | Authenticated but not allowed | | `404` | Resource or route not found | | `429` | Rate limited | | `500` | Server error | ## OpenAPI The public documentation publishes a generated OpenAPI document at: ```text https://docs.ravi.app/openapi.json ``` That file is assembled from the backend schema fragments checked into the backend repo. It is the machine-readable contract to use for API linting, endpoint review, and agent context. ## Current Public Health Check ```bash curl https://api.ravi.app/api/health/ping/ ``` This is the quickest public check that you are talking to the production backend. ## Next Steps - [API Endpoints](/api/endpoints/) - [API Reference](/api/reference/) - [Authentication](/getting-started/authentication/) - [Error Reference](/api/error-reference/) ## API Endpoints This page is a route-family map. For exact request and response fields, use the generated OpenAPI document at `https://docs.ravi.app/openapi.json`. Per-identity route families (Email, Phone & SMS, Contacts, Passwords, Secrets, and per-identity Events) accept an explicit `?identity=` query parameter. The API key is only an auth fence; the *caller* chooses the identity. With a management key the param targets one identity; with an identity-scoped key it must match the key's fenced identity. See [API Overview → Identity Scoping](/api/overview/). ## System And Health | Method | Endpoint | Description | | --- | --- | --- | | `GET` | `/api/health/ping/` | Public health check | | `GET` | `/api/health/whoami/` | Authenticated caller metadata | | `GET` | `/api/master/` | Current account owner metadata | ## Authentication | Method | Endpoint | Description | | --- | --- | --- | | `POST` | `/api/auth/login/` | Login | | `POST` | `/api/auth/logout/` | Logout | | `GET` | `/api/auth/user/` | Current authenticated user | | `POST` | `/api/auth/token/refresh/` | Refresh a JWT | | `POST` | `/api/auth/token/verify/` | Verify a JWT | | `POST` | `/api/auth/device/` | Start device-code login | | `POST` | `/api/auth/device/token/` | Poll device-code login | | `GET` / `POST` | `/api/auth/device/verify/` | Verify a device code | | `GET` | `/api/auth/device/callback/` | Complete device-code OAuth callback | | `POST` | `/api/auth/bind-identity/` | Bind auth flow to an identity | | `POST` | `/api/auth/social/google/` | Google OAuth exchange | ## API Keys | Method | Endpoint | Scope | | --- | --- | --- | | `GET` / `POST` | `/api/auth/keys/management/` | Management keys, `ravi_mgmt_...` | | `GET` / `DELETE` | `/api/auth/keys/management//` | Management key detail/revoke | | `GET` / `POST` | `/api/auth/keys/identity/` | Identity keys, `ravi_id_...` | | `GET` / `DELETE` | `/api/auth/keys/identity//` | Identity key detail/revoke | ## Identities | Method | Endpoint | Description | | --- | --- | --- | | `GET` / `POST` | `/api/identities/` | List or create identities | | `GET` / `PUT` / `PATCH` / `DELETE` | `/api/identities//` | Read, update, or delete an identity | ## Email | Method | Endpoint | Description | | --- | --- | --- | | `GET` / `POST` | `/api/email/` | Email inbox resources | | `GET` / `PUT` / `PATCH` / `DELETE` | `/api/email//` | Email inbox detail | | `GET` | `/api/email-inbox/` | Thread-oriented inbox listing | | `GET` | `/api/email-inbox//` | Thread detail | | `GET` / `POST` | `/api/email-messages/` | List or create email messages | | `GET` / `PUT` / `PATCH` / `DELETE` | `/api/email-messages//` | Email message detail | | `POST` | `/api/email-attachments/presign/` | Create a presigned upload target | | `POST` | `/api/webhook/email/inbound/` | Generic inbound email webhook | | `POST` | `/api/webhook/email/sendgrid-inbound/` | SendGrid inbound parse webhook | ## Domains | Method | Endpoint | Description | | --- | --- | --- | | `GET` / `POST` | `/api/domains/` | List or create domains | | `GET` / `PUT` / `PATCH` / `DELETE` | `/api/domains//` | Domain detail | ## Phone And SMS | Method | Endpoint | Description | | --- | --- | --- | | `GET` / `POST` | `/api/phone/` | Phone number resources | | `GET` / `PUT` / `PATCH` / `DELETE` | `/api/phone//` | Phone number detail | | `GET` | `/api/sms-inbox/` | SMS conversations | | `GET` | `/api/sms-inbox//` | SMS conversation detail | | `GET` / `POST` | `/api/messages/` | SMS messages | | `GET` / `PUT` / `PATCH` / `DELETE` | `/api/messages//` | SMS message detail | | `POST` | `/api/messages/send/` | Send SMS | | `GET` / `POST` | `/api/calls/` | Phone call resources | | `GET` / `PUT` / `PATCH` / `DELETE` | `/api/calls//` | Phone call detail | | `POST` | `/api/webhook/telnyx/` | Telnyx webhook | | `POST` | `/api/webhook/twilio/` | Twilio webhook | | `POST` | `/api/webhook/voipms/` | VoIP.ms webhook | ## Contacts | Method | Endpoint | Description | | --- | --- | --- | | `GET` / `POST` | `/api/contacts/` | List or create contacts | | `GET` / `PUT` / `PATCH` / `DELETE` | `/api/contacts//` | Contact detail | ## Passwords And Secrets | Method | Endpoint | Description | | --- | --- | --- | | `GET` / `POST` | `/api/passwords/` | List or create password entries | | `GET` / `PUT` / `PATCH` / `DELETE` | `/api/passwords//` | Password entry detail | | `GET` | `/api/passwords/generate_password/` | Generate a password | | `GET` / `POST` | `/api/secrets/` | List or create secret entries | | `GET` / `PUT` / `PATCH` / `DELETE` | `/api/secrets//` | Secret entry detail | ## Webhooks And Events | Method | Endpoint | Description | | --- | --- | --- | | `GET` / `POST` | `/api/webhook-subscriptions/` | Customer webhook endpoints | | `GET` / `PUT` / `PATCH` / `DELETE` | `/api/webhook-subscriptions//` | Customer webhook endpoint detail | | `GET` | `/api/webhook-deliveries/` | Webhook delivery logs | | `GET` | `/api/webhook-deliveries//` | Webhook delivery detail | | `GET` | `/api/events/` | Event replay endpoint | ## Billing | Method | Endpoint | Description | | --- | --- | --- | | `GET` | `/api/subscription/` | Current subscription status | ## API Reference Most tasks are easier through the [CLI](/cli/overview/) or an [SDK](/sdk/overview/) — start with [Core Concepts](/core-concepts/identities/) for the common flows. This page is the complete endpoint listing for when you need the raw API. Authenticate every request with a bearer key: ```http Authorization: Bearer ravi_id_... # identity-scoped, for agent runtime calls Authorization: Bearer ravi_mgmt_... # account-level, for setup and management ``` Create one from the [dashboard](https://dashboard.ravi.app/dashboard/api-keys/), with `ravi auth login`, or via `POST /api/auth/keys/`. Agents can also read this page as plain text at `https://docs.ravi.app/api/reference.txt`, or start from `https://docs.ravi.app/llms.txt`. ## Error Reference ## HTTP Status Codes | Status | Meaning | Common recovery | | --- | --- | --- | | `400` | Bad request | Check the request body and required fields | | `401` | Unauthorized | Add or rotate the bearer token/API key | | `403` | Forbidden | Use a key with the right scope | | `404` | Not found | Confirm the route and resource ID | | `409` | Conflict | Check for duplicate names or already-bound resources | | `429` | Rate limited | Back off and retry later | | `500` | Server error | Retry with backoff; report persistent failures | ## Error Body Most Django REST Framework errors include a `detail` field: ```json { "detail": "Authentication credentials were not provided." } ``` Validation errors may be field-shaped: ```json { "name": ["This field is required."] } ``` ## Route Checks If a request returns `404`, confirm the endpoint in [API Endpoints](/api/endpoints/) or the generated OpenAPI document: ```text https://docs.ravi.app/openapi.json ``` API calls should use `https://api.ravi.app`. Documentation and machine-readable reference files are served from `https://docs.ravi.app`. ## See Also - [Troubleshooting](/getting-started/troubleshooting/) - [API Overview](/api/overview/) - [API Endpoints](/api/endpoints/) ## SDK Overview Ravi publishes first-party SDKs for TypeScript and Python. Use an SDK when you want typed resource clients, consistent error handling, retry behavior, and webhook signature helpers without hand-writing HTTP requests. ## Packages | Language | Install package | Import package | Repo version | | --- | --- | --- | --- | | TypeScript | `@ravi-hq/sdk` | `@ravi-hq/sdk` | `0.2.0` | | Python | `ravi-sdk` | `ravi` | `0.2.0` | Install: ```bash npm install @ravi-hq/sdk pip install ravi-sdk ``` ## Authentication Both SDKs send the API key as a bearer token on every request. The key is an **auth fence only** — it defines what you're allowed to touch, but it never selects the identity. The *caller* picks an identity and calls channels on it, and every per-identity request carries `?identity=` automatically. | Key family | Use for | | --- | --- | | `ravi_id_...` | Identity-scoped agent runtime work | | `ravi_mgmt_...` | Account-level management work | Read the key from `RAVI_API_KEY` in server-side code. Create keys from the [dashboard](https://dashboard.ravi.app/dashboard/api-keys/), `ravi auth login`, or the key-management API. ## Resource Map Account-level resources hang off the client. Everything per-identity is reached through an `Identity` object returned by `ravi.identities`, where `identity.email` and `identity.phone` are channel objects (with `.address` / `.number`), not strings. ### Account-level (`ravi.*`) | Area | TypeScript | Python | | --- | --- | --- | | Identities | `ravi.identities` | `ravi.identities` | | Fenced identity | `ravi.me()` | `ravi.me` | | Events | `ravi.events` | `ravi.events` | | Domains | `ravi.domains` | `ravi.domains` | | Webhooks | `ravi.webhooks` | `ravi.webhooks` | | API keys | `ravi.apiKeys` | `ravi.api_keys` | ### Per-identity (`identity.*`) | Area | TypeScript | Python | | --- | --- | --- | | Email channel | `identity.email` | `identity.email` | | Phone channel | `identity.phone` | `identity.phone` | | Contacts | `identity.contacts` | `identity.contacts` | | Passwords | `identity.vault.passwords` | `identity.vault.passwords` | | Secrets | `identity.vault.secrets` | `identity.vault.secrets` | | Events | `identity.events` | `identity.events` | The `email` channel exposes `.address`, `inbox`/`threads`/`get`/`send`/`waitFor` and returns `EmailMessage` objects (`.reply`/`.replyAll`/`.forward`/`.markRead`). The `phone` channel exposes `.number`, `inbox`/`conversations`/`send`/`call`/ `calls`/`waitFor` and returns `SmsMessage` objects (`.reply`/`.markRead`). ## Next Steps - [TypeScript SDK](/sdk/typescript/) - [Python SDK](/sdk/python/) - [API Reference](/api/reference/) ## TypeScript SDK ## Install ```bash npm install @ravi-hq/sdk ``` ## Create A Client ```typescript const ravi = new Ravi({ apiKey: process.env.RAVI_API_KEY!, }); ``` `apiKey` is required. Use a `ravi_id_...` identity key for agent runtime work, or a `ravi_mgmt_...` management key for account-level operations. The key is an **auth fence only** — the caller chooses the identity, and every per-identity call sends `?identity=` automatically. ## Choose An Identity The caller chooses the identity, then calls channels and resources on it. Create one, fetch one by uuid, or — with an identity-scoped key — resolve the single fenced identity with `ravi.me()`. ```typescript const identity = await ravi.identities.create({ name: "shopping-agent", email_identifier: "shopping", // email local part; omit to auto-generate provision_phone: true, // also give it a phone number }); // `email` and `phone` are channel objects — the identifiers are `.address` / `.number`. console.log(identity.email.address, identity.phone?.number); // With an identity key you don't have the uuid handy: const me = await ravi.me(); // the single identity the key is fenced to (throws on a mgmt key) ``` Identity methods: | Method | Purpose | | --- | --- | | `ravi.identities.create(input)` | Create an identity, optionally with email local part and phone provisioning | | `ravi.identities.list()` | List identities visible to the key | | `ravi.identities.get(uuid)` | Fetch one identity | | `ravi.identities.update(uuid, patch)` | Update identity metadata | | `ravi.identities.provisionPhone(uuid)` | Add a phone number to an existing identity | ## Wait For A Verification Code The killer use case: send a request, then block until the OTP or verification message lands. `waitFor` polls the channel's inbox and resolves with the first matching message. ```typescript // Email verification: trigger a signup, then wait for the confirmation email. await identity.email.send("signup@service.com", "Sign me up", "…"); const msg = await identity.email.waitFor((m) => m.subject.includes("verify"), { timeoutMs: 120_000, }); await msg.reply("got it"); // messages carry behaviour: reply/replyAll/forward/markRead // SMS OTP: wait for the 6-digit code on the identity's number. await identity.phone?.send("+15551234567", "START"); const otp = await identity.phone?.waitFor((m) => /\d{6}/.test(m.body), { timeoutMs: 120_000, }); console.log(otp?.body); ``` ## Email Channel `identity.email` is the identity's inbox as a channel. Send from it, list the flat inbox or the grouped threads, and act on returned `EmailMessage` objects. ```typescript const msg = await identity.email.send( "user@example.com", "Hello", "Greetings from my AI agent", ); const inbox = await identity.email.inbox({ unread: true }); // flat EmailMessage list const threads = await identity.email.threads(); // grouped view await msg.reply("Thanks!"); await msg.forward("ops@acme.com"); await msg.markRead(); ``` The TypeScript SDK uses the API's snake_case response fields. It does not remap wire fields to camelCase. ## Phone Channel `identity.phone` is the identity's number as a channel (or `null` if no number is provisioned). Send SMS, list conversations, or place voice calls. ```typescript const sms = await identity.phone?.send("+15551234567", "Your code is 482910"); const inbox = await identity.phone?.inbox({ unread: true }); // flat SmsMessage list const conversations = await identity.phone?.conversations(); // Place an outbound voice call from the identity's number. const call = await identity.phone?.call("+15551234567"); const transcript = await call?.transcript(); ``` ## Vault And Contacts Credentials and the contact directory live under the identity. ```typescript await identity.vault.passwords.create({ domain: "acme.com", username: "agent", password: "…" }); await identity.vault.secrets.create({ key: "OPENAI_API_KEY", value: "sk-…" }); const contact = await identity.contacts.find("user@example.com"); ``` ## Events Replay durable events after your last processed cursor: ```typescript const events = await ravi.events.list({ since: lastSeq, limit: 100, event_types: ["call.ended", "email.message.received"], }); ``` ## API Keys Management keys and identity keys are available through `ravi.apiKeys`. ```typescript const managementKey = await ravi.apiKeys.management.create("ci"); console.log(managementKey.key); // full value is returned once const identityKey = await ravi.apiKeys.identity.create("shopping-agent", identity.uuid); console.log(identityKey.key); // full value is returned once ``` ## Webhook Verification Ravi signs deliveries with `X-Ravi-Timestamp` and `X-Ravi-Signature`. ```typescript app.post("/webhooks/ravi", async (req, res) => { const rawBody = await readRawBody(req); try { verifyWebhookSignature( rawBody, req.headers["x-ravi-timestamp"], req.headers["x-ravi-signature"], process.env.RAVI_WEBHOOK_SECRET!, ); } catch (error) { if (error instanceof WebhookSignatureError) { return res.status(401).end(); } throw error; } const event = JSON.parse(rawBody); res.status(202).end(); }); ``` ## Public Exports The package exports the `Ravi` client, resource classes, resource input types, API models, typed errors, and webhook verification helpers from `@ravi-hq/sdk`. Top-level exports include `Ravi`, `RaviError` subclasses, resource classes, API model types, `verifyWebhookSignature`, and `WebhookSignatureError`. ## Next Steps - [SDK Overview](/sdk/overview/) - [Python SDK](/sdk/python/) - [API Reference](/api/reference/) ## Python SDK ## Install ```bash pip install ravi-sdk ``` ## Create A Client ```python import os from ravi import Ravi ravi = Ravi(api_key=os.environ["RAVI_API_KEY"]) ``` `api_key` is required. Use a `ravi_id_...` identity key for agent runtime work, or a `ravi_mgmt_...` management key for account-level operations. The key is an **auth fence only** — the caller chooses the identity, and every per-identity call sends `?identity=` taken from the identity object. For long-running processes, use the context manager so the underlying HTTP pool is closed: ```python with Ravi(api_key=os.environ["RAVI_API_KEY"]) as ravi: identities = ravi.identities.list() ``` ## Choose An Identity The caller chooses the identity, then calls channels and resources on it. Create one, fetch one by uuid, or — with an identity-scoped key — resolve the single fenced identity with `ravi.me`. ```python identity = ravi.identities.create( name="shopping-agent", email_identifier="shopping", # email local part; omit to auto-generate provision_phone=True, # also give it a phone number ) # `email` and `phone` are channel objects — the identifiers are `.address` / `.number`. print(identity.email.address, identity.phone.number) # With an identity-scoped key you don't have the uuid handy: me = ravi.me # the single identity the key is fenced to ``` Identity methods: | Method | Purpose | | --- | --- | | `ravi.identities.create(...)` | Create an identity, optionally with email local part and phone provisioning | | `ravi.identities.list()` | List identities visible to the key | | `ravi.identities.get(identity_uuid)` | Fetch one identity | | `ravi.identities.update(identity_uuid, name=...)` | Update identity metadata | | `ravi.identities.provision_phone(identity_uuid)` | Add a phone number to an existing identity | ## Wait For A Verification Code The killer use case: send a request, then block until the OTP or verification message lands. `wait_for` polls the channel's inbox and returns the first matching message. ```python # Email verification: trigger a signup, then wait for the confirmation email. identity.email.send(to="signup@service.com", subject="Sign me up", body="…") msg = identity.email.wait_for(lambda m: "verify" in m.subject, timeout=120) msg.reply(body="got it") # messages carry behaviour: reply/forward/mark_read # SMS OTP: wait for the 6-digit code on the identity's number. identity.phone.send(to="+15551234567", body="START") otp = identity.phone.wait_for(lambda m: "code" in m.body, timeout=120) print(otp.body) ``` ## Email Channel `identity.email` is the identity's inbox as a channel. Send from it, list the flat inbox or the grouped threads, and act on returned `EmailMessage` objects. ```python msg = identity.email.send(to="user@example.com", subject="Hello", body="Greetings from my AI agent") inbox = identity.email.inbox(unread=True) # flat list of EmailMessage threads = identity.email.threads() # grouped view msg.reply(body="Thanks!") msg.forward(to="ops@acme.com") msg.mark_read() ``` ## Phone Channel `identity.phone` is the identity's number as a channel (or `None` if no number is provisioned). Send SMS, list conversations, or place voice calls. ```python sms = identity.phone.send(to="+15551234567", body="Your code is 482910") inbox = identity.phone.inbox(unread=True) # flat list of SmsMessage conversations = identity.phone.conversations() # Place an outbound voice call from the identity's number. call = identity.phone.call(to="+15551234567") print(call.uuid, call.status) transcript = call.transcript() ``` ## Vault And Contacts Credentials and the contact directory live under the identity. ```python identity.vault.passwords.create(domain="acme.com", username="agent", password="…") identity.vault.secrets.create(key="OPENAI_API_KEY", value="sk-…") contact = identity.contacts.find("user@example.com") ``` ## Events Replay durable events after your last processed cursor: ```python events = ravi.events.list( since=last_seq, limit=100, event_types=["call.ended", "email.message.received"], ) ``` ## API Keys Management keys and identity keys are available through `ravi.api_keys`. ```python management_key = ravi.api_keys.management.create("ci") print(management_key.key) # full value is returned once identity_key = ravi.api_keys.identity.create("shopping-agent", identity.uuid) print(identity_key.key) # full value is returned once ``` ## Webhook Verification Ravi signs deliveries with `X-Ravi-Timestamp` and `X-Ravi-Signature`. ```python from ravi import verify_webhook_signature, WebhookSignatureError try: verify_webhook_signature( raw_body, request.headers["X-Ravi-Timestamp"], request.headers["X-Ravi-Signature"], signing_secret, ) except WebhookSignatureError: return Response(status_code=401) ``` ## Public Exports The PyPI package is `ravi-sdk`. The Python import module is `ravi`. The module exports the `Ravi` client, resource classes, Pydantic models, typed errors, event payload models, and webhook verification helpers from `ravi`. Top-level exports include `Ravi`, `RaviError` subclasses, resource classes, Pydantic API models, `verify_webhook_signature`, and `WebhookSignatureError`. ## Next Steps - [SDK Overview](/sdk/overview/) - [TypeScript SDK](/sdk/typescript/) - [API Reference](/api/reference/) ## Security Model Ravi's security model is built around scoped agent identity, narrow credentials, and clear account boundaries. ## Design Principles | Principle | What it means | | --- | --- | | Identity isolation | Each agent identity has its own communication and credential resources. | | Scoped API keys | Identity keys are preferred for agent runtime work; management keys are reserved for account-level operations. | | Server-side enforcement | API requests are authorized against the authenticated user, key scope, and resource ownership. | | Agent-native auth | Server runtimes can use API keys without desktop apps or interactive unlock flows. | ## API Key Scopes | Key family | Use for | | --- | --- | | `ravi_id_...` | Work scoped to one identity | | `ravi_mgmt_...` | Account or workspace management operations | Use the narrowest key that supports the task. For autonomous agents, prefer identity-scoped keys so one leaked key does not expose unrelated identities. ## Identity Isolation Each identity is a separate operational surface: - Email inboxes are tied to the identity that owns them. - Phone resources are tied to the identity that owns them. - Password and secret entries are identity-scoped. - Contacts can be queried through the authenticated scope. - Disabled or deleted identities should no longer be used by agent runtimes. ## Runtime Practices - Store Ravi API keys only in server-side environment variables or secret managers. - Do not ship Ravi API keys in browser bundles, mobile apps, logs, or screenshots. - Use a separate identity per agent, workflow, customer, or environment when isolation matters. - Rotate keys when an agent environment is decommissioned. - Use management keys only in trusted backend control planes. ## Request Boundary All API calls should include an authorization header: ```http Authorization: Bearer ``` Use [Authentication](/getting-started/authentication/) for key families and [API Endpoints](/api/endpoints/) for route families.