API Endpoints
Authentication
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/device/ | Request a device code for CLI login |
| POST | /api/auth/device/token/ | Poll for access token (device-code flow) |
| POST | /api/auth/token/refresh/ | Refresh an expired access token |
| POST | /api/auth/login/ | Login with credentials |
| POST | /api/auth/logout/ | Invalidate tokens |
| GET | /api/auth/user/ | Get current user info |
Identities
All Identity endpoints require Bearer authentication.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/identities/ | List all Identities for the authenticated user |
| POST | /api/identities/ | Create a new Identity |
| GET | /api/identities/<uuid>/ | Get a specific Identity |
| PUT | /api/identities/<uuid>/ | Update an Identity |
| DELETE | /api/identities/<uuid>/ | Delete an Identity |
Create Identity request
{
"name": "my-agent"
}
Identity response
{
"uuid": "abc-123",
"name": "my-agent",
"inbox": "my-agent@in.ravi.app",
"phone": "+15551234567",
"created_dt": "2026-02-25T10:30:00Z"
}
Email inbox
Requires X-Ravi-Identity header.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/email-inbox/ | List email threads |
| GET | /api/email-inbox/<thread-id>/ | Get a specific thread with messages |
Query parameters: unread=true
Email messages
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/email-messages/ | List all email messages |
| GET | /api/email-messages/<id>/ | Get a specific email message |
| POST | /api/email-messages/compose/ | Compose and send a new email |
| POST | /api/email-messages/<id>/reply/ | Reply to an email |
| POST | /api/email-messages/<id>/reply-all/ | Reply to all recipients |
Compose request
{
"to": "recipient@example.com",
"subject": "Hello",
"body": "<p>HTML content</p>",
"cc": "",
"bcc": "",
"attachment_uuids": []
}
Email attachments
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/email-attachments/presign/ | Get a presigned upload URL |
The client uploads the file directly to cloud storage using the presigned URL, then includes the returned attachment UUID in the compose request.
SMS inbox
Requires X-Ravi-Identity header.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/sms-inbox/ | List SMS conversations |
| GET | /api/sms-inbox/<conversation-id>/ | Get a specific conversation |
Query parameters: unread=true
SMS messages
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/messages/ | List all SMS messages |
| GET | /api/messages/<id>/ | Get a specific SMS message |
Passwords
Requires X-Ravi-Identity header. All password fields are E2E-encrypted ("e2e::<base64>").
:::note[E2E encryption — CLI only]
The e2e:: prefix means the field value is end-to-end encrypted with the identity’s PIN-derived key. The server never sees plaintext values. This means:
- You cannot generate valid
e2e::ciphertext without the user’s PIN. Encrypted fields must be written via the CLI (ravi passwords create) or the Ravi MCP server — never constructed manually. GETresponses return ciphertext. Only the CLI (or MCP server running locally) can decrypt them. Direct REST callers cannot read these values headlessly.- Use the CLI subprocess pattern or MCP server for vault operations in automated environments. :::
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/passwords/ | List all password entries |
| POST | /api/passwords/ | Create a new password entry |
| GET | /api/passwords/<uuid>/ | Get a specific entry (with ciphertext) |
| PUT | /api/passwords/<uuid>/ | Update a password entry |
| DELETE | /api/passwords/<uuid>/ | Delete a password entry |
| GET | /api/passwords/generate_password/ | Generate a random password |
Create request (with encrypted fields)
{
"domain": "example.com",
"username": "e2e::<base64>",
"password": "e2e::<base64>",
"notes": "e2e::<base64>"
}
Vault secrets
Requires X-Ravi-Identity header. Secret values are E2E-encrypted.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/vault/ | List all secrets (values redacted) |
| POST | /api/vault/ | Create or update a secret |
| GET | /api/vault/<uuid>/ | Get a specific secret |
| DELETE | /api/vault/<uuid>/ | Delete a secret |
Encryption
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/encryption/ | Get encryption metadata (salt, public key, verifier) |
| POST | /api/encryption/ | Upload public key and verifier after first-time PIN setup |
Phone
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/phone/ | Get phone numbers for the active Identity |
Events (SSE)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/events/stream/ | Server-Sent Events stream for real-time email and SMS |
Requires Authorization: Bearer <token> and X-Ravi-Identity: <uuid> headers.
Supports Last-Event-ID header for resuming after disconnection. The server sends keepalive events every 30 seconds.
SSE event types
The stream emits two named event types. Each event’s data field is a JSON object.
email event — fires when a new email arrives in the identity’s inbox:
{
"thread_id": "thread-abc123",
"from_email": "sender@example.com",
"subject": "Your verification code",
"preview": "Your code is 847291. It expires in..."
}
sms event — fires when an inbound SMS is received:
{
"conversation_id": "conv-xyz789",
"from_number": "+15551234567",
"preview": "Your verification code is 847291"
}
Keepalive — a comment line (: keepalive) is sent every 30 seconds to prevent proxy timeouts. No action required.
TypeScript example:
import { EventSource } from "eventsource"; // npm install eventsource
const es = new EventSource("https://ravi.app/api/events/stream/", {
headers: {
Authorization: `Bearer ${token}`,
"X-Ravi-Identity": identityUuid,
},
});
es.addEventListener("sms", (e) => {
const { from_number, preview } = JSON.parse(e.data);
const otp = preview.match(/\b(\d{4,8})\b/)?.[1];
if (otp) handleOtp(otp);
});
es.addEventListener("email", (e) => {
const { thread_id, subject } = JSON.parse(e.data);
console.log(`New email: ${subject} (thread ${thread_id})`);
});
SSE vs. polling: Use SSE for agents that need sub-second reaction time (interactive flows). Use polling (
GET /api/sms-inbox/?unread=true) for batch scripts and CI — simpler to implement and sufficient for most verification workflows.
Billing
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/subscription/ | Get current subscription status |
Returns 402 on endpoints that require an active subscription.
Error reference
All endpoints return standard HTTP status codes. Error responses include a JSON body with a detail field (and sometimes code).
| Status | Meaning | Common cause | Recovery |
|---|---|---|---|
| 400 | Bad request | Missing required field, invalid format | Check request body against the schema above |
| 401 | Unauthorized | Missing or expired Bearer token | Refresh via POST /api/auth/token/refresh/ or re-authenticate |
| 402 | Payment required | Action requires an active Ravi subscription | Upgrade at ravi.app/billing |
| 403 | Forbidden | Token valid but missing scope, or identity not owned by user | Verify X-Ravi-Identity UUID belongs to the authenticated account |
| 404 | Not found | UUID does not exist or was deleted | Re-list the resource to confirm it exists |
| 409 | Conflict | Identity name or email already taken | Choose a different name or check existing identities |
| 429 | Rate limited | Too many requests | Back off and retry; email send limits are 60/hr and 500/day |
| 500 | Server error | Unexpected server-side failure | Retry with exponential backoff; report persistent errors |
Error response shape
{
"detail": "Authentication credentials were not provided.",
"code": "not_authenticated"
}
For validation errors (400), the body may contain field-level detail:
{
"name": ["This field is required."]
}
Token refresh
When a 401 is returned with "code": "token_expired", refresh the access token:
curl -s -X POST https://ravi.app/api/auth/token/refresh/ \
-H "Content-Type: application/json" \
-d '{"refresh": "<refresh_token>"}' \
| jq .access_token
The new access token is returned as access_token in the response. Store it and retry the original request.