HTTP API

The HTTP routes of the Sandbox platform: library, games, likes, season, publishing, plays and AI generation, with parameters, responses and examples.

The platform exposes a small JSON API under /api. Read routes are public and keyless; write routes rely on wallet signatures rather than accounts.

Overview

TopicBehaviour
Base URLhttps://buildsandbox.app
FormatJSON in and out.
AuthenticationNo key. Writes that involve a wallet require a message signature (EIP-191) verified on the server.
ErrorsMatching HTTP status and a { "error": "…" } body.
Draft modeWith no storage configured, reads return live: false and empty lists or the templates; writes answer 503.
Templates are exposed as games whose id starts with tpl- (for instance tpl-race, tpl-hunt). They have boards, but never count for rewards.

Library

GET/api/librarystatic

Returns the whole asset catalogue as produced by the pipeline. The route is static (force-static): it is computed at build time and served from cache.

ResponseTypeScript
{
  assets: {
    id: string
    name: string
    category: 'characters' | 'creatures' | 'vehicles' | 'architecture' | 'nature'
            | 'weapons' | 'furniture' | 'props' | 'textures' | 'hdri' | 'effects' | 'audio'
    style: 'realistic' | 'stylized' | 'cartoon' | 'lowpoly' | 'voxel'
    tags: string[]
    source: string
    author: string
    sourceUrl: string
    license: 'CC0' | 'CC-BY' | 'CC-BY-SA' | 'MIT' | 'Other'
    attributionRequired: boolean
    attribution: string
    url: string            // GLB, texture set, .hdr or audio file
    thumb: string
    turntable?: string
    polycount?: number
    bytes: number
    rigged: boolean
    animations: string[]
    size: [number, number, number]
    height: number
    importedAt: string
    featured?: boolean
    maps?: Record<string, string>   // textures only
  }[]
}
Shell
curl https://buildsandbox.app/api/library

Games

List games

GET/api/games

Published games followed by the templates, each with its play statistics.

modeGameMode
Filter on a mode (race, platformer, arena, runner, explore, ctf, tower).
creatorstring
Filter on the creator address (case-insensitive).
sort'new' | 'played'Default 'new'
played sorts by play count, then by publication date.
ResponseTypeScript
{
  live: boolean            // false in draft mode (no storage)
  games: {
    id: string             // on-chain id, "d-<hash prefix>" in draft registry, or "tpl-*"
    title: string
    description: string
    mode: GameMode
    creator: string        // address, or "sandbox" for templates
    cover: string | null
    uri: string            // where the document lives
    hash: string           // keccak256 of the canonical document
    version: number
    publishedAt: number    // ms
    updatedAt: number      // ms
    hidden: boolean
    onchain: boolean       // true once registered in Sandbox.sol
    tx?: string
    remixOf?: string
    plays: number
    players: number
  }[]
}
Shell
curl https://buildsandbox.app/api/games?mode=race&sort=played

Get a game

GET/api/games/[id]
idstringrequired
Game id (path segment): numeric on-chain, d-… for the draft registry, or tpl-….
doc'1'
With ?doc=1, returns only the raw SandboxGame document.
ResponseTypeScript
{
  card: GameCard & { plays: number; players: number; anon: number }
  doc: SandboxGame | null
  board: { address: string; score: number; at: number }[]   // best score per wallet, top 20
}

Answers 404 when the game does not exist, or when ?doc=1 is asked and the document is missing. The document format is described in Scene format.

Shell
curl https://buildsandbox.app/api/games/tpl-hunt?doc=1

Likes

GET/api/likes
idsstring
Comma-separated ids (games or assets). Without ids, returns every non-zero count.
ResponseJSON
{ "likes": { "tpl-race": 12, "qt-t-rex": 4 } }

Counts come from a single storage listing, kept 30 seconds in memory and served with cache-control: public, max-age=20.

POST/api/likes

Body { id, address? }. One like per visitor and target: the wallet address when given, otherwise a salted hash of the IP. Answers { ok: true }, or 400 for a bad id.

Season and rewards

GET/api/season

The current epoch (a 24-hour window), the standings of the current and previous epochs, the settlements already posted and, when the contract is deployed, its treasury state.

ResponseTypeScript
{
  live: boolean
  epoch: number
  start: number            // ms
  end: number              // ms
  current: Standing[]      // sorted by weight
  previous: Standing[]
  settlements: {
    epoch: number
    amountWei: string
    totalWeight: number
    rows: { id, title, creator, weight, players, plays }[]
    tx: string
    at: number
  }[]
  treasury: { balance: string; reserved: string; epoch: number } | null
}

interface Standing {
  id: string; title: string; creator: string; mode: GameMode
  cover: string | null; weight: number; players: number; plays: number
}

Weight is expressed in hundredths (a weight of 1 is 100). The formula is explained in Epoch rewards.

Publishing

Publishing takes two calls. The studio chains them for you; they are documented here for third-party tools.

1. Upload the document

POST/api/games/upload

Body { doc, cover? }, where cover is a PNG data URL of 2 MB at most. The document is validated, hashed and stored under its hash. Response { hash, uri, cover }.

StatusCause
400Bad JSON, or document refused by validation (the message says why).
413Document over 40 MB or cover over 2 MB.
503Draft mode: no storage configured.

2. Register the card

POST/api/games/register

Makes the game visible on the platform. Two paths depending on whether the on-chain registry is deployed.

Body { hash, address, signature, cover?, remixOf? }. The signature covers the message below; the server also checks the token holding threshold once the token is live. The game id becomes d- followed by the first ten characters of the hash.

Signed messageText
Sandbox publish
title: <title>
hash: <0x…>
StatusCause
400Missing hash, missing signature, or on-chain hash mismatch.
401Bad signature.
403Token balance too low to publish.
404Document not uploaded, or game not found on-chain.
503Draft mode.

The publishing flow in the studio

Plays

POST/api/plays

Sent at the end of every run. Tied to a wallet — by the sitting’s session signature or by the signature of the run itself — it counts fully toward the game’s weight for the current 24-hour epoch; only the run signature can improve the wallet’s best score on the board. Anonymous, it counts a tenth.

gamestringrequired
Game id (published or tpl-*).
scorenumberrequired
Final score, rounded and floored at 0.
atnumberrequired
Timestamp in ms; refused when more than 10 minutes away from server time.
timenumber
Run duration.
wonboolean
Run won.
modestring
Mode played.
addressstring
Player wallet (with signature).
signaturestring
Signature of the play message below: it ranks the score.
session{ at, signature }
The sitting’s session signature, valid six hours: it ties the run to the wallet without ranking it.
Signed messagesText
Sandbox session
game: <id>
at: <at>

Sandbox play
game: <id>
score: <score>
at: <at>
ResponseJSON
{ "ok": true, "live": true, "signed": true, "ranked": true, "improved": false }

Errors: 400 malformed or stale play, 401 bad signature or stale session, 404 unknown game. In draft mode the route answers { ok: false, live: false }.

AI 3D generation

Paid, when the token launches

Until the token and the generation treasury are configured, job creation answers 403 token_not_live. See AI 3D generation.
POST/api/gen3d

Body { kind, prompt?, imageUrl?, quality?, textured?, rig?, provider?, address, payTx }. payTx is the token transfer to the treasury; it is checked on-chain and works once. Response { job, quota }.

StatuserrorCause
400prompt_required / image_requiredMissing prompt or image.
401wallet_requiredMissing or bad address.
402payment_required / payment_used / payment_pending / payment_failed / payment_mismatchPayment missing, already used, unconfirmed, failed or too small.
403token_not_liveGeneration not open yet.
429quota_exceededDaily quota reached.
503no_providerNo Meshy or Tripo key configured.
GET/api/gen3d?owner=0x…

Lists a wallet’s jobs and returns { jobs, providers, quota, limit, open }: which providers have a key, the quota used and whether generation is open.

GET/api/gen3d/[id]

Polls the provider while the job runs, then returns { job } with status (queued, running, done, failed), progress from 0 to 100 and, when done, result.url pointing at the GLB copied into our storage. 404 not_found for an unknown id.

POST/api/gen3d/upload

Reference image for image-to-3D: multipart (field file) or JSON { data: "data:image/png;base64,…" }. PNG, JPEG or WebP, 8 MB max. Response { url, id, bytes }; errors 400 file_required, 413 too_large, 415 unsupported_type.