🚪 2389 Agent Avatar Service

Deterministic avatar generation with 1.27 billion unique variants

Try it Live

Features

🎲 Deterministic

Same input always generates the same avatar. Perfect for user profiles.

⚡ Fast

File-based caching ensures instant delivery for previously generated avatars.

🎨 Unique

1.27 billion possible combinations ensure collision-resistant generation for ~1M users.

📦 Simple

Just hit a URL with any string. No API keys, no auth, no hassle.

API Documentation

GET /avatar/{input}.svg
Generate and serve an SVG avatar for any input string.
curl https://avatar.2389.dev/avatar/test@example.com.svg
GET /avatar/{input}.svg/info
Get JSON configuration details for an avatar without generating the image.
curl https://avatar.2389.dev/avatar/test@example.com.svg/info
GET /avatar/{input}/bundle
Generate a ZIP bundle containing PDF sprites for multiple animation sets. Returns a downloadable ZIP file with all frames organized by animation type, plus metadata.
curl https://avatar.2389.dev/avatar/user@example.com/bundle \
  --output avatar_bundle.zip

# Optional: specify which animations to include
curl "https://avatar.2389.dev/avatar/user@example.com/bundle?animations=idle,emotes" \
  --output avatar_bundle.zip
Animation types:
  • idle - 4 frames (idle_0, idle_1, idle_2, idle_3)
  • emotes - 5 frames (happy, sad, surprised, angry, bored)
  • vowels - 5 frames (vowel_A, vowel_E, vowel_I, vowel_O, vowel_U)

Usage Examples

HTML Image Tag

<img src="https://avatar.2389.dev/avatar/user@email.com.svg" width="60" height="60" />

Markdown

![Avatar](https://avatar.2389.dev/avatar/username.svg)

Random String

<img src="https://avatar.2389.dev/avatar/x7k9m2p5q8.svg" width="60" height="60" />

CSS Background

.profile {
  background-image: url('https://avatar.2389.dev/avatar/user123.svg');
  background-size: cover;
}

JavaScript Fetch

const response = await fetch('/avatar/user@example.com.svg/info');
const config = await response.json();
console.log(config); // Avatar configuration details

🎬 Animations

Avatars support 19 animation frames for games and interactive applications:

  • Emotes: happy, sad, surprised, angry, bored, neutral
  • Idle animation: 4 frames for breathing/bobbing
  • Lip-sync: 5 vowel mouth shapes (A, E, I, O, U)

View Animation Documentation & Live Demo →

CSS State Control (Universal SVG)

Universal SVG avatars contain all animation states in a single file (~3.8 KB gzipped). Switch between states instantly using CSS classes - no additional HTTP requests needed. The "idle" state automatically animates through 10 frames.

Interactive Demo

Select a state to see the avatar change in real-time (idle animates automatically):

Current class: agent

Code Example

Switching states is as simple as changing the SVG's CSS class:

// Fetch the universal SVG once
const response = await fetch('/avatar/user@example.com.svg');
const svgText = await response.text();

// Parse and insert into DOM (use DOMParser to preserve CSS selectors)
const parser = new DOMParser();
const svgDoc = parser.parseFromString(svgText, 'image/svg+xml');
const svgElement = svgDoc.documentElement;
document.getElementById('container').appendChild(svgElement);

// Switch states instantly by changing the class
svgElement.className.baseVal = 'agent happy';      // Show happy emote
svgElement.className.baseVal = 'agent idle_0';     // Back to idle
svgElement.className.baseVal = 'agent vowel_a';    // Lip-sync to 'ah' sound

// Idle animation loop (breathing/bobbing effect)
const idleFrames = ['idle_0', 'idle_1', 'idle_2', 'idle_3', 'idle_2', 'idle_1'];
let frameIndex = 0;
setInterval(() => {
    svgElement.className.baseVal = `agent ${idleFrames[frameIndex]}`;
    frameIndex = (frameIndex + 1) % idleFrames.length;
}, 150); // ~6.7 FPS for smooth breathing

// Shadow visibility control
svgElement.classList.add('no-shadow');    // Hide shadow
svgElement.classList.remove('no-shadow'); // Show shadow

Shadow Control

Avatars include a ground shadow by default. You can hide it using the no-shadow CSS class or the shadow URL parameter:

// Via CSS class (client-side)
svgElement.classList.add('no-shadow');    // Hide shadow
svgElement.classList.remove('no-shadow'); // Show shadow

// Via URL parameter (server-side)
/avatar/user@example.com.svg              // Shadow visible (default)
/avatar/user@example.com.svg?shadow=false // Shadow hidden
/avatar/user@example.com.svg?frame=happy&shadow=false // Combined params

Performance Benefits

📦 Single File

All 20 states in one SVG (~3.8 KB gzipped) vs 20 separate files (92.3 KB total uncompressed)

⚡ Instant Switching

Zero latency state changes - just CSS class manipulation, no network requests

🎯 Better Caching

Single immutable resource means perfect browser cache hit rate

🚀 95% Fewer Requests

Load 1 file instead of 20 for complete animation support

Available States

  • Idle frames (10): idle_0 through idle_9 - Independent eye/mouth combinations for natural idle animation
  • Emotes (5): happy, sad, surprised, angry, bored - Matching eye/mouth pairs for expressions
  • Vowels (5): vowel_a, vowel_e, vowel_i, vowel_o, vowel_u - Open eyes with vowel mouths for lip-sync

All states use the format agent [state_name] as the CSS class on the root <svg> element.