Reeve
Services API

Authentication

Session-based auth with Clerk integration, magic links, and service-to-service tokens.

Services Authentication

The Services API uses session-based authentication backed by Clerk for user identity and shared tokens for service-to-service communication.

Auth Flow

User → Clerk (sign in) → JWT → POST /api/auth/session → Session Token (UUID)

                                          All subsequent API calls use this token

Session Creation

After Clerk authentication, the frontend exchanges the Clerk JWT for a Reeve session token:

POST /api/auth/session
Authorization: Bearer <clerk-jwt>

# Response:
{
  "session_token": "550e8400-e29b-41d4-a716-446655440000",
  "user_id": "user_abc123",
  "email": "user@example.com"
}

Session Validation

Every API request validates the session token:

async def get_current_user(
    authorization: str = Header(None),
    db: Session = Depends(get_db),
) -> AuthUser:
    token = extract_bearer_token(authorization)
    if token:
        session = validate_session(db, token)
        if session:
            return AuthUser(
                user_id=session.user_id,
                email=session.email
            )
    raise HTTPException(status_code=401, detail="Not authenticated")

Auth Context

Every authenticated endpoint receives an AuthUser:

@router.get("/api/credits/balance")
async def get_balance(user: AuthUser = Depends(get_current_user)):
    return credit_service.get_balance(user.user_id)

Service-to-Service Auth

The gateway authenticates with the Services API using a shared secret:

def verify_service_token(
    x_reeve_services_token: str = Header(None),
) -> bool:
    expected = settings.reeve_services_token
    if not x_reeve_services_token or x_reeve_services_token != expected:
        raise HTTPException(status_code=401, detail="Invalid service token")
    return True

Used for:

  • Credit deductions (gateway → services after LLM calls)
  • Usage tracking
  • Tenant provisioning

Guest Mode

For demo and unauthenticated access:

async def get_current_user_or_guest(
    x_guest_id: str = Header(None),
    authorization: str = Header(None),
    db: Session = Depends(get_db),
) -> AuthUser:
    # Try session token first
    if authorization:
        # ... validate session
        pass
    
    # Fall back to guest
    if x_guest_id:
        return AuthUser(user_id=x_guest_id, is_guest=True)
    
    raise HTTPException(status_code=401)

Guest users get read-only access to demo data. Write operations check user.is_guest and reject accordingly.

Session tokens are UUIDs stored in PostgreSQL, not JWTs. This means they can be revoked instantly by deleting the row — no waiting for token expiry.

On this page