Design: submissions

Design, 27 October 2026. Stage 1 of the Build connector plan (Mark): hand it in first. This page is the contract between the server and everything that submits: the site's form, the ship editor and the AI connector all use the same API.

Why

Every road into Build used to end on GitHub. The jobs board now offers real in-game commissions for Titanoboa modules, and most people who'd design one have never opened a pull request. So handing something in should take a sign-in and a button, from wherever you made it.

The rules

What the server fills in

So the credit and the commission can't go astray, the server sets these, whatever the file says:

The API

On https://space.witchspace.org.uk. JSON in and out. Our pages only (CORS for witchspace.org.uk and localhost), signed in with the game's session: Authorization: Bearer <session>, as the game's own /me calls. Errors are {error} with a status: 400 (a bad request, or a file that fails: {error, problems: [...]}), 401 (not signed in), 403 (no callsign, or not yours), 404, 409 (can't do that in its state), 413 (too big), 429 (limits).

A submission

interface Submission {
  id: string;                  // 10 letters and digits
  kind: 'ship' | 'module';
  itemId: string;              // the file's id: content/ships/<itemId>.json or content/modules/<itemId>.json
  title: string;               // the file's name
  revises: boolean;            // an improvement to something already in the game
  status: 'submitted' | 'changes' | 'accepted' | 'withdrawn';
  note: string;                // the author's note to the reviewer
  ai: boolean;                 // an AI assistant helped
  via: 'site' | 'connector';
  review: string | null;       // the maintainer's notes, when sent back (or accepted)
  pr: string | null;           // the pull request, once accepted
  version: number;             // 1, then up by one each revision
  created: number;             // ms
  updated: number;             // ms
  warnings: string[];          // things the checks noticed that don't stop it (ask the author to look)
  file?: object;               // the file as stored, on GET /submissions/<id> only
  callsign?: string;           // the author, in the maintainers' queue only
}

For everyone signed in

Call Body Answer
POST /submissions {kind, file, note?, ai, via} 201 {submission}, or 400 {error, problems}
GET /submissions/mine {submissions: Submission[], limits: {today, perDay, waiting, maxWaiting}}, newest first, without file
GET /submissions/<id> {submission} with file (yours, or any for maintainers)
PUT /submissions/<id> {file, note?, ai?} {submission}: a new version, back to submitted. Only while submitted or changes.
DELETE /submissions/<id> {submission}, now withdrawn. Only while submitted or changes.

file is the JSON object, or its text. kind must match what it is. ai is required (true or false), so the question is always asked.

Checking without sending

POST /submissions/check {kind, file} runs the same checks and stores nothing: {problems, warnings, title, itemId, revises} (the last three null when it fails). It works signed out, so people can check before they make an account, and it doesn't count towards the daily limit; it has its own, 30 a minute per network. The site's form and the ship editor use it as you type.

Problems and warnings

A ship that flies must fit the docking slot (90 m wide, 26 m tall, wings level), or say "docks": false: otherwise it's a dock: … problem (ships and classes, "Shape and size").

For a ship that can fly, the warnings include the MCP server's balance check against the fleet, as balance: … lines ("Top speed is among the highest in the fleet", "give it a real weakness"); they never stop a submission.

Every problem and warning reads <where>: <what>. where is a field path as the file writes it (length, geometry.vertices[3], credits.authors) when the check names one, or else an area (file, id, kind, geometry, size, maker, description, homage). The site pins the ones with a field path to that field and lists the rest.

For maintainers

Signed in as a maintainer (an account whose email is on the Worker's MAINTAINERS secret, comma-separated), with the game's session as everywhere else; or with the admin key (X-Admin-Key), from npx tsx tools/submissions.ts. GET /me says maintainer: true for a maintainer, so the site shows the review page only to them. (Mark reviews from his phone: a session, not the admin key, is what belongs in a browser.)

Call Body Answer
GET /submissions?status=submitted {submissions} with callsign, oldest first (any status; default submitted)
POST /submissions/<id>/review {decision: 'accept' | 'changes', notes?} {submission}

Privacy

A submission keeps the account it came from, the file, the notes and the dates. Deleting your account deletes your submissions, except what's already in a pull request, which is public in the repository under CC BY-SA, credited to your callsign.

As built (27 October 2026)

Think something here should change? Say so on the ideas board, where others can vote it up, or email hello@witchspace.org.uk. If you code: this page is docs/design/submissions.md on GitHub, open to pull requests.