Avatar Editor API
API contract for the PFP Editor
The editor uses a single Base URL you provide (e.g. https://api.example.com/api). All endpoints below are relative to that URL.
Quick Start
The editor fetches data in this order:
GET /— project manifestGET /bases/:base— category listGET /bases/:base/tree— all traitsGET /bases/:base/variants— variantsGET /premades— premades (if enabled)
All responses: JSON, Content-Type: application/json
CORS
Your API must allow cross-origin requests:
Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, OPTIONS Access-Control-Allow-Headers: Content-Type
GET / — Project Manifest
{
"name": "myproject",
"bases": ["human", "robot"],
"hasPremades": true,
"defaultImageUrl": "https://cdn.example.com/default-avatar.png"
}| Field | Type | Description |
|---|---|---|
name | string | Project identifier |
bases | string[] | Available base types |
hasPremades | boolean | Has premade content |
defaultImageUrl | string | null | Default avatar image URL for users who haven't built a PFP |
GET /bases — List Bases
["human", "robot"]
GET /bases/:base — Base Detail
Returns categories with rendering metadata.
{
"id": "human",
"name": "Human",
"categories": [
{ "id": "skin",
"name": "Skin Tone",
"order": 0,
"zIndex": 0,
"required": true },
{ "id": "eyes",
"name": "Eyes",
"order": 1,
"zIndex": 1,
"required": true }
]
}Category Fields
| Field | Type | Description |
|---|---|---|
id | string | Category ID |
name | string | Display name |
order | number | Sort order (ascending) |
zIndex | number | Layer stacking order |
required | boolean | Must select a trait |
GET /bases/:base/categories/:cat
Returns all traits for a category.
{
"category": {
"id": "hat",
"name": "Hat",
"order": 3,
"zIndex": 6,
"required": false
},
"base": "human",
"project": "myproject",
"items": [
{
"id": "hat-beanie",
"name": "Beanie",
"isAnimated": false,
"imageUrl": "https://...beanie.png",
"blockedBy": [],
"require": [],
"multiTrait": [],
"meta": { "rarity": "common" }
}
]
}GET /bases/:base/categories/:cat/:id
Single trait. Same shape as one items entry above.
GET /bases/:base/tree — Full Tree
All categories and traits in one call.
{
"base": "human",
"project": "myproject",
"categories": [
{
"category": {
"id": "eyes",
"name": "Eyes",
"order": 1,
"zIndex": 1,
"required": true
},
"items": [
{
"id": "eyes-blue",
"name": "Blue",
"isAnimated": false,
"imageUrl": "https://...blue.png",
"blockedBy": [],
"require": [],
"multiTrait": []
}
]
}
]
}GET /bases/:base/variants
Variants are alternate body styles. Each has dynamic sub-categories.
[
{
"id": "variant-ice",
"name": "Ice",
"subCategories": [
{
"id": "pattern",
"name": "Pattern",
"order": 0,
"zIndex": 0,
"items": [
{
"id": "pattern-snowflake",
"name": "Snowflake",
"isAnimated": false,
"imageUrl": "https://...snow.png",
"blockedBy": [],
"require": [],
"multiTrait": []
}
]
},
{
"id": "arms",
"name": "Arms",
"order": 1,
"zIndex": 1,
"items": [...]
}
]
}
]Sub-categories are returned pre-sorted by order (ascending). The zIndex controls stacking order within the variant layer.
GET /bases/:base/variants/:variant
Single variant by name. Same shape as one array entry above.
GET /premades
Pre-built avatars. Only if hasPremades: true.
[
{
"id": "premade-frostking",
"name": "Frost King",
"imageUrl": "https://...frostking.png",
"meta": { "edition": "genesis" }
}
]Data Types
TraitItem
interface TraitItem {
id: string;
name: string;
isAnimated: boolean;
imageUrl: string | null;
frames?: Frame[];
blockedBy: string[];
require: string[];
multiTrait: string[];
meta?: Record<string, unknown>;
}Frame
interface Frame {
index: number;
imageUrl: string;
}CategoryMeta
interface CategoryMeta {
id: string;
name: string;
order: number;
zIndex: number;
required: boolean;
}VariantDetail
interface VariantDetail {
id: string;
name: string;
subCategories: {
id: string;
name: string;
order: number;
zIndex: number;
items: TraitItem[];
}[];
}PremadeItem
interface PremadeItem {
id: string;
name: string;
imageUrl: string;
meta?: Record<string, unknown>;
}ProjectManifest
interface ProjectManifest {
name: string;
bases: string[];
hasPremades: boolean;
defaultImageUrl: string | null;
}Animated Traits
- Set
isAnimated: true - Set
imageUrl: null - Provide
framesarray
{
"id": "eyes-sparkle",
"name": "Sparkle",
"isAnimated": true,
"imageUrl": null,
"frames": [
{ "index": 1, "imageUrl": "...1.png" },
{ "index": 2, "imageUrl": "...2.png" },
{ "index": 3, "imageUrl": "...3.png" }
],
"blockedBy": [],
"require": [],
"multiTrait": []
}Trait Dependencies
| Field | Behavior |
|---|---|
blockedBy | Cannot select if any listed trait is active |
require | Can only select if all listed traits are active |
multiTrait | Also selects all listed traits as a bundle |
All fields use trait IDs.
The meta Field
Optional freeform object. The editor preserves it but doesn't act on it.
{
"meta": {
"rarity": "legendary",
"chance": 5,
"artist": "studio_name"
}
}Errors
{ "error": "Not found" }| Status | Meaning |
|---|---|
| 200 | Success |
| 401 | Unauthorized |
| 404 | Not found |
| 500 | Server error |
Image Hosting
- URLs must be absolute
- Accessible cross-origin (CORS)
- PNGs with transparent backgrounds
Endpoint Summary
| Endpoint | Required |
|---|---|
GET / | Yes |
GET /bases | Yes |
GET /bases/:base | Yes |
GET /bases/:base/tree | Yes |
GET /bases/:base/categories/:cat | Yes |
GET .../:cat/:id | Yes |
GET /bases/:base/variants | Yes |
GET .../variants/:v | Yes |
GET /premades | If hasPremades |