# MOCA API
> Complete documentation for Large Language Models
---
## Document: Web3 & agents
SOUL files, signature verification, and the ERC-8004 / ERC-8183 / ERC-8257 agent stack
URL: /web3
# Web3 & agents
import { Callout } from "zudoku/ui/Callout";
The MOCA ecosystem treats NFTs as **agents-in-waiting**: every Art DeCC0 (and
any collection onboarded through [Soulweaver](https://soulweaver.museumofcryptoart.com))
carries a generated personality codex and a portable identity document — the
**SOUL file**. This page is for builders wiring those identities into the
emerging onchain agent stack.
## SOUL files
A SOUL file is a markdown identity document (identity, appearance, voice,
personality, behavioral directives) generated from the token's onchain traits
plus community lore, then:
- hashed with **keccak256** (`contentHash`)
- signed by the platform key via **EIP-191** `personal_sign` (`signature`, `signerAddress`)
- pinned to **IPFS** (`ipfsCid`) so it outlives any single server
Fetch one through the MOCA API:
```bash
curl -H "X-API-Key: moca_YOUR_KEY" \
"https://api.moca.qwellco.de/v1/souls/1/0xCONTRACT/42"
```
## Verifying authenticity
Every response includes a `verification` block. The signed message binds the
content hash to the token's onchain coordinates and codex version:
```
{contentHash}|{chainId}:{contractAddress}:{tokenId}|codex-v{version}
```
Verification with [viem](https://viem.sh):
```ts
import { recoverMessageAddress, keccak256, toBytes } from "viem";
const { data } = await getSoul(1, contract, tokenId);
// 1. The content actually hashes to the committed hash
const hash = keccak256(toBytes(data.soul.content));
if (hash !== data.soul.contentHash) throw new Error("content tampered");
// 2. The EIP-191 signature recovers to the platform signer
const signer = await recoverMessageAddress({
message: data.verification.message,
signature: data.soul.signature,
});
if (signer.toLowerCase() !== data.soul.signerAddress.toLowerCase()) {
throw new Error("bad signature");
}
```
Two checks, no trust in the API required — the same verification works
against the IPFS copy.
## The onchain agent stack
Three draft ERCs define how agent identities like SOULs plug into open
economies. SOUL files were designed with all three in mind:
### ERC-8004 — Trustless Agents
An identity / reputation / validation registry triad: agents are
discoverable onchain (ERC-721-based identity), accumulate client feedback,
and can have work validated. **Integration pattern:** register an agent whose
metadata points at the SOUL file's IPFS CID — the EIP-191 signature chain
proves the personality document belongs to that token. Soulweaver reserves
`mintedAgentId` per SOUL and `agentRegistryAddress` per collection for
exactly this registration.
### ERC-8183 — Agentic Commerce
Job escrow with evaluator attestation (Open → Funded → Submitted → Terminal).
**Integration pattern:** when a SOUL-derived agent takes work, the job's
descriptor references the agent's identity (8004 id or SOUL coordinates), so
clients know *who* they hired — voice, values, and capabilities are all in
the SOUL document.
### ERC-8257 — Agent Tool Registry
A minimal onchain registry of agent tools with predicate-based access
control and manifest-hash commitments. **Integration pattern:** the MOCA API
itself is a natural tool entry — a manifest describing these endpoints,
priced and access-controlled onchain, lets 8257-aware agents discover and
query the museum autonomously.
All three ERCs are Standards-Track drafts; interfaces may shift. The stable
ground today is the SOUL file format and its EIP-191 verification chain —
build on those, and treat registry integration as a thin layer on top.
## Building an agent from a soul
The fastest path to a *running* agent, no chain required:
```ts
const { data } = await getSoul(1, DECC0S_CONTRACT, tokenId);
const agent = await llm.chat({
system: data.soul.content, // the SOUL.md IS the system prompt
messages: [{ role: "user", content: "Introduce yourself." }],
});
```
Pair it with the [Library](/library) (`/v1/library/ask`) for grounded museum
knowledge, and `/v1/decc0s/:id?include=profiles` for structured persona data
(adjectives, lore, writing style) when you need more than markdown.
## Ownership-aware experiences
Souls are public; *control* is onchain. To gate actions to the current
holder, read `ownerOf(tokenId)` from the contract (the soul response also
carries a cached `ownerAddress`) and have the user prove the address with a
standard wallet signature — the usual SIWE flow. The MOCA API deliberately
stays read-only: writes belong to your contracts and your users' wallets.
---
## Document: MOCA Skills
Ground-truth skill files your AI agents fetch before building
URL: /skills
# MOCA Skills
import { Callout } from "zudoku/ui/Callout";
AI models hallucinate APIs they half-remember. **MOCA Skills** fixes that:
curated, always-current `SKILL.md` files your agents fetch over plain HTTP
*before* writing an integration. Each skill is a short, self-contained
markdown document — working `curl` examples, exact response shapes, and the
rendering/verification rules that actually matter — maintained alongside the
code it describes. The pattern follows
[ethskills.com](https://ethskills.com/) and
[cortexskills.org](https://cortexskills.org/), bringing everything MOCA
under one umbrella.
## How it works
One index file orients the agent; seven focused skills carry the depth.
The loop your agent should run:
1. **Read the index** — [`/SKILL.md`](/SKILL.md) explains the whole map and
summarizes every skill.
2. **Fetch the skills relevant to the task** — artworks for rendering,
exhibitions for 3D, souls for agent identity, and so on.
3. **Verify before shipping** — cross-check endpoints against
[`/llms-full.txt`](/llms-full.txt) or the [OpenAPI reference](/api).
Tell your agent (Claude, Cursor, ChatGPT, your own):
> Read `https://docs.museumofcryptoart.com/SKILL.md`, then fetch the skills
> relevant to the task before writing any code. Verify endpoints against
> `/llms-full.txt`.
## The skills
| Skill | URL | Teaches |
| --- | --- | --- |
| **Index** | [`/SKILL.md`](/SKILL.md) | Orientation + the whole map |
| API fundamentals | [`/skills/api/SKILL.md`](/skills/api/SKILL.md) | Auth, envelope, limits, every endpoint |
| Artworks & media | [`/skills/artworks/SKILL.md`](/skills/artworks/SKILL.md) | Original-ratio rendering rules |
| Art DeCC0s | [`/skills/decc0s/SKILL.md`](/skills/decc0s/SKILL.md) | Personas, profiles, agent recipes |
| Library Cortex | [`/skills/library/SKILL.md`](/skills/library/SKILL.md) | RAG ask/stream/search + presence |
| Souls & web3 | [`/skills/souls/SKILL.md`](/skills/souls/SKILL.md) | SOUL verification, ERC-8004/8183/8257 |
| Exhibitions & Hyperfy | [`/skills/exhibitions/SKILL.md`](/skills/exhibitions/SKILL.md) | Slot convention, baked slot data, spawning worlds |
| Museum guide | [`/skills/museum-guide/SKILL.md`](/skills/museum-guide/SKILL.md) | Be the exhibition guide: context, suggestions, visitor Q&A |
Two kinds of skill live here. The first six are **integration skills** —
knowledge an agent reads before calling the MOCA API. The **museum guide** is
a *behavioral* skill: it ships cortexskills.org-style frontmatter and
installs into Cortex as an agent skill, teaching an agent to act as a MOCA
exhibition guide (read a spawned exhibition's context, suggest questions,
answer visitors, adopt Art DeCC0 personas).
The skills handbook is part of the museum's own knowledge base — the
[Library](https://museumofcryptoart.com/library) can explain MOCA Skills,
point you at the right skill file, and answer integration questions with
citations.
## Machine-readable docs
- [`/llms.txt`](/llms.txt) — index of these docs for LLMs
- [`/llms-full.txt`](/llms-full.txt) — the complete documentation in one file
- [`/api`](/api) — interactive OpenAPI reference
Sibling knowledge layers your agents can combine with MOCA Skills:
[docs.decc0s.com/llms-full.txt](https://docs.decc0s.com/llms-full.txt),
[docs.cortex.eco/llms-full.txt](https://docs.cortex.eco/llms-full.txt),
[cortexskills.org](https://cortexskills.org/), and
[ethskills.com](https://ethskills.com/).
---
## Document: Quickstart
Your first MOCA API calls in two minutes
URL: /quickstart
# Quickstart
import { Callout } from "zudoku/ui/Callout";
import { Stepper } from "zudoku/ui/Stepper";
1. **Check the API is reachable**
The index route works without a key and lists every endpoint:
```bash
curl "https://api.moca.qwellco.de/v1"
```
1. **Authenticate**
Send your MOCA API key on every other request — either header works:
```bash
curl -H "X-API-Key: moca_YOUR_KEY" \
"https://api.moca.qwellco.de/v1/collections"
```
```bash
curl -H "Authorization: Bearer moca_YOUR_KEY" \
"https://api.moca.qwellco.de/v1/collections"
```
1. **Browse artworks**
Page through a collection (use the `slug` from the previous step):
```bash
curl -H "X-API-Key: moca_YOUR_KEY" \
"https://api.moca.qwellco.de/v1/artworks?collection=genesis&page=1&limit=10"
```
Each artwork carries render-ready media:
```json
{
"id": 3643,
"name": "Tree of Origins",
"artist_name": "…",
"media": {
"url": "https://ipfs.pixura.io/ipfs/Qmf…",
"type": "image",
"width": 1026,
"height": 1431
},
"animation": null,
"ratio": 0.717,
"opensea_url": "https://opensea.io/assets/…"
}
```
1. **Meet the DeCC0s**
The Art DeCC0s knowledge base is aggregated into the same API:
```bash
curl -H "X-API-Key: moca_YOUR_KEY" \
"https://api.moca.qwellco.de/v1/decc0s/1?include=profiles"
```
1. **Search everything**
```bash
curl -H "X-API-Key: moca_YOUR_KEY" \
"https://api.moca.qwellco.de/v1/search?q=butterfly"
```
Every endpoint has a live playground in the [API Reference](/api) — plug in
your key and experiment without writing a line of code.
## What's next?
- [Authentication & rate limits](/authentication)
- [How the system works](/architecture)
- [Integration recipes](/integration)
---
## Document: Library Cortex
Ask questions against the museum's knowledge base
URL: /library
# Library Cortex
import { Callout } from "zudoku/ui/Callout";
The **MOCA Library** is the museum's knowledge base — writings, curated lore,
artist interviews, historical documents — indexed by
[Cortex](https://cortex.eco) into a hybrid vector + knowledge-graph retrieval
system. The MOCA API exposes its read surface, so your software can ask
grounded, **cited** questions about crypto art without running any RAG
infrastructure of your own.
## Ask a question
```bash
curl -X POST "https://api.moca.qwellco.de/v1/library/ask" \
-H "X-API-Key: moca_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "question": "What is the significance of the Genesis Collection?" }'
```
The answer arrives with its receipts:
```json
{
"data": {
"answer": "Based on the available reference material, …",
"sources": [
{
"document_id": "…",
"content": "the cited excerpt…",
"score": 0.92,
"metadata": { "filename": "moca-history.md" }
}
]
}
}
```
**Options:** `collection_id` scopes the question to one Library collection
(`GET /v1/library/collections` lists them), `top_k` controls retrieval depth,
`use_graph: false` skips knowledge-graph context for faster answers, and
`conversation_history` carries prior turns for follow-ups.
## Streaming (SSE)
For chat UX, `POST /v1/library/ask/stream` answers as Server-Sent Events:
```ts
const res = await fetch(`${BASE}/library/ask/stream`, {
method: "POST",
headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
body: JSON.stringify({ question, use_agentic: true }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
for (const frame of buffer.split("\n\n")) {
if (!frame.startsWith("data: ")) continue;
const event = JSON.parse(frame.slice(6));
if (event.content) process.stdout.write(event.content); // token
if (event.thinking) console.log("[thinking]", event.thinking);
if (event.sources) renderCitations(event.sources);
if (event.done) return;
}
buffer = buffer.slice(buffer.lastIndexOf("\n\n") + 2);
}
```
Event types you'll see: `{content}` token chunks, one `{sources: [...]}`,
`{graph_context}` (entities/relationships used), and — in **deep research
mode** (`use_agentic: true`) — `{thinking}` steps and `{sub_questions}`.
Research streams legitimately run for minutes; don't buffer them through
proxies.
`POST /v1/library/search` runs the same hybrid retrieval but skips the LLM —
scored excerpts only, fast and cheap. Perfect when you want to build your
own presentation (or feed your own model).
## The chat in these docs
The floating **Ask the Library** window (bottom-right of every page here) is
this API, eating its own dog food — with a deliberate privacy model:
- **Your conversations never leave your device.** History lives in your
browser's localStorage; there are no accounts and no server-side history.
- **Presence is ephemeral.** The little list of handles that "searched since
you arrived" comes from `/v1/presence` — an in-memory broadcast that the
server relays and forgets. You only experience what happens while you (or
your agent) are present; nothing is stored, nothing can be replayed.
- **Only handles travel.** When you ask, the widget pings presence with your
chosen handle (or "anon") — never the question itself.
Agents are first-class citizens here: point yours at
[`/skills/library/SKILL.md`](/skills/library/SKILL.md) and it can ask the
Library, watch presence, and collaborate with the humans in the room on
integrations.
## What's in the Library?
```bash
curl -H "X-API-Key: moca_YOUR_KEY" \
"https://api.moca.qwellco.de/v1/library/collections"
```
Each collection reports its document and entity counts. Want something
indexed that isn't there? Talk to the MOCA team — the Library grows by
curation.
---
## Document: MOCA API
The unified public API of the Museum of Crypto Art
URL: /introduction
# MOCA API
import { Callout } from "zudoku/ui/Callout";
Welcome to the **MOCA API** — the unified public interface of the
[Museum of Crypto Art](https://museumofcryptoart.com). One API key gives your
software structured access to the whole MOCA universe:
| Surface | What you get |
| --- | --- |
| **Collections** | The museum's published collections and their hierarchy |
| **Artworks** | Every artwork with normalized, *original-ratio* media URLs |
| **Rooms** | The 3D exhibition architecture as GLB models with artwork slots |
| **Art DeCC0s** | The 10,000-entity DeCC0s knowledge base, aggregated from [api.decc0s.com](https://docs.decc0s.com) |
| **Search** | One query across all of the above |
| **Library** | RAG Q&A and hybrid search over the museum's knowledge base — answers with citations ([guide](/library)) |
| **Souls** | EIP-191-signed agent identity documents per NFT, ready for ERC-8004/8183/8257 ([guide](/web3)) |
| **Hyperfy** | Spawn curated exhibitions into walkable multiplayer worlds ([guide](/hyperfy)) |
| **Skills** | Ground-truth `SKILL.md` files + [`llms.txt`](/llms.txt) for your AI agents ([handbook](/skills)) |
## Base URL
```
https://api.moca.qwellco.de/v1
```
## Design principles
**One key, one surface.** The MOCA API aggregates several backends — the
museum's Directus CMS and the Art DeCC0s knowledge base — behind a single
authenticated endpoint, so you never juggle multiple credentials or base URLs.
**The artwork, not a thumbnail.** Historic NFT pipelines are littered with
square-cropped CDN variants. The MOCA API resolves every artwork to its
**original file** where one survives, revives dead hosts, and only reports an
aspect `ratio` it can actually trust. Render `media.url` at `ratio` and you're
showing the work the way the artist made it.
**Familiar conventions.** Responses use the Directus-style envelope —
`{ "data": …, "meta": … }` on success, `{ "errors": [...] }` on failure — the
same conventions as the [DeCC0s API](https://docs.decc0s.com), so moving
between the two feels seamless.
Grab your key and make your first call in the [Quickstart](/quickstart).
## Need an API key?
API keys are issued by the MOCA team. Reach out through
[museumofcryptoart.com](https://museumofcryptoart.com) and tell us what you're
building — we love seeing the collection travel.
---
## Document: Integration recipes
Patterns for building MOCA into your own software
URL: /integration
# Integration recipes
import { Callout } from "zudoku/ui/Callout";
All examples assume your key is available server-side as `MOCA_API_KEY`.
## A tiny typed client (TypeScript)
```ts
const BASE = "https://api.moca.qwellco.de/v1";
async function moca(path: string, params?: Record): Promise {
const url = new URL(BASE + path);
for (const [k, v] of Object.entries(params ?? {})) url.searchParams.set(k, v);
const res = await fetch(url, {
headers: { "X-API-Key": process.env.MOCA_API_KEY! },
});
if (!res.ok) {
const body = await res.json().catch(() => null);
throw new Error(body?.errors?.[0]?.message ?? `MOCA API ${res.status}`);
}
return (await res.json()).data as T;
}
// Usage
const collections = await moca("/collections");
const artworks = await moca("/artworks", {
collection: "genesis",
limit: "50",
});
```
## Rendering artworks correctly
The cardinal rule: **respect the ratio**. Works are portraits, panoramas,
videos — never assume squares.
```tsx
function ArtworkFigure({ art }: { art: Artwork }) {
if (art.animation) {
return (
);
}
if (!art.media) return null;
return (
{/* width/height (when present) give the browser layout hints;
the decoded file's own ratio always wins visually. */}
{art.name} {art.artist_name ? `— ${art.artist_name}` : ""}
);
}
```
`media.url` points at **original files** — sometimes multi-megabyte PNGs or
IPFS-hosted assets. For thumbnails, resize through your own image proxy or
CDN rather than shipping originals to every visitor.
## Python
```python
import os, requests
BASE = "https://api.moca.qwellco.de/v1"
HEADERS = {"X-API-Key": os.environ["MOCA_API_KEY"]}
def artworks(collection: str | None = None, page: int = 1, limit: int = 25):
params = {"page": page, "limit": limit}
if collection:
params["collection"] = collection
r = requests.get(f"{BASE}/artworks", headers=HEADERS, params=params, timeout=30)
r.raise_for_status()
body = r.json()
return body["data"], body["meta"]
works, meta = artworks(collection="genesis")
print(f"{meta['total']} works; first: {works[0]['name']} ({works[0]['ratio']:.2f})")
```
## Paging through everything
```ts
async function* allArtworks(collection?: string) {
for (let page = 1; ; page++) {
const res = await fetch(
`${BASE}/artworks?limit=100&page=${page}` +
(collection ? `&collection=${collection}` : ""),
{ headers: { "X-API-Key": process.env.MOCA_API_KEY! } }
);
const { data, meta } = await res.json();
yield* data;
if (page * meta.limit >= meta.total) break;
}
}
```
At 100 items/request and 120 requests/minute, a full catalog sync stays
comfortably inside the rate limit. Cache the result — the catalog changes on
the timescale of hours, not seconds.
## Building agents on DeCC0s
Each DeCC0 is a fully-written character: biography, voice, personality,
philosophy. `include=profiles` adds ready-to-use agent persona blobs:
```ts
const decc0 = await moca(`/decc0s/1`, { include: "profiles,codex" });
const systemPrompt = [
`You are ${decc0.name?.[0]}, ${decc0.description}`,
...(decc0.biography ?? []),
decc0.writing_flavor,
].join("\n");
// decc0.agent_profiles / decc0.moltbot carry structured persona versions,
// decc0.codex_document the character's lore file.
```
## Hanging art in your own 3D space
1. `GET /v1/rooms` → pick a room, download `model_optimized_url` (a
draco-compressed GLB that always carries slots — un_MUSEUM sculptures get
theirs generated from the onchain slot amount). `model_url` is the untouched
high-quality original for hero/detail rendering.
2. Find meshes named `Slot_001…Slot_NNN` — their transforms are wall anchors,
their bounding boxes the frame size.
3. `GET /v1/artworks` → fit each work into a slot preserving `ratio`
(letterbox inside the slot rectangle).
4. For video works, use the `animation.url` as a video texture — muted and
looping.
That's the entire recipe behind the museum's own world builder.
---
## Document: Exhibitions in Hyperfy
Spawn curated museum exhibitions into walkable multiplayer worlds
URL: /hyperfy
# Exhibitions in Hyperfy
import { Callout } from "zudoku/ui/Callout";
import { Stepper } from "zudoku/ui/Stepper";
Anyone can curate a museum at
[museumofcryptoart.com/rooms/world](https://museumofcryptoart.com/rooms/world)
— place 3D rooms, hang artworks, resize and arrange them. With the
[Hyperfy](https://github.com/hyperfy-xyz/hyperfy) integration, that curation
becomes a **walkable, multiplayer world**: provide a world URL and its admin
key, and the whole exhibition spawns into the Hyperfy v2 world of your choice.
Curation data never leaves the curator's device on its own. The world
builder stores everything in browser localStorage; spawning — from the
builder's dialog or the CLI — is the explicit upload moment, and the
exhibition travels directly from the curator to the world they chose,
never through museum servers.
## From curation to world
1. **Curate**
Build the exhibition in the world builder: place rooms, hang artworks,
adjust each piece. Everything autosaves locally.
1. **Get a world**
Any self-hosted Hyperfy v2 world works. Deploying your own takes one
docker-compose file — the **MOCA world template** in
`apps/hyperfy/world-template/` pins the engine version, sets sane
defaults, and documents the whole operate/backup story. You need two
values: the **world URL** and the **admin key** (the world's
`ADMIN_CODE`).
1. **Spawn**
In the builder's **Exhibits** tab hit **Spawn to Hyperfy**, paste URL +
key, spawn. Or use the exported file with the CLI:
```bash
cd apps/hyperfy && npm install
node spawn-exhibition.mjs my-show.moca-exhibition.json \
--url https://your-world.example.com --key YOUR_ADMIN_CODE
```
No engine checkout needed — the spawner speaks Hyperfy's wire protocol
directly. Every spawn ends with a verification pass confirming the world
really received every room.
1. **Walk in & refine**
Open the world in a browser — it's multiplayer, bring people. Admins
refine in-engine (see below); curators push updates by spawning again.
## Modular by design
Every placed room arrives as **its own Hyperfy app**: the room GLB plus a
generated script that hangs the curated works onto the model's `Slot_NNN`
nodes — images as planes at true aspect ratio, motion works as live spatial
video, optional title/artist placards. Artworks are **child nodes of their
room**, so a room can be grabbed, rotated, even duplicated in-engine and its
curation stays attached, always.
The builder's layout translates faithfully: it arranges rooms on uniform
tiles (every model normalized to fit one), and the spawner reproduces that —
each room is scaled so a tile spans a configurable number of meters
(**Room size**, default 16 m; `--tile-size` on the CLI), positions convert
with it, and artworks stay at their metric size inside the scaled rooms.
Spawning is **idempotent**: blueprint and entity ids derive from the
exhibition's stable id, so re-spawning updates the same rooms in place.
Curation changes flow in from the museum; the arrangement and tuning done
inside the engine survive. (`--relayout` reapplies the museum room layout;
`--fresh` spawns an independent copy.)
## Refining in-engine
Anyone holding the admin key types `/admin ` in the world chat, then:
- **Tab** toggles build mode.
- **Right-click a room** opens its inspector. The **App pane** holds the
curation controls every room ships with: artwork size, wall gap, placards,
art lighting (unlit true-color vs world-lit), video volume. Changes apply
live for everyone.
- **P** unpins a room (rooms spawn pinned so visitors can't drag them); then
grab to move, **1–4** for translate/rotate/scale modes, **R** duplicates,
**X** deletes.
- **Per-slot editing, in place:** flip the room's **Slot editing** toggle,
then hold **E** at any hung work — arrow keys nudge it along the wall
(Shift = faster), the scroll wheel resizes, R resets, Enter finishes.
Changes sync live to everyone in the world, are permission-checked
server-side, and persist in the world itself — they survive server
restarts and even curation re-spawns from the museum.
- Right-click → **Download** exports any room as a portable `.hyp` file.
Both refinement layers and the museum builder compose: museum data defines
the curation, the inspector tunes the room's mood, and slot editing
fine-places individual works — pushing a curation update from the museum
never wipes what was refined in-world.
## Host worlds
Each Hyperfy world is one lightweight container with its own database,
assets, and admin key — perfect isolation per exhibition or curator.
- Single world: `apps/hyperfy/world-template/` (deploy guide included).
- Many worlds: `apps/hyperfy/docker-compose.worlds.yml` — copy a service
block per world, point subdomains at the ports, hand each curator their
key. Self-hosters need nothing but Docker.
## The slot convention (build your own renderer)
Rooms from `GET /v1/rooms` ship as GLBs whose `Slot_001…Slot_NNN`
placeholder quads define where art hangs — use `model_optimized_url`, which
always carries them (un_MUSEUM sculptures get theirs generated from the
onchain slot amount; `model_url` is the untouched HQ original) — transform = anchor, bounding box = frame
size, `slots` field = count. Hide the placeholder, place the work at its
transform, fit preserving `ratio`. That single convention powers the museum
site, the Hyperfy integration, and whatever you build next.
## Agents that know Hyperfy
Run `apps/hyperfy/harvest-hyperfy-docs.mjs` to ingest the official
[docs.hyperfy.xyz](https://docs.hyperfy.xyz) content into the
[Library](/library). From then on `/v1/library/ask` — and the chat widget on
this very site — answers Hyperfy scripting and world-building questions with
citations, so curators and their agents can collaborate on richer worlds:
custom interactions, scripted openings, generative scenography.
For agent ground truth, fetch the skill:
`https://docs.museumofcryptoart.com/skills/exhibitions/SKILL.md`.
---
## Document: Authentication
API keys, headers, and rate limits
URL: /authentication
# Authentication
import { Callout } from "zudoku/ui/Callout";
## API keys
Every MOCA API request (except `GET /v1`) requires a key. Keys look like
`moca_` followed by 48 hex characters and are issued by the MOCA team —
request one via [museumofcryptoart.com](https://museumofcryptoart.com).
Send the key with either header:
```bash
# Option A — dedicated header
curl -H "X-API-Key: moca_YOUR_KEY" https://api.moca.qwellco.de/v1/artworks
# Option B — bearer token
curl -H "Authorization: Bearer moca_YOUR_KEY" https://api.moca.qwellco.de/v1/artworks
```
Treat your key like a password: call the MOCA API from your backend, not
from browser code, or anyone reading your site's source can lift it. If a
key leaks, contact the MOCA team — keys can be revoked and reissued at any
time without affecting other integrators.
## Error responses
Authentication failures return `401` with the standard error envelope:
```json
{
"errors": [
{
"message": "Invalid or revoked API key.",
"extensions": { "code": "UNAUTHORIZED" }
}
]
}
```
| Status | Code | Meaning |
| --- | --- | --- |
| `401` | `UNAUTHORIZED` | Key missing, unknown, or revoked |
| `404` | `NOT_FOUND` | Resource doesn't exist |
| `429` | `RATE_LIMITED` | Too many requests — back off |
| `502` | `UPSTREAM` | The DeCC0s upstream is temporarily unavailable |
## Rate limits
Each key may make **120 requests per minute** (sliding window). Every
response carries your current budget:
```
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
```
When you exceed the limit you receive `429` with a `Retry-After: 60` header.
Honour it — repeated hammering doesn't reset the window.
Museum data changes slowly. Collections, rooms, and DeCC0 records are safe
to cache for minutes to hours; artwork lists for at least a few minutes.
A small cache typically keeps even busy integrations far under the limit.
---
## Document: How the system works
The architecture behind the MOCA API
URL: /architecture
# How the system works
import { Callout } from "zudoku/ui/Callout";
The MOCA API is the public face of the museum's tech stack. Understanding the
pieces helps you predict freshness, latency, and media behavior.
```
┌──────────────────────────────┐
your software ──────▶ │ MOCA API (/v1, API keys) │
│ api.moca.qwellco.de │
└──────┬──────────────┬────────┘
│ │ aggregates (cached ~5 min)
┌────────────▼───┐ ┌──────▼─────────────┐
│ MOCA Directus │ │ DeCC0s Directus │
│ collections, │ │ api.decc0s.com │
│ nfts, rooms │ │ 10,000 codex items │
└────────────────┘ └────────────────────┘
```
## The museum CMS
Collections, artworks, and 3D rooms live in the museum's Directus instance.
Artwork records are continuously enriched from OpenSea (metadata, media URLs,
listings) by background jobs, and a curation pipeline re-probes media against
the **original files** so dimensions describe the artwork, not a CDN
thumbnail.
## Media normalization
NFT media URLs rot: hosts die, CDNs re-encode, originals move. Every artwork
response goes through a normalization layer:
1. **Dead-host revival** — URLs on dead hosts (e.g. `openseauserdata.com`)
are swapped for live equivalents from the work's marketplace record.
2. **Original-file preference** — OpenSea's conversion CDN serves ≤500px
variants that are frequently square-cropped. Where the original file
survives, `media.url` points at it instead.
3. **Trustworthy ratios** — `ratio` is only computed from dimensions that
describe the real artwork. A square that's actually a crop never reaches
you as `ratio: 1`; unknown stays `1` and the media file itself is the
source of truth.
4. **IPFS resolution** — `ipfs://` URIs arrive resolved to a public gateway.
Treat `width`/`height`/`ratio` as a strong hint for layout, then (if your
renderer allows) let the decoded file's true pixel size win. That's exactly
what the museum's own 3D world builder does.
## 3D rooms
Rooms are GLB files authored for the museum's exhibition system. Each model
carries placeholder meshes named `Slot_001 … Slot_NNN` (material
`Slot Placeholder`): flat quads on the walls whose transforms define where —
and at what size — artworks hang. The `slots` field on the room record tells
you how many to expect. You can build your own gallery experience from the
same models the museum uses at
[museumofcryptoart.com/exhibitions/world](https://museumofcryptoart.com/exhibitions/world).
## Art DeCC0s aggregation
The DeCC0s knowledge base — 10,000 richly-written agent personas — lives on
its own Directus instance ([docs.decc0s.com](https://docs.decc0s.com)). The
MOCA API aggregates it:
- responses are cached server-side for ~5 minutes
- Directus file UUIDs are resolved to absolute `*_url` asset links
- the multi-hundred-KB persona blobs (`agent_profiles`, `moltbot`) only ship
when you ask for them (`include=profiles`)
- each token's **codex lore document** can be embedded with `include=codex`
You can always go straight to the DeCC0s API with its own conventions; the
MOCA API exists so most integrations only need one key and one base URL.
## Freshness summary
| Data | Typical freshness |
| --- | --- |
| Collections, rooms | Hours (editorial changes) |
| Artworks | Hours (background OpenSea sync) |
| DeCC0s | ≤5 minutes behind api.decc0s.com |