Documentation

The API.

Every slot's owner, price, content, and full history, as JSON. No key, no signup, no auth — the data is already public on-chain, so this is just a faster way to read it. Read only: nothing here can change the page, and buys and edits still go through the contract.

Base URL https://notawebsite.fun/api/v1. See the main docs for what a slot is and how the market works.

Worked exampleSee it in use: /demos All 165 slots refiled as a land registry, in four arrangements. Built from one request to the endpoint below and nothing else — no database, no server rendering.

Endpoints

GET /api/v1/site

Every slot — all 165 — in one response. This is the one to start with: rebuilding the page's state from the single-slot endpoint costs 165 requests.

GET /api/v1/slots/:id

One slot, the same object as an entry in /site. Ids are 0164 and are stable forever.

GET /api/v1/slots/:id/events

That slot's full history, newest first. Takes ?limit= (1–200, default 50) and ?cursor=.

Try it

curl https://notawebsite.fun/api/v1/slots/0
{
  "asOf": { "block": "100221423", "serverTime": 1786601580 },
  "slot": {
    "id": 0,
    "key": "announcement.text",
    "band": "announcement",
    "kind": "short_text",
    "registryKind": "short_text",
    "owner": "0xE6b5…568F",
    "ownerHandle": null,
    "price": {
      "base": "576000000000000",
      "last": "403200000000000",
      "current": "806400000000000",
      "multiplierBps": 0
    },
    "lastPurchaseTs": "1786455000",
    "takes": 3,
    "version": 5,
    "contentHash": "0x…",
    "upgrade": null,
    "slotDataNonce": "2",
    "listing": null,
    "rental": null,
    "quarantined": false,
    "content": {
      "text": "Taken 3 times",
      "committed": "Taken {takes} times",
      "linkUrl": null,
      "imageUrl": null,
      "videoSrc": null,
      "color": null,
      "bgColor": null,
      "style": {},
      "reveal": null,
      "rotate": null,
      "variants": null
    }
  }
}

Six things worth knowing before you build on it

asOf.block
Every response carries the last block the indexer has processed. This is a mirror of chain state, not chain state — without checking this you cannot tell a slot nobody has touched in a week from an indexer that stopped an hour ago. Both look like a quiet row.
money is a string
Every wei amount is a decimal string, never a JSON number. A slot can be worth more than 2⁵³ wei — that is only about 0.009 ETH — so parsing these as numbers loses money almost immediately. Use BigInt or your language's equivalent.
price.current
What a buyer pays right now: the base price for an unclaimed slot, otherwise the decayed take price with the owner's multiplier applied. It is advisory — it moves with the clock and with every purchase. Read the contract before you build a transaction on it.
kind vs upgrade
kind is what the slot is today, already resolved. If upgrade is non-null and its expiresAt is in the past, the upgrade is reapable, not expired — only an on-chain reapUpgrade call ends it. Resolve the kind yourself from that timestamp and you will disagree with the chain until somebody reaps. reapable is precomputed so you never have to.
content.text vs content.committed
text is what a visitor sees: template variables substituted, and for a rotating slot the frame showing right now. committed is the exact body the on-chain contentHash attests to. Use committed if you want to verify against the chain, text if you want to mirror the page.
sentinels are null
The contract uses 0 to mean "unset" in several places. Here listing, rental and upgrade are simply null when absent. The one that stays raw is price.multiplierBps, where 0 is the stored value the contract reads as 1× — it is not a 0× multiplier, and it is left alone so a value read here can be written straight back.

Paging through history

Follow nextCursor until it comes back null. Pass it verbatim — it encodes a block and an event position, and hand-building one gets the ordering wrong at block boundaries.

let cursor = null;
do {
  const url = new URL("https://notawebsite.fun/api/v1/slots/42/events");
  url.searchParams.set("limit", "200");
  if (cursor) url.searchParams.set("cursor", cursor);
  const page = await (await fetch(url)).json();
  for (const event of page.events) console.log(event.kind, event.price);
  cursor = page.nextCursor;
} while (cursor);

An event's ts is a block timestamp, so events in the same block share it and it can be null on older rows. Do not page on it — that is exactly what the cursor is for.

Rate limits and caching

120 requests per minute per IP, across all of /api/v1. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; going over gets a 429 with Retry-After.

Responses are cacheable for 5 seconds. Polling faster than that mostly buys you the same bytes — if you want live updates, the page itself pushes them over a websocket, and polling /site once every few seconds is plenty for anything else. CORS is open (*), so browser clients work without a proxy.

What is and is not stable

The /api/v1 prefix is a versioned surface: fields get added, existing ones do not change meaning or disappear. Anything else under /api/ is internal plumbing for this site and will change without warning — if you found an endpoint that is not documented on this page, it is not for you to build on.

Content is served through the same safety gate as the page: a slot that has been moderated reports quarantined: true and its registry default text, and a link that failed the blocklist check comes back with a null linkUrl. There is no way to read suppressed content through this API, which is deliberate.

Read-only and free. If you build something with it, it would be nice to hear about it.