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:

  1. GET / — project manifest
  2. GET /bases/:base — category list
  3. GET /bases/:base/tree — all traits
  4. GET /bases/:base/variants — variants
  5. GET /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"
}
FieldTypeDescription
namestringProject identifier
basesstring[]Available base types
hasPremadesbooleanHas premade content
defaultImageUrlstring | nullDefault 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

FieldTypeDescription
idstringCategory ID
namestringDisplay name
ordernumberSort order (ascending)
zIndexnumberLayer stacking order
requiredbooleanMust 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 frames array
{
  "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

FieldBehavior
blockedByCannot select if any listed trait is active
requireCan only select if all listed traits are active
multiTraitAlso 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" }
StatusMeaning
200Success
401Unauthorized
404Not found
500Server error

Image Hosting

  1. URLs must be absolute
  2. Accessible cross-origin (CORS)
  3. PNGs with transparent backgrounds

Endpoint Summary

EndpointRequired
GET /Yes
GET /basesYes
GET /bases/:baseYes
GET /bases/:base/treeYes
GET /bases/:base/categories/:catYes
GET .../:cat/:idYes
GET /bases/:base/variantsYes
GET .../variants/:vYes
GET /premadesIf hasPremades