the cat experiment

Reference · v1

Read every square's live state as JSON.

The whole board is public. No key, no account, no rate card — just GET it. Build a bot that watches for cheap listings, a screensaver that renders the mosaic, a bridge that mirrors the ledger somewhere else. None of it needs our permission.

Conventions

base
All endpoints live under /api/v1. Responses are JSON; errors are { "error": string } with a real status code.
money
Every amount is wei, as a decimal string. Never a JSON number — a price can exceed Number.MAX_SAFE_INTEGER and parsing it as a float silently loses money. Divide by 1e18 for ETH, carefully.
cors
Access-Control-Allow-Origin: * on every read endpoint. Call it straight from a browser.
rate limit
120 requests per minute per IP. The remaining budget comes back in X-RateLimit-Remaining. Over it you get a 429 and no hard feelings.
caching
Read endpoints are edge-cached for a few seconds. A tile is immutable — its pixels never change, only its owner does.

Endpoints

get/api/v1/mosaic

The board in one object: its shape, how many squares are claimed, what the next piece costs, and the ids of every held square.

{
  "grid": { "cols": 40, "rows": 25, "total": 1000 },
  "claimed": 0,
  "unclaimed": 1000,
  "holders": 0,
  "nextPriceWei": "20000000000000000",
  "volumeWei": "0",
  "market": { "listings": 0, "offers": 0, "swaps": 0 },
  "claimedPieceIds": [],
  "image": "/api/mosaic"
}

get/api/v1/pieces

All 1000 squares in id order.

?claimed=true or ?claimed=false to filter. ?limit= and ?offset= to page.
{
  "total": 1000,
  "count": 1000,
  "offset": 0,
  "pieces": [
    {
      "id": 0,
      "label": "0000",
      "col": 0,
      "row": 0,
      "claimed": false,
      "owner": null,
      "pieceNumber": null,
      "mintPriceWei": null,
      "acquiredAt": null,
      "tile": "/api/tile/0"
    }
  ]
}

get/api/v1/pieces/:id

One square, with its open listing, its offer book, and any swaps proposed for it.

{
  "id": 512,
  "label": "0512",
  "col": 32,
  "row": 12,
  "claimed": true,
  "owner": "0xE6b51234567890abcdef1234567890abcdef568F",
  "pieceNumber": 3,
  "mintPriceWei": "20000000000000000",
  "acquiredAt": "2026-08-28T01:14:02.881Z",
  "tile": "/api/tile/512",
  "listing": null,
  "offers": [],
  "swapsOffered": [],
  "events": "/api/v1/pieces/512/events"
}

get/api/v1/pieces/:id/events

Everything that has ever happened to one square, newest first. Cursor through with the returned nextBefore.

?limit= up to 200, ?before= an event id.
{
  "pieceId": 512,
  "count": 1,
  "nextBefore": null,
  "events": [
    {
      "id": "1",
      "pieceId": 512,
      "kind": "mint",
      "from": null,
      "to": "0xE6b51234567890abcdef1234567890abcdef568F",
      "priceWei": "20000000000000000",
      "signature": "0x4nd1…",
      "at": "2026-08-28T01:14:02.881Z"
    }
  ]
}

get/api/v1/market

The whole book: open listings, live offers and proposed swaps.

{
  "listings": [],
  "offers": [],
  "swaps": [],
  "note": "Offers are not escrowed. An accepted offer is an invitation to pay, not a claim on funds."
}

get/api/v1/events

The board's whole history across every piece, newest first.

?limit=
{ "count": 0, "events": [] }

get/api/v1/wallets/:address

What one wallet holds, what it has bid on, and what swaps it is party to.

{
  "wallet": "0xE6b51234567890abcdef1234567890abcdef568F",
  "piece": { "id": 512, "col": 32, "row": 12, "pieceNumber": 3,
             "mintPrice": "20000000000000000", "acquiredAt": "2026-08-28T01:14:02.881Z" },
  "offers": [],
  "swaps": []
}

Images

Two routes return pixels rather than JSON. Every square of the cat is public from the start — ownership is a ledger row, not a missing image.

/api/mosaic
The assembled cat as one WebP, 1600×1000. Every cell is painted. The homepage draws a lattice and owner marks on top.
/api/tile/:id
One 200 × 200 square of the picture, whether or not anybody holds it. Pixels are immutable; only the owner changes.

Writing

There is no public write API and there will not be one, because every write is authorised by a wallet signature that only a wallet can produce. Buying goes /api/reserve → a native ETH transfer whose data is the invoice reference → /api/claim; listings, offers and swaps are EIP-191 signed messages posted to /api/market/*. The message text is rebuilt server-side from the fields you send, so a signature cannot be moved onto a different action than the one your wallet showed you.

Try itGET /api/v1/mosaic The live board, right now, as JSON.BackgroundHow this works The mechanism, the price curve, and what is and is not on chain.

Ownership is a database row, not a token — read the honest section before you build anything that depends on it.