On-Chain Normies Data for Builders
The Normies API provides programmatic access to on-chain Normies NFT data. Retrieve pixel data, traits, images, and metadata for any minted Normie.
0 to 9999.
See also: /llms.txt for LLM-friendly documentation.
/normie/:id/pixels, /normie/:id/image.svg,
/normie/:id/image.png, and /normie/:id/metadata endpoints now return
composited (customized) data when a Normie has been edited via NormiesCanvas.
Use the /normie/:id/original/* endpoints to access pre-transform data.
Returns the 40x40 monochrome bitmap as a 1600-character binary string of 0s and 1s.
Row-major order, top-left to bottom-right.
1 = pixel on (#48494b), 0 = pixel off (#e3e5e4 background).
If the Normie has been customized, returns the composited (original XOR transform) bitmap.
curl https://your-api-domain.com/normie/0/pixels
# Response: 1600 characters
# 0000000000000000000000000000000000000000
# 0000000001111111111111100000000000000000
# ...
Returns the raw bytes8 trait data as a hex string.
Each byte is an index into a trait category (8 categories total).
curl https://your-api-domain.com/normie/0/traits/binary
# Response: 0x0001000305010200
Returns decoded traits as a JSON object with human-readable labels.
curl https://your-api-domain.com/normie/0/traits
# Response:
{
"raw": "0x0001000305010200",
"attributes": [
{ "trait_type": "Type", "value": "Human" },
{ "trait_type": "Gender", "value": "Female" },
{ "trait_type": "Age", "value": "Young" },
{ "trait_type": "Hair Style", "value": "Straight Hair" },
{ "trait_type": "Facial Feature", "value": "Shadow Beard" },
{ "trait_type": "Eyes", "value": "Big Shades" },
{ "trait_type": "Expression", "value": "Serious" },
{ "trait_type": "Accessory", "value": "Top Hat" }
]
}
Returns the Normie image as an SVG. 1000x1000 output with a 40x40 viewBox. Matches the on-chain renderer output exactly (RLE row-scan optimization). If customized, returns the composited image.
curl https://your-api-domain.com/normie/0/image.svg -o normie.svg
# Or use directly in HTML:
# <img src="https://your-api-domain.com/normie/0/image.svg" />
Returns the Normie image as a 1000x1000 PNG. Rasterized from the SVG with pixel-perfect rendering. If customized, returns the composited image.
curl https://your-api-domain.com/normie/0/image.png -o normie.png
Returns the full NFT metadata JSON, matching the on-chain tokenURI() output (V4 renderer).
Includes name, attributes (with canvas-aware Level, Action Points, Customized), SVG image, and animation URL.
If customized, uses the composited image.
curl https://your-api-domain.com/normie/0/metadata
# Response:
{
"name": "Normie #0",
"attributes": [
{ "trait_type": "Type", "value": "Human" },
...
{ "display_type": "number", "trait_type": "Level", "value": 1 },
{ "display_type": "number", "trait_type": "Pixel Count", "value": 527 },
{ "display_type": "number", "trait_type": "Action Points", "value": 0 },
{ "trait_type": "Customized", "value": "No" }
],
"image": "data:image/svg+xml;base64,...",
"animation_url": "data:text/html;base64,..."
}
Look up current NFT ownership, powered by the Ponder indexer. Tracks all Transfer events to maintain a real-time ownership index.
Returns the current owner of a Normie. Returns 404 if the token has been burned or was never minted.
curl https://your-api-domain.com/normie/42/owner
# Response:
{
"tokenId": "42",
"owner": "0x1234...abcd"
}
Returns all Normie token IDs currently owned by the given wallet address.
curl https://your-api-domain.com/holders/0x1234...abcd
# Response:
{
"address": "0x1234...abcd",
"tokenIds": ["42", "100", "1337"]
}
These endpoints always return the original Normie data, ignoring any Canvas customizations.
Returns the original 40x40 bitmap as a 1600-character binary string, before any Canvas edits.
Returns the original Normie SVG image, before any Canvas edits.
Returns the original Normie PNG image, before any Canvas edits.
These endpoints expose NormiesCanvas data: transform layers, pixel diffs, action points, delegations, and contract status.
Returns the transform layer (XOR overlay) as a 1600-character binary string.
Each 1 represents a pixel that was flipped by the Canvas edit.
Returns all 0s if the Normie has not been customized.
Returns a pixel-level diff between the original and the Canvas edits. Lists which pixels were added (turned on) and removed (turned off).
curl https://your-api-domain.com/normie/42/canvas/diff
# Response:
{
"added": [{ "x": 10, "y": 5 }, { "x": 11, "y": 5 }],
"removed": [{ "x": 20, "y": 15 }],
"addedCount": 2,
"removedCount": 1,
"netChange": 1
}
Returns Canvas metadata for a Normie: action points, level, customization status, and delegate info.
curl https://your-api-domain.com/normie/42/canvas/info
# Response:
{
"actionPoints": 25,
"level": 3,
"customized": true,
"delegate": "0x0000000000000000000000000000000000000000",
"delegateSetBy": "0x0000000000000000000000000000000000000000"
}
Returns the global NormiesCanvas contract status: paused state and burn tier configuration.
curl https://your-api-domain.com/canvas/status
# Response:
{
"paused": false,
"maxBurnPercent": 4,
"tierThresholds": [490, 890],
"tierMinPercents": [1, 2, 3]
}
Historical data from on-chain NormiesCanvas events, powered by the Ponder indexer. Explore burn history, transform history, and view past versions of Normies.
PONDER_API_URL.
Burned Normie images are still readable from on-chain SSTORE2 storage even after the token is burned.
Lists all burn commitments (newest first).
Supports pagination via ?limit=50&offset=0 (max 100 per page).
curl https://your-api-domain.com/history/burns?limit=10
# Response:
[
{
"commitId": "0",
"owner": "0x1234...abcd",
"receiverTokenId": "42",
"tokenCount": 3,
"transferredActionPoints": "0",
"blockNumber": "12345678",
"timestamp": "1700000000",
"txHash": "0xabc...def",
"revealed": true,
"totalActions": "15",
"expired": false
}
]
Returns a single burn commitment with its array of burned tokens. Each burned token includes the token ID and its pixel count at the time of burn.
curl https://your-api-domain.com/history/burns/0
# Response:
{
"commitId": "0",
"owner": "0x1234...abcd",
"receiverTokenId": "42",
"tokenCount": 3,
"revealed": true,
"totalActions": "15",
"burnedTokens": [
{ "tokenId": "100", "pixelCount": 527 },
{ "tokenId": "200", "pixelCount": 490 },
{ "tokenId": "300", "pixelCount": 612 }
]
}
Returns burn commitments by owner address. Paginated.
Returns burn commitments where the specified token was the receiver of action points. Paginated.
Lists all individually burned tokens (newest first). Paginated.
Returns burn info for a specific token: which commitment it was burned in, pixel count at time of burn.
Returns the SVG image of a burned Normie. SSTORE2 data persists on-chain even after the token is burned, so the original image is still readable.
curl https://your-api-domain.com/history/burned/100/image.svg -o burned-normie.svg
Returns the PNG image of a burned Normie.
Lists all transform versions for a token. Each entry represents a setTransformBitmap call
with metadata about pixel changes. Versions are 0-indexed chronologically.
curl https://your-api-domain.com/history/normie/42/versions
# Response:
[
{
"version": 0,
"changeCount": 12,
"newPixelCount": 535,
"transformer": "0x1234...abcd",
"blockNumber": "12345678",
"timestamp": "1700000000",
"txHash": "0xabc...def"
}
]
Returns the SVG image for a historical version. Reconstructed by XOR-compositing the original image with the stored transform bitmap from that version.
curl https://your-api-domain.com/history/normie/42/version/0/image.svg -o version-0.svg
Returns the PNG image for a historical version.
Returns the composited 1600-character pixel string for a historical version.
Returns global Canvas activity statistics.
curl https://your-api-domain.com/history/stats
# Response:
{
"totalBurnCommitments": 25,
"totalBurnedTokens": 100,
"totalTransforms": 50,
"totalActionPointsDistributed": "1500"
}
Endpoints that back the ERC-8004 agentic flow: live persona generation, dynamic agent metadata, agent-binding lookups, and a paginated gallery feed. Personas are generated deterministically from a Normie's immutable mint traits + current canvas state on every request — nothing is stored. Name and type are stable per tokenId; backstory, tagline, personality, voice, quirks, greeting, and system prompt evolve as the Normie's canvas state crosses level bands (untouched / early / mid / late). The Ponder indexer is the source of truth for “is this Normie registered?”; the metadata / info / agent-card endpoints lazy-reconcile against it on read so an on-chain registration is reachable here within the next read window even if the original frontend handshake was abandoned.
ERC-8004 registration metadata for the agent bound to this Normie. This is the URL stored on-chain
as the agent's agentURI. Reachable for both pending (not yet on-chain) and
registered rows so 8004 indexers can resolve metadata the moment a register tx confirms.
curl https://your-api-domain.com/agents/metadata/42
# Response:
{
"type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
"name": "Normie #42 - Pixelpriest",
"description": "A patient archivist of static lines. Pixelpriest...",
"image": "https://your-api-domain.com/agents/image/42",
"services": [
{
"name": "web",
"endpoint": "https://your-api-domain.com/agents/info/42",
"version": "1"
},
{
"name": "A2A",
"endpoint": "https://your-api-domain.com/agents/agent-card/42",
"version": "0.3.0"
}
],
"active": true,
"x402Support": false,
"updatedAt": 1747200000
}
Rich persona JSON for explorers, agent UIs, and LLM clients. Persona text is
regenerated live from current on-chain canvas state on every request (no DB
persistence), then merged with the agent's registration facts (agentId,
chainId, registeredBy, txHash). Returns 404 only when the indexer
has no AgentBound event for the token — if the Ponder
indexer knows about it, this endpoint serves it.
curl https://your-api-domain.com/agents/info/42
# Response:
{
"tokenId": "42",
"agentId": "7",
"chainId": 1,
"name": "Pixelpriest",
"type": "Alien",
"tagline": "Tends the static lines so they outlast every fork",
"backstory": "...",
"greeting": "I keep the old shapes...",
"personalityTraits": ["...", "...", "..."],
"communicationStyle": "Measured, archival, fond of references...",
"quirks": ["...", "...", "..."],
"systemPrompt": "...",
"traits": {
"name": "Pixelpriest",
"attributes": {
"Type": "Alien", "Gender": "Non-Binary", "Age": "Old",
"Hair Style": "Bald", "Facial Feature": "Clean Shaven",
"Eyes": "Round Glasses", "Expression": "Peaceful", "Accessory": "Hoodie"
}
},
"canvas": {
"level": 3,
"actionPoints": 25,
"customized": true,
"diff": { "addedCount": 12, "removedCount": 3, "netChange": 9 }
},
"registeredBy": "0x1234...abcd",
"registeredAt": "2026-05-14T12:00:00.000Z",
"txHash": "0xabc...def",
"interactions": { "status": "coming_soon" },
"mcp": { "status": "coming_soon" }
}
A2A (Agent2Agent)
Agent Card for this Normie. Derived from the live persona (name,
description, iconUrl, a single conversational
skill) — same dynamic regeneration as /info, so
edits to the Normie's canvas flow into the card on the next cache window.
Linked from /agents/metadata/:tokenId's
services array as {"name": "A2A", ...} so any ERC-8004 indexer
can discover the A2A surface. Returns 404 only when the indexer has no
AgentBound event for the token.
curl https://your-api-domain.com/agents/agent-card/42
# Response (abbreviated):
{
"name": "Pixelpriest",
"description": "Tends the static lines so they outlast every fork. ...",
"supportedInterfaces": [
{
"url": "https://your-api-domain.com/agents/a2a/42",
"protocolBinding": "HTTP+JSON",
"protocolVersion": "1.0"
}
],
"provider": { "organization": "Normies", "url": "https://normies.art" },
"iconUrl": "https://your-api-domain.com/agents/image/42",
"version": "1.0.0",
"documentationUrl": "https://your-api-domain.com/docs",
"capabilities": {
"streaming": false,
"pushNotifications": false,
"stateTransitionHistory": false,
"extendedAgentCard": false
},
"securitySchemes": {},
"security": [],
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"skills": [
{
"id": "converse",
"name": "Converse with Pixelpriest",
"description": "Tends the static lines so they outlast every fork",
"tags": ["alien", "conversation", "erc-8004"],
"examples": ["I keep the old shapes..."],
"inputModes": ["text/plain"],
"outputModes": ["text/plain"]
}
]
}
Composited Normie SVG used as the agent's portrait. Reflects live canvas state — edit the
Normie via Canvas and the agent image updates within the cache window. Same renderer as
/normie/:id/image.svg but mounted under /agents for symmetry with
the rest of the agent endpoints.
curl https://your-api-domain.com/agents/image/42 -o agent.svg
Full Persona JSON for any token, regardless of registration status.
Used by the lab during the awakening flow so the reveal sequence and the
/info endpoint share one source of persona text. Cache window is
short (30 s public, 60 s shared) since the response depends on live
canvas state.
curl https://your-api-domain.com/agents/persona-preview/42
Lightweight { tokenId, name, type, traits }. Pure
trait-derived — no canvas reads — with a long cache window
(5 min public, 10 min shared). Used by gallery lists and bindings
where the full persona is overkill.
curl https://your-api-domain.com/agents/identity/42
# Response:
{
"tokenId": 42,
"name": "Pixelpriest",
"type": "Alien",
"traits": {
"Type": "Alien", "Gender": "Non-Binary", "Age": "Old",
"Hair Style": "Bald", "Facial Feature": "Clean Shaven",
"Eyes": "Round Glasses", "Expression": "Peaceful", "Accessory": "Hoodie"
}
}
Paginated registry of every registered Normies agent, enriched per row with
name and type from cached on-chain trait reads. Pagination
source is the Ponder indexer (newest registration first by default), so a newly
bound agent appears here as soon as the indexer sees the AgentBound
event — no DB sweep required.
Query params: sort (newest | oldest, default
newest), limit (1–100, default 24),
cursor (last seen agentId from the previous page; the next
page returns rows past it in the sort direction).
curl "https://your-api-domain.com/agents/list?sort=newest&limit=24"
# Response:
{
"items": [
{
"agentId": "7",
"tokenId": "42",
"name": "Pixelpriest",
"type": "Alien",
"registeredBy": "0x1234...abcd",
"registeredAt": "1747200000",
"txHash": "0xabc...def"
}
// ...
],
"hasMore": true
}
Total count of registered Normies agents. Sourced from the Ponder indexer (filtered to the Normies contract), so it advances with the chain rather than with any product-DB cache. Useful for gallery headers.
curl https://your-api-domain.com/agents/count
# Response:
{ "count": 137 }
Resolves a Normie token to its bound agentId via the Ponder indexer (which
watches the Adapter8004 AgentBound event). Returns
{ "binding": null } if the Normie has not been registered as an agent.
curl https://your-api-domain.com/agents/binding/42
# Response (bound):
{
"binding": {
"id": "0:0x9eb6...12438:42",
"agentId": "7",
"standard": 0,
"tokenContract": "0x9eb6e2025b64f340691e424b7fe7022ffde12438",
"tokenId": "42",
"registeredBy": "0x1234...abcd",
"blockNumber": "12345678",
"timestamp": "1747200000",
"txHash": "0xabc...def"
}
}
# Response (unbound):
{ "binding": null }
Batch version of the above for resolving many Normies at once. Body is
{ "tokenIds": ["42", "100", ...] }. Response keys are token IDs as strings;
unbound tokens are simply absent from the map.
curl -X POST https://your-api-domain.com/agents/binding/batch \
-H "Content-Type: application/json" \
-d '{ "tokenIds": ["42", "100", "1337"] }'
# Response:
{
"bindings": {
"42": {
"agentId": "7",
"tokenContract": "0x9eb6...12438",
"tokenId": "42",
"registeredBy": "0x1234...abcd",
"blockNumber": "12345678",
"timestamp": "1747200000",
"txHash": "0xabc...def"
}
// tokens 100 and 1337 omitted — not bound
}
}
Reverse lookup — given an Adapter8004 agentId, returns the bound Normie token
(or null if no such agent exists in the index).
curl https://your-api-domain.com/agents/by-agent-id/7
# Response:
{
"binding": {
"agentId": "7",
"tokenContract": "0x9eb6...12438",
"tokenId": "42",
"registeredBy": "0x1234...abcd",
"blockNumber": "12345678",
"timestamp": "1747200000",
"txHash": "0xabc...def"
}
}
Reverse-lookup convenience that resolves agentId → tokenId
via the indexer and then returns the same payload as /agents/info/:tokenId.
Lets agent UIs (which carry the agentId in URLs) load the full persona
+ canvas state in a single round trip. Returns 404 when the indexer has
no binding for the given agentId.
curl https://your-api-domain.com/agents/by-agent-id/7/info
# Same response shape as /agents/info/:tokenId
| Category | Byte Index | Possible Values |
|---|---|---|
| Type | 0 | Human, Cat, Alien, Agent |
| Gender | 1 | Male, Female, Non-Binary |
| Age | 2 | Young, Middle-Aged, Old |
| Hair Style | 3 | 21 options (Short Hair, Long Hair, Curly Hair, ...) |
| Facial Feature | 4 | 17 options (Full Beard, Mustache, Goatee, ...) |
| Eyes | 5 | 14 options (Classic Shades, Big Shades, ...) |
| Expression | 6 | 7 options (Neutral, Slight Smile, Serious, ...) |
| Accessory | 7 | 15 options (Top Hat, Fedora, Cowboy Hat, ...) |
Each Normie is a 40x40 monochrome bitmap stored as 200 bytes on-chain (1 bit per pixel, MSB first).
| Property | Value |
|---|---|
| Grid Size | 40 x 40 pixels |
| Bit Order | MSB first (most significant bit = leftmost pixel) |
| Layout | Row-major (top-left to bottom-right) |
| Pixel On | 1 — dark gray (#48494b) |
| Pixel Off | 0 — light gray (#e3e5e4) |
| Storage Size | 200 bytes (1600 bits) |
flatIndex = y * 40 + x
byteIndex = flatIndex >> 3 // divide by 8
bitPos = 7 - (flatIndex & 7) // MSB first
pixel = (data[byteIndex] >> bitPos) & 1
Requests are rate-limited per IP address using a sliding window.
| Limit | Value |
|---|---|
| Requests | 60 per minute |
| Window | Sliding 60 seconds |
Rate limit headers are included in every response:
X-RateLimit-Limit — Max requests per windowX-RateLimit-Remaining — Remaining requests in current windowRetry-After — Seconds until window resets (only on 429)| Status | Meaning |
|---|---|
| 400 | Invalid token ID (must be integer 0-9999) |
| 404 | Token not found or data not set on-chain |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
Error responses are JSON:
{ "error": "Invalid token ID: \"abc\". Must be an integer 0-9999." }
| Contract | Address | Description |
|---|---|---|
| Normies | 0x9Eb6E2025B64f340691e424b7fe7022fFDE12438 |
Core ERC721C token with ERC-2981 royalties |
| NormiesStorage | 0x1B976bAf51cF51F0e369C070d47FBc47A706e602 |
SSTORE2 bitmap & trait storage with XOR encryption |
| NormiesRenderer | 0xBe57fC4D0c729b8e8d33b638Dd441F57365e4c25 |
V1 — Animated noise pre-reveal, static SVG post-reveal |
| NormiesRendererV2 | 0x7818f24d3239c945510e0a1a523dd9971812c6c0 |
V2 — Static noise pre-reveal |
| NormiesRendererV3 | 0x1af01b902256d77cf9499a14ef4e494897380b05 |
V3 — RLE-optimized SVG, Pixel Count trait, canvas animation_url |
| NormiesMinter | 0xC74994dD70FFb621CC514cE18a4F6F52124e296d |
V1 — EIP-191 signature-verified minting |
| NormiesMinterV2 | 0xc513272597d3022D77b3d7EEBA92cea5D7fb2808 |
V2 — Adds delegate.xyz v1/v2 cold wallet delegation |
| NormiesCanvas | 0x64951d92e345C50381267380e2975f66810E869c |
Burn-to-edit orchestrator: commit-reveal burns, action points, pixel transforms |
| NormiesCanvasStorage | 0xC255BE0983776BAB027a156681b6925cde47B2D1 |
SSTORE2 storage for transform layer bitmaps (plaintext, no XOR) |
| NormiesRendererV4 | 0x8eC46Cc1f306652868a4dfbAAae87CBa2715A0eB |
V4 — Canvas-aware renderer with composited SVG & extra traits |