http://localhost:8000
- Type: JWT Bearer Token
- Token Location: Query parameter
authorizationORAuthorizationheader - Token Lifespan: 30 days
- Format:
Bearer <token>(header) or?authorization=<token>(query param)
| Category | Route | Method | Protected | Purpose |
|---|---|---|---|---|
| Auth | /auth/register |
POST | ❌ | Register new user |
/auth/login |
POST | ❌ | Login user | |
/auth/me |
GET | ✅ | Get current user profile | |
| Questions | /questions/create |
POST | ✅ | Create new question |
/questions/nearby |
GET | ❌ | Get nearby questions | |
/questions/{id} |
GET | ❌ | Get question detail with answers | |
/questions/search/results |
GET | ❌ | Search questions by title | |
| Answers | /answers/submit |
POST | ✅ | Submit answer with ML verification |
/answers/{id} |
GET | ❌ | Get answer detail | |
/answers/{id}/approve |
POST | ✅ | Approve answer (admin) | |
/answers/{id}/reject |
POST | ✅ | Reject answer (admin) | |
| Votes | /votes/answers/{id} |
POST | ✅ | Upvote/downvote answer |
| Leaderboard | /leaderboard |
GET | ❌ | Get leaderboard rankings |
/leaderboard/user/{id} |
GET | ❌ | Get user's rank | |
| Notifications | /notifications |
GET | ✅ | Get user notifications |
/notifications/{id}/read |
POST | ✅ | Mark notification as read |
POST /auth/register
Request Body:
{
"username": "john_doe",
"email": "john@example.com",
"password": "secure_password_123"
}Response (200):
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "user-uuid-123",
"username": "john_doe",
"email": "john@example.com",
"credits": 0,
"reputation": 0,
"total_answers": 0,
"upvotes_received": 0
}
}Error Responses:
400- Email already registered or username taken500- Failed to create user
POST /auth/login
Request Body:
{
"email": "john@example.com",
"password": "secure_password_123"
}Response (200):
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "user-uuid-123",
"username": "john_doe",
"email": "john@example.com",
"credits": 0,
"reputation": 0
}
}Error Responses:
401- Invalid email or password
GET /auth/me?authorization=<token>
Headers (Alternative):
Authorization: Bearer <token>
Response (200):
{
"id": "user-uuid-123",
"username": "john_doe",
"email": "john@example.com",
"credits": 10,
"reputation": 25,
"total_answers": 3,
"upvotes_received": 5
}Error Responses:
401- Authorization required / Invalid token / Token expired
POST /questions/create?authorization=<token>
Request Body:
{
"title": "What's this building?",
"description": "Can anyone identify this historic building in downtown?",
"latitude": 40.7128,
"longitude": -74.0060,
"radius_meters": 500,
"category": "architecture"
}Response (200):
{
"success": true,
"message": "Question created successfully",
"data": {
"question_id": "q-uuid-456"
}
}Error Responses:
401- Authorization required500- Failed to create question
GET /questions/nearby?latitude=40.7128&longitude=-74.0060&radius=5000
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
latitude |
float | ✅ | - | Latitude coordinate |
longitude |
float | ✅ | - | Longitude coordinate |
radius |
integer | ❌ | 1000 | Search radius in meters |
Response (200):
{
"questions": [
{
"id": "q-uuid-456",
"user_id": "user-uuid-123",
"title": "What's this building?",
"description": "Can anyone identify this historic building?",
"latitude": 40.7128,
"longitude": -74.0060,
"created_at": "2026-03-01T10:30:00Z",
"status": "active",
"answer_count": 3,
"upvote_count": 5,
"user": {
"username": "john_doe",
"reputation": 25
}
}
]
}GET /questions/{question_id}
Path Parameters:
question_id(string, required) - UUID of question
Response (200):
{
"id": "q-uuid-456",
"user_id": "user-uuid-123",
"title": "What's this building?",
"description": "Can anyone identify this historic building?",
"latitude": 40.7128,
"longitude": -74.0060,
"created_at": "2026-03-01T10:30:00Z",
"status": "active",
"answer_count": 3,
"upvote_count": 5
}Error Responses:
404- Question not found
GET /questions/search/results?q=building
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
q |
string | ✅ | Search query (min 1 char) |
Response (200):
[
{
"id": "q-uuid-456",
"title": "What's this building?",
"description": "Can anyone identify...",
"latitude": 40.7128,
"longitude": -74.0060,
"created_at": "2026-03-01T10:30:00Z",
"answer_count": 3
}
]POST /answers/submit?authorization=<token>
Request Body:
{
"question_id": "q-uuid-456",
"image_base64": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
"text_answer": "This is a historic city hall building built in 1850s",
"gps_latitude": 40.7128,
"gps_longitude": -74.0060,
"gps_accuracy": 15.5,
"verification": {
"gyro_pitch": 12.34,
"gyro_roll": -5.67,
"gyro_yaw": 89.12,
"gyro_valid": true,
"ssim_score": 0.92,
"ssim_valid": true,
"timestamp": "2026-03-01T10:35:00Z"
}
}Notes:
- Image must be base64 encoded
- GPS coordinates must be within question's radius
- Verification data is optional but recommended
- ML pipeline runs in background (background task)
- Image uploaded to Supabase Storage automatically
Response (200):
{
"success": true,
"message": "Answer submitted successfully",
"data": {
"answer_id": "a-uuid-789"
}
}Error Responses:
401- Authorization required404- Question not found400- Invalid image format500- Failed to submit answer
Background ML Pipeline:
- ✅ Image Quality Check (blur detection)
- ✅ NSFW Detection
- ✅ Face Detection & Blur
- ✅ Object Detection
- ✅ OCR Text Extraction
- ✅ Semantic Similarity Matching (Gemini Embeddings)
- ✅ Auto-approval if relevant + high quality + non-NSFW
- ✅ Reputation & Credits awarded
GET /answers/{answer_id}
Path Parameters:
answer_id(string, required) - UUID of answer
Response (200):
{
"id": "a-uuid-789",
"question_id": "q-uuid-456",
"user_id": "user-uuid-123",
"image_url": "https://storage.supabase.co/answers/q-uuid/user-uuid_answer_123456.jpg",
"text_answer": "This is a historic city hall building",
"created_at": "2026-03-01T10:35:00Z",
"status": "approved",
"upvote_count": 5,
"downvote_count": 1,
"helpful_score": 0.83,
"analysis": {
"quality": 0.85,
"nsfw": 0.02,
"faces": 0,
"similarity": 0.779,
"objects": ["building", "urban", "architecture"],
"ocr": "Historic City Hall 1850s",
"caption": "Detected: building, urban, architecture"
}
}POST /answers/{answer_id}/approve?authorization=<token>
Response (200):
{
"success": true,
"message": "Answer approved"
}POST /answers/{answer_id}/reject?authorization=<token>
Response (200):
{
"success": true,
"message": "Answer rejected"
}POST /votes/answers/{answer_id}?authorization=<token>
Path Parameters:
answer_id(string, required) - UUID of answer
Request Body:
{
"vote_type": "upvote"
}Vote Types:
upvote- Like/helpful answerdownvote- Dislike/unhelpful answer
Response (200):
{
"success": true,
"message": "Vote recorded",
"data": {
"answer_id": "a-uuid-789",
"upvotes": 6,
"downvotes": 1,
"helpful_score": 0.857
}
}Error Responses:
401- Authorization required400- Invalid vote type500- Failed to vote
GET /leaderboard?limit=20&offset=0
Query Parameters:
| Parameter | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
limit |
integer | ❌ | 10 | 1-100 | Number of entries |
offset |
integer | ❌ | 0 | ≥0 | Pagination offset |
Response (200):
{
"leaderboard": [
{
"rank": 1,
"username": "top_contributor",
"avatar_url": "https://...",
"reputation": 250,
"credits": 100,
"total_answers": 25,
"upvotes_received": 45
},
{
"rank": 2,
"username": "john_doe",
"avatar_url": "https://...",
"reputation": 150,
"credits": 50,
"total_answers": 15,
"upvotes_received": 30
}
]
}GET /leaderboard/user/{user_id}
Path Parameters:
user_id(string, required) - UUID of user
Response (200):
{
"success": true,
"rank": 5,
"reputation": 120,
"total_answers": 12,
"upvotes_received": 20
}Error Responses:
404- User not found on leaderboard
GET /notifications?limit=20&offset=0&authorization=<token>
Query Parameters:
| Parameter | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
limit |
integer | ❌ | 20 | 1-100 | Number of notifications |
offset |
integer | ❌ | 0 | ≥0 | Pagination offset |
Headers OR Query:
authorizationquery parameter with token ORAuthorization: Bearer <token>header
Response (200):
{
"notifications": [
{
"id": "notif-uuid-123",
"notification_type": "answer_approved",
"message": "Your answer was approved!",
"question_id": "q-uuid-456",
"is_read": false,
"created_at": "2026-03-01T11:00:00Z"
},
{
"id": "notif-uuid-124",
"notification_type": "answer_upvoted",
"message": "Someone upvoted your answer",
"question_id": "q-uuid-456",
"is_read": true,
"created_at": "2026-03-01T10:50:00Z"
}
]
}POST /notifications/{notification_id}/read?authorization=<token>
Path Parameters:
notification_id(string, required) - UUID of notification
Response (200):
{
"success": true,
"message": "Notification marked as read"
}Error Responses:
401- Authorization required500- Failed to mark as read
{
"id": "uuid",
"username": "string",
"email": "string",
"password_hash": "string (hashed)",
"avatar_url": "string (optional)",
"credits": "integer",
"reputation": "integer",
"total_answers": "integer",
"upvotes_received": "integer",
"created_at": "datetime",
"updated_at": "datetime"
}
{
"id": "uuid",
"user_id": "uuid (foreign key)",
"title": "string",
"description": "string",
"latitude": "float",
"longitude": "float",
"radius_meters": "integer",
"category": "string",
"status": "enum (active, closed, resolved)",
"answer_count": "integer",
"upvote_count": "integer",
"created_at": "datetime"
}
{
"id": "uuid",
"question_id": "uuid (foreign key)",
"user_id": "uuid (foreign key)",
"image_url": "string (Supabase storage URL)",
"text_answer": "string",
"status": "enum (pending, approved, rejected)",
"upvote_count": "integer",
"downvote_count": "integer",
"helpful_score": "float (0.0-1.0)",
"created_at": "datetime"
}
{
"id": "uuid",
"answer_id": "uuid (foreign key)",
"image_quality": "float (0.0-1.0)",
"is_motion_blurred": "boolean",
"nsfw_score": "float (0.0-1.0)",
"contains_faces": "boolean",
"face_count": "integer",
"objects_detected": ["string"],
"scene_caption": "string",
"ocr_text": "string",
"semantic_similarity": "float (0.0-1.0)",
"final_status": "enum (approved, pending_review, rejected)"
}
1. User submits question with GPS location
POST /questions/create
2. Frontend captures nearby questions
GET /questions/nearby?latitude=40.7128&longitude=-74.0060
3. User selects answer question and captures photo
- Camera capture
- Get GPS coordinates
- Capture gyroscope data
4. Submit answer with all verifications
POST /answers/submit
- Image base64
- Text answer
- GPS coordinates (must match question radius)
- Gyroscope data
5. Backend ML Pipeline (background task):
- Analyze image quality
- Check for NSFW content
- Blur faces for privacy
- Detect objects
- Extract OCR text
- Match semantic similarity with Gemini
- Auto-approve if all checks pass
6. User gets upvotes on answer
POST /votes/answers/{answer_id}
- Vote count increases
- Reputation earned
7. View leaderboard rankings
GET /leaderboard?limit=100
1. Register new user
POST /auth/register
→ Get JWT token valid for 30 days
2. Use token in subsequent requests
GET /auth/me?authorization=<token>
OR
GET /auth/me
Header: Authorization: Bearer <token>
3. Token expires after 30 days
→ User must login again
POST /auth/login
All errors follow this format:
{
"detail": "Error message here"
}- No rate limits implemented (can be added)
- Consider adding for production
- Configured for frontend at
http://localhost:3000 - Can accept requests from specified origins
- Images stored in Supabase Storage bucket:
answers - Auto-bucket creation on first upload
- Public URL generation for frontend access
- Base64 encoding/decoding handled server-side
- Quality Check: Blur score via Laplacian variance
- NSFW Detection: Custom CNN model
- Face Detection: MTCNN with automatic blur
- Object Detection: YOLOv5 pre-trained
- OCR: EasyOCR
- Embeddings: Google Gemini
text-embedding-004 - Similarity Threshold: 0.65 (auto-approved if > threshold)
JWT Header: {
"alg": "HS256",
"typ": "JWT"
}
JWT Payload: {
"sub": "user-uuid-123",
"email": "user@example.com",
"exp": 1809523200,
"iat": 1741987200
}
The React frontend uses Axios to call these endpoints with:
- Automatic token injection from Zustand store
- Toast notifications for errors
- Background ML processing status polling
- Real-time vote count updates
- Map-based question browser with Leaflet.js
See frontend/src/services/api.ts for complete frontend wrapper implementation.
- Check token validity: Call
/auth/meto verify token - Test without auth: Use public endpoints first
- Monitor ML pipeline: Check backend terminal for
[ML PIPELINE]logs - Image upload issues: Ensure base64 is properly formatted
- GPS verification: Ensure answer GPS is within question radius
- Database: Check Supabase console for data
- All timestamps in UTC (ISO 8601 format)
- All IDs are UUIDs (v4)
- All distances in meters
- All coordinates in WGS84 (lat/lng)
- Reputation system: +10 per approved answer, +1 per upvote
- Credits system: +10 per approved answer, usable for badges/features