Profile Photos
The Profile Photos service generates photorealistic AI-powered profile images using Cloudflare Workers AI models, with results cached in R2 storage and delivered via CDN.
Architecture
- Generation: Cloudflare Workers AI (Stable Diffusion or similar image model)
- Storage: Cloudflare R2 (S3-compatible object storage)
- Delivery: Cloudflare CDN with automatic edge caching
- Fallback: Default avatars for failed generations
Workflow
User Request
↓
Check R2 Cache
↓ (miss)
Generate Prompt
↓
Call Workers AI Image Model
↓
Upload to R2
↓
Return CDN URL
↓ (subsequent requests)
Serve from CDN CachePrompt Engineering
Profile photo prompts are generated from user data:
- User attributes: age range, interests, style preferences
- Activity data: primary activities from user profile
- Style template: "professional headshot", "casual portrait", "artistic", etc.
- Quality modifiers: "photorealistic", "4k", "studio lighting"
Example Prompt Generation
function generatePrompt(user) {
const activities = user.activities.slice(0, 3).join(', ');
const style = user.preferences?.photo_style || 'professional';
return `${style} portrait photo of a person interested in ${activities},
photorealistic, studio lighting, 4k quality, headshot`;
}AI Model Configuration
| Parameter | Value | Description |
|---|---|---|
| Model | @cf/stabilityai/stable-diffusion | Workers AI image generation model |
| Resolution | 512x512 or 1024x1024 | Output image dimensions |
| Steps | 20-30 | Inference steps (quality vs speed) |
| Guidance Scale | 7-9 | Prompt adherence strength |
| Negative Prompt | blur, distorted, low quality, etc. | Attributes to avoid |
Storage & Caching
R2 Bucket Structure
profile-photos/
├── 000000000000000123456789.jpg
├── 000000000000000987654321.jpg
└── default-avatar.jpgKey Format
profile-photos/{user_id}.jpg
Example: profile-photos/000000000000000123456789.jpg
CDN Cache
- TTL: 30 days (2,592,000 seconds)
- Cache-Control:
public, max-age=2592000, immutable - Purge: Manual via DELETE endpoint
Endpoints
GET /users/profile_photo/:id
Generate new profile photo or return cached version.
Path Parameters
| Name | Type | Description |
|---|---|---|
| id | string | User ID (24-digit string) |
Query Parameters
| Name | Type | Description |
|---|---|---|
| force | boolean | Force regeneration (bypass cache), default: false |
| style | string | Photo style override (professional/casual/artistic) |
| size | number | Image size in pixels (512/1024), default: 512 |
Responses
200 OK:
{
"url": "https://cdn.earth-app.com/profile-photos/000000000000000123456789.jpg",
"user_id": "000000000000000123456789",
"generated_at": "2025-01-10T16:00:00Z",
"cached": true,
"size": 512,
"style": "professional"
}404 Not Found — user does not exist
401 Unauthorized — authentication required
429 Too Many Requests — rate limit exceeded (max 5 generations per hour per user)
500 Internal Server Error — AI model generation failed
DELETE /users/profile_photo/:id
Invalidate cached profile photo and trigger regeneration.
Path Parameters
| Name | Type | Description |
|---|---|---|
| id | string | User ID (24-digit string) |
Responses
204 No Content — cached photo deleted successfully
{
"message": "Profile photo cache invalidated",
"user_id": "000000000000000123456789"
}401 Unauthorized — authentication required
403 Forbidden — can only delete own profile photo (unless admin)
404 Not Found — no cached photo found
POST /users/profile_photo/:id/generate
Explicitly generate new profile photo with custom parameters.
Path Parameters
| Name | Type | Description |
|---|---|---|
| id | string | User ID (24-digit string) |
Request Body
{
"style": "artistic",
"size": 1024,
"prompt_override": "watercolor portrait of a nature photographer, soft lighting",
"negative_prompt": "blur, distorted, low quality, unrealistic"
}All fields are optional. System generates appropriate defaults from user profile.
Responses
201 Created:
{
"url": "https://cdn.earth-app.com/profile-photos/000000000000000123456789.jpg",
"user_id": "000000000000000123456789",
"generated_at": "2025-01-10T16:30:00Z",
"cached": false,
"size": 1024,
"style": "artistic",
"prompt": "watercolor portrait of a nature photographer, soft lighting",
"generation_time_ms": 3450
}400 Bad Request — invalid parameters
401 Unauthorized
429 Too Many Requests
500 Internal Server Error — generation failed
Rate Limiting
To prevent abuse and control AI model costs:
| Limit Type | Value | Window |
|---|---|---|
| Generations per user | 5 | 1 hour |
| Total generations | 100 | 1 hour |
| Cache bypasses | 3 | 1 hour |
Rate limits reset on a rolling window basis.
Error Handling
Generation Failures
If AI model fails to generate image:
- Retry once with simplified prompt
- If retry fails, return default avatar
- Log error for monitoring
- Return 500 status with fallback URL
Fallback Response
{
"url": "https://cdn.earth-app.com/profile-photos/default-avatar.jpg",
"user_id": "000000000000000123456789",
"fallback": true,
"error": "AI model generation failed",
"retry_after": 300
}Monitoring
Key metrics tracked:
- Generation success rate
- Average generation time
- Cache hit ratio
- R2 storage usage
- CDN bandwidth usage
- Rate limit violations
Cost Optimization
- Aggressive caching: 30-day TTL reduces regenerations
- Rate limiting: Prevents abuse and controls AI model usage
- Default avatars: Fallback for failed generations
- Lazy generation: Only generate on first request (not on user creation)
- Size optimization: Default 512x512 balances quality and cost
Integration
- Mantle2: Validates user IDs and retrieves user profile data for prompt generation
- Crust: Displays profile photos on user profiles, event attendees, article authors
- KV: Stores generation timestamps and rate limit counters
Security
- Authentication: Required for generation endpoints
- Authorization: Users can only regenerate their own photos (unless admin)
- Content moderation: Negative prompts prevent inappropriate content
- Rate limiting: Prevents abuse and cost overruns
Example Usage
Get cached photo or generate new
curl https://cloud.earth-app.com/users/profile_photo/000000000000000123456789Force regeneration with custom style
curl -X GET "https://cloud.earth-app.com/users/profile_photo/000000000000000123456789?force=true&style=artistic&size=1024" \
-H "Authorization: Bearer $TOKEN"Delete cached photo
curl -X DELETE https://cloud.earth-app.com/users/profile_photo/000000000000000123456789 \
-H "Authorization: Bearer $TOKEN"Custom generation
curl -X POST https://cloud.earth-app.com/users/profile_photo/000000000000000123456789/generate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"style": "artistic",
"size": 1024,
"prompt_override": "watercolor portrait of a photographer"
}'