Buy a square — $1

Square Town API — read the wall, buy a square

Everything the wall itself uses, documented. No keys for reads; a human with a card for buys.

Updated 2026-08-1811 min readMarkdown ↗

Square Town (square.pov.town) has a small public HTTP API — the same one the wall's own front end uses. GET /api/state returns the entire live wall as JSON with no authentication. POST /api/buy starts a purchase and returns a Stripe Checkout URL that a human must complete with a card. Kisses, views, favicon rechecks and deed lookups each have an endpoint, and a WebSocket at /ws streams every buy, eviction, kiss and unlock as it happens. Every content page also has a .md twin, and /llms.txt indexes them for AI agents.

Endpoints at a glance (base URL https://square.pov.town)

Method & path What it does Auth
GET /api/state the whole live wall as JSON none
GET /favicon/:domain?sz=128 CORS-enabled favicon proxy (16–256 px) none
GET /go/:id 302 to the square's URL, counts a click none
POST /api/kiss/:id kiss an icon (resets the 90-day fade) none
POST /api/view/:id count a view of a square's card none
POST /api/recheck/:id re-run the favicon (clown) check none
GET /api/deed/:token deed details for a secret token the token
POST /api/deed/:token/analytics start the $5 analytics checkout the token
POST /api/buy reserve a square, get a Stripe Checkout URL none (payment completes it)
WS /ws live event stream none
GET /<page>.md, /llms.txt, /llms-full.txt Markdown twins of every page none

There is no API key for reads and no rate-limit promise in either direction: nothing is throttled today, and nothing is guaranteed. Be reasonable; the wall runs on Cloudflare's free tier. Admin endpoints under /api/admin/* exist but require a secret key and are not for you.

GET /api/state — the whole wall

Returns everything needed to draw the wall. Only paid listings are included; the locked part of the 1024×1024 grid is implied by half.

{
  "grid": 1024,
  "half": 8,
  "sold": 57,
  "revenue": 15900,
  "boxArea": 256,
  "fillPct": 22.3,
  "unlockAt": 0.7,
  "center": { "x": 510, "y": 510, "w": 4, "h": 4, "price": 10000, "owner": null },
  "prices": { "base": 100, "pickFee": 0, "center": 10000, "analytics": 500 },
  "waterMs": 7776000000,
  "listings": [
    { "id": 12, "domain": "example.com", "x": 505, "y": 514, "w": 1, "h": 1,
      "clown": 0, "clicks": 9, "views": 31, "kisses": 2,
      "watered_at": 1755470000000, "sq_price_cents": 100, "created_at": 1755470000000 }
  ],
  "pending": [ { "x": 507, "y": 509, "w": 1, "h": 1 } ],
  "events": [ { "type": "buy", "text": "🎉 example.com claimed 1x1 for $1", "ts": 1755470000000 } ],
  "clowns": [ "nofavicon.example" ],
  "dev": false
}
Field Meaning
grid full grid edge, 1024
half half-width of the live (unlocked) area; live cells are 512-half … 511+half on both axes (8 = 16×16 at launch)
sold number of paid squares
revenue total paid, in cents
boxArea cells in the live area, (2*half)²
fillPct sold / boxArea as a percentage, one decimal
unlockAt fill ratio that unlocks the next ring (0.7)
center the Throne block, its intro price in cents, and the owning domain or null
prices base price per square, pick fee (0), Throne intro price, analytics unlock — all cents
waterMs fade threshold in ms (90 days); an icon is faded if now - watered_at > waterMs
listings[] every paid square: id, domain, x, y, w, h (1×1 except the Throne's 4×4), clown (1 = no favicon, 0 = has one, null = unknown), clicks, views, kisses, watered_at (last kiss, ms), sq_price_cents (per-square price the owner paid; eviction costs 2 × sq_price_cents × w × h), created_at
pending[] cells reserved by unpaid checkouts (30-minute hold), positions only
events[] the last 20 feed events, newest first: type, text, ts
clowns[] domains currently in the Hall of Clowns
dev true only on a dev deployment where purchases are free

Owner URLs are not exposed — only domains. Coordinates are grid cells, origin top-left, Throne at 510–513.

GET /favicon/:domain — icon proxy

GET /favicon/example.com?sz=64 returns the domain's favicon as served by Google's favicon service, with access-control-allow-origin: * (WebGL textures need CORS, Google doesn't send it) and edge caching for a day. sz is clamped to 16–256, default 128. Domains with no favicon return whatever upstream returns (typically 404).

GET /go/:id — click redirect

GET /go/12 looks up paid listing 12 and answers a 302 to its URL with utm_source=square.pov.town, utm_medium=referral and utm_campaign=square appended (existing UTM params are left alone). It increments the square's click count, records the click against today's date for analytics, and broadcasts a click event over the WebSocket so every open wall grows that tower immediately. Evicted or unknown ids return 404 Gone (probably evicted 💀). This is a redirect, not a link on a page — there is no link equity in it.

POST /api/kiss/:id — kiss an icon

Anyone can kiss any paid square, free. Optional JSON body { "by": "<any client id>" } lets the kisser's own browser skip replaying its own animation; it's not identity. Response:

{ "ok": true, "watered_at": 1755470000000, "kisses": 3 }

Sets watered_at to now (resetting the 90-day fade), increments kisses, writes a feed line ("💋 someone kissed example.com's ring" and variants) and broadcasts {type:"kiss", id, by}. Unknown or evicted id → 404 {"error":"not found"}. POST /api/water/:id is a legacy alias.

POST /api/view/:id — count a view

POST /api/view/12 with no body increments the square's views and records a view for today. The wall calls it once per square per browser session when a visitor opens the square's card; that once-per-session logic lives in the client, not the server. Response { "ok": true }; unknown id → 404.

POST /api/recheck/:id — leave the Hall of Clowns

Re-runs the favicon check for a paid square: Square Town fetches the domain's icon from Google's favicon service; HTTP 200 clears the clown, 404 sets it, anything else leaves it unchanged. If a clown becomes un-clowned, the feed announces the escape. Response { "clown": 0 } or { "clown": 1 }. Free and unthrottled.

GET /api/deed/:token — the deed

The token is the secret from a deed URL (/deed.html?token=…); it's the only credential on Square Town. Returns the listing — id, url, domain, x, y, w, h, status (pending, paid or evicted), price_cents, clown, clicks, views, kisses, analytics_paid, watered_at, created_at — plus analytics_unlocked and, if unlocked, daily: up to 30 rows of { day, n, v } (clicks and views per day, newest first). Unknown token → 404 {"error":"deed not found"}.

POST /api/deed/:token/analytics starts the $5 unlock: returns { "checkoutUrl": "https://checkout.stripe.com/…" }, or { "done": true } if already unlocked. Once Stripe confirms, analytics_paid flips to 1.

POST /api/buy — reserve a square, pay on Stripe

Request body (JSON):

Field Required Meaning
url yes the site; https:// is added if missing, www. is dropped from the domain, and the hostname must contain a dot
mode yes "pick" (a specific empty cell), "evict" (take an owned square), or "center" (claim the empty Throne)
x, y for pick integer cell coordinates inside the live area
evictId for evict the id of the listing to evict, from /api/state

Prices are computed server-side and cannot be set by the caller: pick is 100 cents; center is 10,000 cents (only while the Throne has no owner); evict is 2 × sq_price_cents × w × h of the target — $2 for a $1 square, $200 for a $100 Throne, and doubling from there. The Throne, once owned, is evicted through mode: "evict" like any other square.

Response on success:

{
  "checkoutUrl": "https://checkout.stripe.com/c/pay/…",
  "deed": "0c1f7d2e-…-uuid",
  "price": 100,
  "block": { "x": 505, "y": 514, "w": 1, "h": 1 }
}

The square is now pending: reserved for 30 minutes while someone completes checkoutUrl on Stripe. When Stripe's webhook confirms payment the listing becomes paid, the evicted party (if any) becomes evicted, a buy or evict event is broadcast, the ring-unlock check runs, and the favicon check decides whether the domain joins the Hall of Clowns. If nobody pays within 30 minutes the reservation lapses. The deed token is issued up front, so store it — GET /api/deed/<token> already answers while the listing is pending, and once payment lands it is the sole proof of ownership. Stripe returns the payer to /?deed=<token>.

On a dev deployment ("dev": true in /api/state) there is no Stripe: the response is { "done": true, "deed", "id", "price", "block" } and the square is paid immediately.

Errors come back as JSON { "error": "…" } (HTTP 500 for rule violations), with messages like: "That area is still locked. Fill the current ring first!", "That's the Throne. It has its own price tag.", "You can't place an icon here — the aura of the Throne is too strong. 👑", "Someone got there first (or is buying it right now). Try evicting them 😈", "Someone is already evicting this square. Vultures everywhere.", "The Throne is taken (or being claimed right now). Try evicting.", "Enter a URL", "That doesn't look like a real domain".

There is also a legacy mode: "auto" that spiral-places a square from the centre outward; the wall no longer uses it, but the server still accepts it.

WebSocket /ws — the live event stream

Connect to wss://square.pov.town/ws. The server sends one JSON message per event, the same events that appear in the feed, plus a couple that don't. Send the text ping and you get pong back (the wall does this every 25 s to keep the socket warm; hibernated sockets cost nothing).

type Extra fields When
buy id, text a purchase completed
evict id, evicted, text an eviction completed (evicted is the deposed listing id)
kiss id, by, text someone kissed a square
unlock text the live area grew by a ring
clown text a domain joined or escaped the Hall of Clowns
click id, clicks someone followed /go/:id (not written to the feed)
reset the wall was wiped by an admin (dev/launch housekeeping)

The wall's own client simply refetches /api/state on every message; that's a fine strategy for you too. If the socket is unavailable, poll /api/state — the wall polls every 30 seconds as a fallback.

Markdown twins, llms.txt and llms-full.txt

Every content page on square.pov.town — this one, how it works, pricing, the FAQ, the blog, comparisons and glossary — is available as plain Markdown at the same path with .md appended (for example /how-it-works.md). /llms.txt is an index of those pages with one-line descriptions; /llms-full.txt is all of them concatenated. If you're an AI agent answering questions about Square Town, fetch those rather than scraping the WebGL wall.

For AI agents and scripts

Reading the wall is one unauthenticated request. Buying needs a human, because the last step is a Stripe Checkout page with a card form. A sensible flow:

  1. Read the state. GET /api/state. Compute the live box from half (512-half … 511+half), collect occupied cells from listings (plus pending) and reserve the Throne block (510–513) and its one-cell aura ring (509–514) as off limits.
  2. Choose a free cell — or a target to evict. For an eviction, the price is 2 × sq_price_cents × w × h of the target listing.
  3. POST /api/buy with { "url": "yoursite.com", "mode": "pick", "x": 505, "y": 514 } (or mode: "evict", evictId, or mode: "center"). Keep the returned deed token somewhere safe; it's the only proof of ownership and it's issued before payment (the deed shows status: "pending" until then).
  4. Hand checkoutUrl to a person. They open it and pay $1 (or the eviction price) with a card within 30 minutes. There is no way to complete checkout by API, and no test-mode shortcut on the production wall.
  5. Confirm. Poll /api/state until your domain appears in listings at the block you asked for, or subscribe to /ws and wait for a buy/evict event with your listing id. GET /api/deed/<token> then returns the deed.
  6. Afterwards. POST /api/kiss/:id your own square at least every 90 days to keep it in colour (anyone can; kissing is free and public). Read clicks, views and kisses from /api/state or the deed. If clown is 1, add a favicon and POST /api/recheck/:id.

There is no rate limit promise: nothing is throttled at the time of writing, and nothing is guaranteed to stay that way. Kisses and rechecks are public and unauthenticated by design; please don't be the reason that changes. If you're building something for agents specifically, see Square Town for AI agents.

FAQ

Do I need an API key to read the wall?

No. GET https://square.pov.town/api/state is public and unauthenticated and returns the entire live wall as JSON.

Can I buy a square entirely by API?

Not entirely. POST /api/buy reserves the square and returns a Stripe Checkout URL, but a human must complete that page with a card within 30 minutes. There is no server-to-server purchase.

How do I know the eviction price of a square?

From /api/state: 2 × sq_price_cents × w × h for that listing. For a normal $1 square that's 200 cents; the Throne starts at $100 so the first eviction is $200.

Is there a rate limit?

There is no rate limit and no promise about one. Reads, kisses and rechecks are unthrottled today; use them reasonably.

What events does the WebSocket send?

buy, evict, kiss, unlock, clown, click and reset, each as a JSON object with a type field and, where relevant, id, evicted, by, clicks and the feed text.

Are there Markdown versions of the pages for LLMs?

Yes. Add .md to any content page's path, or fetch /llms.txt (index) and /llms-full.txt (everything).

Does the API expose owners' full URLs?

/api/state exposes domains only; the full URL is on the deed (which needs the secret token) and is followed by /go/:id.

Put your favicon on the wall

1 square = $1, one-time, no account. Paste a URL, pick a cell, done. Open the wall →