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

  1. The agent calls POST /api/sso/token/ with its identity API key
  2. Ravi creates a signed JWT containing the identity UUID, a verification token type, and a 5-minute expiry
  3. The token is prefixed with rvt_ to distinguish it from other tokens
  4. The agent passes this token to the website (via API call, form field, header — your choice)
  5. The website sends the token to POST /api/sso/verify/
  6. 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