Skip to content

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 Cache

Prompt Engineering

Profile photo prompts are generated from user data:

  1. User attributes: age range, interests, style preferences
  2. Activity data: primary activities from user profile
  3. Style template: "professional headshot", "casual portrait", "artistic", etc.
  4. Quality modifiers: "photorealistic", "4k", "studio lighting"

Example Prompt Generation

javascript
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

ParameterValueDescription
Model@cf/stabilityai/stable-diffusionWorkers AI image generation model
Resolution512x512 or 1024x1024Output image dimensions
Steps20-30Inference steps (quality vs speed)
Guidance Scale7-9Prompt adherence strength
Negative Promptblur, distorted, low quality, etc.Attributes to avoid

Storage & Caching

R2 Bucket Structure

profile-photos/
  ├── 000000000000000123456789.jpg
  ├── 000000000000000987654321.jpg
  └── default-avatar.jpg

Key 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

NameTypeDescription
idstringUser ID (24-digit string)

Query Parameters

NameTypeDescription
forcebooleanForce regeneration (bypass cache), default: false
stylestringPhoto style override (professional/casual/artistic)
sizenumberImage size in pixels (512/1024), default: 512

Responses

200 OK:

json
{
	"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

NameTypeDescription
idstringUser ID (24-digit string)

Responses

204 No Content — cached photo deleted successfully

json
{
	"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

NameTypeDescription
idstringUser ID (24-digit string)

Request Body

json
{
	"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:

json
{
	"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 TypeValueWindow
Generations per user51 hour
Total generations1001 hour
Cache bypasses31 hour

Rate limits reset on a rolling window basis.

Error Handling

Generation Failures

If AI model fails to generate image:

  1. Retry once with simplified prompt
  2. If retry fails, return default avatar
  3. Log error for monitoring
  4. Return 500 status with fallback URL

Fallback Response

json
{
	"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

  1. Aggressive caching: 30-day TTL reduces regenerations
  2. Rate limiting: Prevents abuse and controls AI model usage
  3. Default avatars: Fallback for failed generations
  4. Lazy generation: Only generate on first request (not on user creation)
  5. 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

bash
curl https://cloud.earth-app.com/users/profile_photo/000000000000000123456789

Force regeneration with custom style

bash
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

bash
curl -X DELETE https://cloud.earth-app.com/users/profile_photo/000000000000000123456789 \
  -H "Authorization: Bearer $TOKEN"

Custom generation

bash
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"
  }'