piclets-server / API_DOCUMENTATION.md
Fraser's picture
UPDATE
f9201f6
# 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": "https://avatars.huggingface.co/...",
"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": "https://avatars.huggingface.co/...",
"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": "https://avatars.huggingface.co/...",
"email": "[email protected]",
"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 <hf_token>` 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