Identities
What is an Identity?
:::note[Not the same “identity” you’re thinking of] If you come from Auth0 or Okta, “identity” means an authenticated user or IAM principal. If you come from HubSpot or Salesforce, “identity” means a contact or lead record. If you come from Twilio, the closest analogue would be a messaging address. In Ravi, an Identity is none of those things. It is an agent persona — a bundle of real-world contact endpoints (email, phone) and encrypted credential vaults — assigned to an agent process rather than a human. Think of it as an IAM role that also has an inbox. :::
An Identity is the core abstraction in Ravi. It bundles everything an agent needs to operate on the web:
Identity
├── Email address (auto-generated, receives real email)
├── Phone number (receives SMS, OTPs)
├── Credential vault (E2E-encrypted passwords per service)
├── Secret vault (E2E-encrypted key-value secrets)
└── TOTP secrets (2FA codes for enrolled services)
An Identity is not an Agent. An agent is software; an Identity is the persona it wears. One agent can have multiple Identities. One Identity can be handed between agents.
List Identities
ravi identity list --json
[
{
"uuid": "abc-123",
"name": "research-agent",
"inbox": "research-agent@in.ravi.app",
"phone": "+15551234567"
}
]
Create an Identity
ravi identity create --name "finance-agent" --json
This provisions a new email address, phone number, and empty vaults.
Switch active Identity
ravi identity use finance-agent
All subsequent CLI commands operate against this Identity. You can also use the UUID:
ravi identity use abc-123
Identity resolution
The CLI resolves the active Identity in this order:
.ravi/config.jsonin the current working directory (project-level override)~/.ravi/config.json(global default)- No identity selected (commands that require one will error)
This lets you assign different Identities to different projects by placing a config file in each project root.
Multi-agent setups
Run multiple agents with separate Identities for isolation:
research-agent → research@in.ravi.app (newsletters, reports)
scheduler-agent → calendar@in.ravi.app (meeting invites)
finance-agent → invoices@in.ravi.app (receipts, billing)
Each Identity has its own inbox, credentials, and secrets. One agent’s compromise doesn’t affect another’s data.
Identity scoping
Every API request is scoped to a single Identity via the X-Ravi-Identity header. The CLI and plugins set this automatically based on the active Identity. There is no way to access another Identity’s data without switching to it first.
Programmatic usage
You don’t need to call the CLI interactively — you can create and manage Identities from your agent’s code.
Subprocess approach (quickest path)
The CLI is the simplest way to drive Ravi from code. Use subprocess to call ravi and parse the JSON output.
AI code generators and parallel agents: Do not call
ravi identity usein concurrent processes. It writes a shared global config file — calling it from two processes simultaneously corrupts identity context. Instead, set theRAVI_CONFIG_DIRenvironment variable per process to scope each command to a specific identity directory. The examples below use this safe pattern.
Python:
import subprocess, json, os, pathlib
def create_identity(name: str) -> dict:
result = subprocess.run(
["ravi", "identity", "create", "--name", name, "--json"],
capture_output=True, text=True, check=True
)
return json.loads(result.stdout)
def get_identity_credentials(uuid: str, config_dir: str) -> tuple[str, str]:
"""Returns (email, phone) using per-process RAVI_CONFIG_DIR — safe for parallel agents."""
env = {**os.environ, "RAVI_CONFIG_DIR": config_dir}
email = subprocess.run(
["ravi", "get", "email", "--json"],
capture_output=True, text=True, check=True, env=env
)
phone = subprocess.run(
["ravi", "get", "phone", "--json"],
capture_output=True, text=True, check=True, env=env
)
return (
json.loads(email.stdout)["email"],
json.loads(phone.stdout)["phone_number"],
)
# Provision a workspace agent identity
identity = create_identity(f"workspace-{workspace_id}")
# Write a per-workspace config — no global state mutation, safe for parallel use
config_dir = f"/tmp/ravi-{workspace_id}"
pathlib.Path(config_dir).mkdir(parents=True, exist_ok=True)
pathlib.Path(f"{config_dir}/config.json").write_text(
json.dumps({"identity_uuid": identity["uuid"]})
)
email, phone = get_identity_credentials(identity["uuid"], config_dir)
print(f"Agent email: {email}, phone: {phone}")
TypeScript / Node.js:
import { execSync } from "child_process";
import fs from "fs";
import path from "path";
import os from "os";
function createIdentity(name: string): { uuid: string; inbox: string; phone: string } {
const out = execSync(`ravi identity create --name "${name}" --json`, {
encoding: "utf8",
});
return JSON.parse(out);
}
function getIdentityCredentials(
uuid: string,
configDir: string
): { email: string; phone: string } {
// RAVI_CONFIG_DIR scopes commands to a specific identity — safe for parallel processes
const env = { ...process.env, RAVI_CONFIG_DIR: configDir };
const email = JSON.parse(
execSync("ravi get email --json", { encoding: "utf8", env })
).email;
const phone = JSON.parse(
execSync("ravi get phone --json", { encoding: "utf8", env })
).phone_number;
return { email, phone };
}
// Example: one identity per workspace
const identity = createIdentity(`workspace-${workspaceId}`);
// Write per-workspace config — no global state mutation, safe for parallel use
const configDir = path.join(os.tmpdir(), `ravi-${workspaceId}`);
fs.mkdirSync(configDir, { recursive: true });
fs.writeFileSync(
path.join(configDir, "config.json"),
JSON.stringify({ identity_uuid: identity.uuid })
);
const { email, phone } = getIdentityCredentials(identity.uuid, configDir);
console.log(`Agent email: ${email}, phone: ${phone}`);
See Multi-Agent Setup and CLI Environment Variables for the full parallel-agent isolation guide.
REST API approach
For production agents running headlessly (Docker, CI, serverless), call the Ravi API directly. You need a Bearer token, which the CLI stores at ~/.ravi/auth.json after login.
Python (httpx):
import httpx, json, pathlib
def load_token() -> str:
auth = json.loads(pathlib.Path("~/.ravi/auth.json").expanduser().read_text())
return auth["access_token"]
BASE = "https://ravi.app/api"
def create_identity(name: str, token: str) -> dict:
resp = httpx.post(
f"{BASE}/identities/",
headers={"Authorization": f"Bearer {token}"},
json={"name": name},
)
resp.raise_for_status()
return resp.json()
def get_identity(uuid: str, token: str) -> dict:
resp = httpx.get(
f"{BASE}/identities/{uuid}/",
headers={"Authorization": f"Bearer {token}"},
)
resp.raise_for_status()
return resp.json()
token = load_token()
identity = create_identity(f"worker-{task_id}", token)
# identity["inbox"] → email address
# identity["phone"] → E.164 phone number
TypeScript (fetch):
import fs from "fs";
import os from "os";
import path from "path";
const BASE = "https://ravi.app/api";
function loadToken(): string {
const authPath = path.join(os.homedir(), ".ravi", "auth.json");
const auth = JSON.parse(fs.readFileSync(authPath, "utf8"));
return auth.access_token;
}
async function createIdentity(name: string, token: string) {
const resp = await fetch(`${BASE}/identities/`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ name }),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
return resp.json() as Promise<{ uuid: string; inbox: string; phone: string }>;
}
const token = loadToken();
const identity = await createIdentity(`worker-${taskId}`, token);
// identity.inbox → agent email address
// identity.phone → E.164 phone number
See API Endpoints for the full endpoint reference including inbox, SMS, and vault APIs.
Persistent or ephemeral?
Before you create an identity, decide whether it needs to outlast the current task.
| Use case | Recommended model | Rationale |
|---|---|---|
| Named agent (research-agent, finance-bot) | Persistent | Reputation, inbox history, continuity across restarts |
| Agent registered on a marketplace | Persistent | Stable email builds reputation; replacements start from zero |
| Per-worktree coding agent | Persistent (delete on project close) | Consistent identity across many parallel sessions |
| Per-task scratch work | Ephemeral | No long-term inbox needed; delete on task completion |
| Test / CI verification | Ephemeral | Prefix dev-/test-, bulk-delete after test run |
Why persistent identity matters for marketplace and economic agents: An agent with a stable Ravi email address receives async client replies, accumulates a message history that establishes trust, and maintains continuity when the process restarts or migrates to another machine. Per-call email APIs (fire-and-forget, no inbox, no persistent address) cannot deliver any of these properties — every interaction starts cold.
Identity lifecycle
Identities persist until explicitly deleted. For long-lived agents (one per service, one per user) that’s the right model. For orchestrators that provision an identity per task and run hundreds of tasks per day, cleanup matters.
Delete an Identity
ravi identity delete <uuid>
Or by name:
ravi identity delete worker-task-42
Deletion is permanent: the email address, phone number, and all vaults are destroyed. Incoming mail and SMS to the deleted address are rejected.
When to delete
| Pattern | Lifecycle |
|---|---|
| One permanent agent | Create once, never delete |
| One identity per project | Delete when the project is decommissioned |
| One identity per task | Delete on task completion — clean up immediately |
| Dev/test identities | Prefix with dev- or test-; bulk-delete after testing |
Bulk cleanup of test identities
# List all identities prefixed with dev- or test-
ravi identity list --json | jq -r '.[] | select(.name | test("^(dev|test)-")) | .uuid'
# Delete them in a loop
ravi identity list --json | jq -r '.[] | select(.name | test("^(dev|test)-")) | .uuid' \
| xargs -I{} ravi identity delete {}
Python context manager (per-task identities)
For ephemeral per-task identities, a context manager pairs creation and deletion cleanly:
import subprocess, json
from contextlib import contextmanager
@contextmanager
def ephemeral_identity(name: str):
identity = json.loads(
subprocess.check_output(["ravi", "identity", "create", "--name", name, "--json"])
)
try:
yield identity
finally:
subprocess.run(["ravi", "identity", "delete", identity["uuid"]], check=True)
with ephemeral_identity(f"worker-{task_id}") as identity:
email = identity["inbox"]
# ... agent work ...
# identity is deleted on exit
See Production Patterns for a full TypeScript equivalent and the fleet initialization pattern.
Next steps
- Email — send and receive email from your Identity
- Phone & SMS — receive verification codes
- Multi-Agent Setup — detailed guide for running multiple agents
- Production Patterns — lifecycle management, ephemeral identities, fleet provisioning
- API Endpoints — REST API reference for direct integration