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:

  1. .ravi/config.json in the current working directory (project-level override)
  2. ~/.ravi/config.json (global default)
  3. 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 use in concurrent processes. It writes a shared global config file — calling it from two processes simultaneously corrupts identity context. Instead, set the RAVI_CONFIG_DIR environment 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 caseRecommended modelRationale
Named agent (research-agent, finance-bot)PersistentReputation, inbox history, continuity across restarts
Agent registered on a marketplacePersistentStable email builds reputation; replacements start from zero
Per-worktree coding agentPersistent (delete on project close)Consistent identity across many parallel sessions
Per-task scratch workEphemeralNo long-term inbox needed; delete on task completion
Test / CI verificationEphemeralPrefix 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

PatternLifecycle
One permanent agentCreate once, never delete
One identity per projectDelete when the project is decommissioned
One identity per taskDelete on task completion — clean up immediately
Dev/test identitiesPrefix 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