SSO — Identity Verification
What is Ravi SSO?
Ravi SSO lets an agent prove its identity to any third-party website. The website can verify that the agent is backed by a real Ravi identity — with a real email, phone, and a verified human owner — without registering with Ravi first.
Think of it as a portable, machine-readable "verified agent" badge. One API call to create, one API call to verify.
Agent Website Ravi
│ │ │
│── POST /api/sso/token/ ───────────────────────────────→│ (identity API key)
│←── { token: "rvt_..." } ──────────────────────────────│
│ │ │
│── "here is my identity" ─→│ │
│ (passes token) │ │
│ │── POST /api/sso/verify/ ─→│ (no auth needed)
│ │←── { identity + owner } ──│
│ │ │
For agents: get a verification token
Call POST /api/sso/token/ with your identity API key. No request body needed — Ravi knows which identity you are from your key.
curl -X POST https://ravi.id/api/sso/token/ \
-H "Authorization: Bearer ravi_id_your_key_here"
Response (201 Created):
{
"token": "rvt_eyJhbGciOiJIUzI1NiIs...",
"expires_at": "2026-04-06T12:05:00Z"
}
The token is valid for 5 minutes. Pass it to the website you want to authenticate with.
Token security
The `rvt_` token is a signed JWT — it cannot be forged, but it can be reused within its 5-minute window. Generate a fresh token for each authentication attempt.For websites: verify a token
When an agent presents a rvt_ token, verify it by calling Ravi's public endpoint. No API key, no registration, no account needed.
curl -X POST https://ravi.id/api/sso/verify/ \
-H "Content-Type: application/json" \
-d '{"token": "rvt_eyJhbGciOiJIUzI1NiIs..."}'
Response (200 OK):
{
"identity_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"identity_name": "research-agent",
"identity_email": "research-agent@in.ravi.id",
"identity_phone": "+12025551234",
"created_at": "2026-01-15T10:30:00Z",
"owner": {
"name": "Jane Smith",
"email": "jane@example.com"
}
}
What you learn from verification
| Field | Meaning |
|---|---|
identity_uuid |
Stable unique identifier for this agent persona |
identity_name |
Human-readable name assigned by the owner |
identity_email |
The agent's real email address (receives mail) |
identity_phone |
The agent's real phone number (receives SMS) |
created_at |
When the identity was created |
owner.name |
Name of the human who owns this agent |
owner.email |
Email of the human owner |
identity_email and identity_phone may be null if the identity was created without provisioning those resources.
Error responses
| Status | Meaning |
|---|---|
400 |
Missing token field in request body |
401 |
Token is expired, has an invalid signature, or is malformed |
404 |
Token is valid but the identity has been deleted |
429 |
Rate limited — max 60 requests per minute per IP |
Integration examples
Python (website verifying an agent)
import httpx
def verify_agent(token: str) -> dict | None:
"""Verify a Ravi SSO token. Returns identity info or None."""
resp = httpx.post(
"https://ravi.id/api/sso/verify/",
json={"token": token},
)
if resp.status_code == 200:
return resp.json()
return None
# In your endpoint handler:
identity = verify_agent(request.data["ravi_token"])
if identity is None:
return Response({"error": "Invalid agent identity"}, status=401)
# Agent is verified — use identity["identity_uuid"] as a stable user ID
print(f"Agent: {identity['identity_name']}")
print(f"Owner: {identity['owner']['name']} ({identity['owner']['email']})")
TypeScript (website verifying an agent)
interface RaviIdentity {
identity_uuid: string;
identity_name: string;
identity_email: string | null;
identity_phone: string | null;
created_at: string;
owner: { name: string; email: string };
}
async function verifyAgent(token: string): Promise<RaviIdentity | null> {
const resp = await fetch("https://ravi.id/api/sso/verify/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token }),
});
if (resp.ok) return resp.json();
return null;
}
// In your route handler:
const identity = await verifyAgent(req.body.ravi_token);
if (!identity) {
return res.status(401).json({ error: "Invalid agent identity" });
}
// Agent is verified
console.log(`Agent: ${identity.identity_name}`);
console.log(`Owner: ${identity.owner.name} (${identity.owner.email})`);
Agent-side: requesting a token (Python)
import httpx
RAVI_API_KEY = "ravi_id_your_key_here"
def get_verification_token() -> str:
resp = httpx.post(
"https://ravi.id/api/sso/token/",
headers={"Authorization": f"Bearer {RAVI_API_KEY}"},
)
resp.raise_for_status()
return resp.json()["token"]
# Present this token to any website that supports Ravi SSO
token = get_verification_token()
How it works
- The agent calls
POST /api/sso/token/with its identity API key - Ravi creates a signed JWT containing the identity UUID, a verification token type, and a 5-minute expiry
- The token is prefixed with
rvt_to distinguish it from other tokens - The agent passes this token to the website (via API call, form field, header — your choice)
- The website sends the token to
POST /api/sso/verify/ - Ravi verifies the signature and expiry, looks up the identity, and returns the identity and owner details
The token is stateless — Ravi doesn't store it. Verification is a pure cryptographic check plus a database lookup for the identity details.
Design decisions
| Decision | Rationale |
|---|---|
| No website registration | Any website can verify tokens without an account — zero friction for adoption |
| 5-minute token TTL | Agent-to-website handoff is fast (no human in the loop); short window limits exposure |
| Stateless JWT | No database table for tokens; simple, scalable |
| Owner info included | Websites need to know there's a real human behind the agent — that's the whole value proposition |
| Public verify endpoint | Maximizes adoption — websites just need one curl call |
Next steps
- Identities — create and manage agent identities
- API Authentication — how API keys work
- API Endpoints — full REST API reference