# Piclets Discovery Server API Documentation ## Overview The Piclets Discovery Server provides a Gradio-based API for the Piclets discovery game. Each real-world object has ONE canonical Piclet, with variations tracked based on attributes. All data is stored in a public HuggingFace Dataset. ## Quick Start ### Running Locally ```bash pip install -r requirements.txt python app.py ``` ### Accessing the API - **Web Interface**: http://localhost:7860 - **Programmatic Access**: Use Gradio Client to connect to the space ### Frontend Integration ```javascript import { Client } from "@gradio/client"; const client = await Client.connect("Fraser/piclets-server"); const result = await client.predict("/search_piclet", { object_name: "pillow", attributes: ["velvet"] }); ``` ## API Endpoints ### 1. Search Piclet **Endpoint**: `/search_piclet` **Purpose**: Search for canonical Piclet or variations **Method**: Gradio function call **Input Parameters**: ```json { "object_name": "pillow", "attributes": ["velvet", "blue"] } ``` **Response Types**: **New Object** (no Piclet exists): ```json { "status": "new", "message": "No Piclet found for 'pillow'", "piclet": null } ``` **Existing Canonical** (exact match): ```json { "status": "existing", "message": "Found canonical Piclet for 'pillow'", "piclet": { "objectName": "pillow", "typeId": "pillow_canonical", "discoveredBy": "user123", "discoveredAt": "2024-07-26T10:30:00", "scanCount": 42, "picletData": { /* full Piclet data */ } } } ``` **Variation Found**: ```json { "status": "variation", "message": "Found variation of 'pillow'", "piclet": { /* variation data */ }, "canonicalId": "pillow_canonical" } ``` **New Variation Suggested**: ```json { "status": "new_variation", "message": "No variation found for 'pillow' with attributes ['velvet', 'blue']", "canonicalId": "pillow_canonical", "piclet": null } ``` ### 2. Create Canonical **Endpoint**: `/create_canonical` **Purpose**: Register the first discovery of an object with OAuth verification **Method**: Gradio function call **Input Parameters**: ```json { "object_name": "pillow", "piclet_data": "{ /* JSON string of Piclet instance */ }", "token_or_username": "hf_xxxxxxxxxxxxx" // OAuth token or username for testing } ``` **Success Response**: ```json { "success": true, "message": "Created canonical Piclet for 'pillow'", "piclet": { "objectName": "pillow", "typeId": "pillow_canonical", "discoveredBy": "username123", "discovererSub": "987654321", "discovererUsername": "username123", "discovererName": "Display Name", "discovererPicture": "/static-proxy?url=https%3A%2F%2Favatars.huggingface.co%2F...", "discoveredAt": "2024-07-26T10:30:00", "scanCount": 1, "picletData": { /* full Piclet data */ } } } ``` **Error Responses**: ```json { "success": false, "error": "Invalid OAuth token" } ``` ```json { "success": false, "error": "Failed to save canonical Piclet" } ``` **Notes**: - If `token_or_username` starts with `hf_`, it's verified as an OAuth token - Token verification calls `https://huggingface.co/oauth/userinfo` - User profile is created/updated with cached OAuth fields - Legacy mode: Plain usernames create `legacy_{username}` profiles ### 3. Create Variation **Endpoint**: `/create_variation` **Purpose**: Add a variation to an existing canonical Piclet with OAuth verification **Method**: Gradio function call **Input Parameters**: ```json { "canonical_id": "pillow_canonical", "attributes": ["velvet", "blue"], "piclet_data": "{ /* JSON string of variation data */ }", "token_or_username": "hf_xxxxxxxxxxxxx", // OAuth token or username for testing "object_name": "pillow" } ``` **Success Response**: ```json { "success": true, "message": "Created variation of 'pillow'", "piclet": { "typeId": "pillow_001", "attributes": ["velvet", "blue"], "discoveredBy": "player456", "discovererSub": "123456789", "discovererUsername": "player456", "discovererName": "Player Name", "discovererPicture": "/static-proxy?url=https%3A%2F%2Favatars.huggingface.co%2F...", "discoveredAt": "2024-07-26T11:00:00", "scanCount": 1, "picletData": { /* variation data */ } } } ``` **Error Responses**: ```json { "success": false, "error": "Invalid OAuth token" } ``` ```json { "success": false, "error": "Canonical Piclet not found for 'pillow'" } ``` **Notes**: - Same OAuth verification as create_canonical - User profile updated with variation discovery (+50 rarity points) - Variation numbering is automatic (pillow_001, pillow_002, etc.) ### 4. Increment Scan Count **Endpoint**: `/increment_scan_count` **Purpose**: Track how many times a Piclet has been discovered **Method**: Gradio function call **Input Parameters**: ```json { "piclet_id": "pillow_canonical", "object_name": "pillow" } ``` **Success Response**: ```json { "success": true, "scanCount": 43 } ``` ### 5. Get Recent Activity **Endpoint**: `/get_recent_activity` **Purpose**: Get global discovery feed **Method**: Gradio function call **Input Parameters**: ```json { "limit": 20 } ``` **Response**: ```json { "success": true, "activities": [ { "type": "discovery", "objectName": "pillow", "typeId": "pillow_canonical", "discoveredBy": "user123", "discoveredAt": "2024-07-26T10:30:00", "scanCount": 42 }, { "type": "variation", "objectName": "pillow", "typeId": "pillow_001", "attributes": ["velvet", "blue"], "discoveredBy": "user456", "discoveredAt": "2024-07-26T11:00:00", "scanCount": 5 } ] } ``` ### 6. Get Leaderboard **Endpoint**: `/get_leaderboard` **Purpose**: Get top discoverers by rarity score **Method**: Gradio function call **Input Parameters**: ```json { "limit": 10 } ``` **Response**: ```json { "success": true, "leaderboard": [ { "rank": 1, "username": "explorer123", "totalFinds": 156, "uniqueFinds": 45, "rarityScore": 2340 }, { "rank": 2, "username": "hunter456", "totalFinds": 134, "uniqueFinds": 38, "rarityScore": 1890 } ] } ``` ### 7. Get User Profile **Endpoint**: `/get_user_profile` **Purpose**: Get individual user's discovery statistics **Method**: Gradio function call **Input Parameters**: ```json { "sub": "987654321" // HuggingFace user ID (preferred) or username for legacy } ``` **Response**: ```json { "success": true, "profile": { "sub": "987654321", "preferred_username": "player123", "name": "Player Display Name", "picture": "/static-proxy?url=https%3A%2F%2Favatars.huggingface.co%2F...", "email": "user@example.com", "joinedAt": "2024-07-01T10:00:00", "lastSeen": "2024-07-26T12:00:00", "discoveries": ["pillow_canonical", "chair_002", "lamp_canonical"], "uniqueFinds": 2, "totalFinds": 3, "rarityScore": 250, "visibility": "public" } } ``` **Notes**: - Profile keyed by `sub` (stable HF user ID), not username - OAuth fields (preferred_username, name, picture) cached and refreshed on each login - Legacy profiles have `sub = "legacy_{username}"` - Visibility can be "public" or "private" (future feature) ## Object Normalization Rules The server normalizes object names for consistent storage: 1. Convert to lowercase 2. Remove articles (the, a, an) 3. Handle pluralization: - `pillows` → `pillow` - `berries` → `berry` - `leaves` → `leaf` - `boxes` → `box` 4. Replace spaces with underscores 5. Remove special characters Examples: - `"The Blue Pillow"` → `pillow` - `"wooden chairs"` → `wooden_chair` - `"A pair of glasses"` → `pair_of_glass` ## Rarity Tiers Based on scan count: - **Legendary**: ≤ 5 scans - **Epic**: 6-20 scans - **Rare**: 21-50 scans - **Uncommon**: 51-100 scans - **Common**: > 100 scans ## Scoring System - **Canonical Discovery**: +100 rarity points - **Variation Discovery**: +50 rarity points - **Scan Bonus**: Additional points based on rarity tier ## Error Handling All endpoints return consistent error structures: ```json { "success": false, "error": "Description of what went wrong" } ``` Common error scenarios: - Piclet not found - Invalid JSON data - Failed to save to dataset - Network/connection errors ## Rate Limiting Currently no rate limiting implemented. For production: - Consider adding per-user rate limits - Implement cooldowns for discoveries - Cache frequent requests ## Authentication **OAuth Token Verification** (Production Mode): - Frontend sends `Authorization: Bearer ` headers - Server verifies tokens via `https://huggingface.co/oauth/userinfo` - Returns user info: `sub` (stable ID), `preferred_username`, `name`, `picture`, `email` - User profiles keyed by `sub` (HF user ID) instead of username - Usernames can change, but `sub` remains stable **Legacy Mode** (Testing Only): - For backward compatibility, endpoints accept plain usernames - If token doesn't start with `hf_`, treated as username - Creates legacy user profile with `sub = "legacy_{username}"` **Example OAuth Flow**: ```javascript // Frontend: Get OAuth token from HuggingFace Space import { HfInference } from "https://cdn.jsdelivr.net/npm/@huggingface/inference/+esm"; const auth = await hfAuth.signIn(); // Make authenticated request const response = await fetch('/api/endpoint', { headers: { 'Authorization': `Bearer ${auth.accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ /* payload */ }) }); ``` **Token Verification Process**: 1. Extract Bearer token from Authorization header 2. Call `https://huggingface.co/oauth/userinfo` with token 3. Verify response status 200 4. Extract user info (sub, preferred_username, name, picture) 5. Get or create user profile using `sub` as key 6. Cache profile fields on each request ## Data Storage All data stored in HuggingFace Dataset: - Repository: `Fraser/piclets` - Type: Public dataset - Structure: - `piclets/` - Canonical and variation data - `users/` - User profiles - `metadata/` - Global statistics ## Best Practices 1. **Always normalize object names** before searching 2. **Check for existing Piclets** before creating new ones 3. **Increment scan counts** when rediscovering 4. **Cache responses** on the client side 5. **Handle network errors** gracefully 6. **Validate JSON data** before sending ## Example Workflow 1. User scans an object (e.g., pillow) 2. Extract object name and attributes from caption 3. Search for existing Piclet 4. If new: - Create canonical Piclet - Award discovery bonus 5. If variation: - Create or retrieve variation - Update scan count 6. Update user profile 7. Refresh activity feed ## Support For issues or questions: - Check CLAUDE.md for implementation details - Review example code in app.py - Open an issue in the repository