Skip to content
ShortPlay Developer Docs

Makers API

The Makers API lets your own tooling drive ShortPlay Studio over HTTP — the same upload, publish, and edit actions the Studio UI performs, called with a Personal Access Token (PAT) instead of a browser session. This page covers minting a token, what a PAT can (and can't) do, the generated route reference, and a copy-pasteable walkthrough for publishing a game end to end.

Personal access tokens

Mint tokens from Studio → Settings → API tokens:

Studio Settings — API tokens panel, showing a named token row with its prefix, created/last-used dates, and a Revoke button, plus a New token button

  1. Click New token, give it a name (e.g. "CI publisher" — anything that helps you tell your tokens apart later; a token isn't scoped by name, it's a full credential for your account).
  2. Click Create. The raw token is shown once, in a sp_pat_…-prefixed value — copy it immediately. ShortPlay never stores the raw value, only its hash; if you close the dialog without copying it, the token still works, you just can't see it again (revoke it and mint a new one instead of trying to recover it).
  3. Use it as a bearer credential: Authorization: Bearer sp_pat_AbCd1234… on every request.
  4. The tokens list shows each token's name, its prefix (enough to recognize it — never the full value again), when it was created, and when it was last used — so you can spot a token that's gone stale or one that's being called from somewhere you didn't expect.
  5. Revoke is immediate and irreversible: the very next call with that token gets 401. There is no "un-revoke" — mint a new token if you need a replacement.

What a PAT can do

A PAT authenticates as your account, but only on an allowlist of maker-action routes — the upload → publish lifecycle, metadata edits, cover replacement, versions, analytics, and link submission. Every route in the API reference below is on that allowlist.

What a PAT cannot do

Every route not on the allowlist — identity, sessions, social, account deletion, and (deliberately) token management itself — returns a uniform 403 for a PAT caller, even though the same route works fine in a signed-in browser session. This is not a bug to work around: a PAT can never mint, list, or revoke a PAT. Token creation, listing, and revocation are session-auth only — you manage tokens from the Studio Settings UI in a signed-in browser, never via the API itself. That keeps a leaked token from being used to mint itself a replacement or lock you out by revoking your other tokens.

Rate limits

Every write call (POST/PUT/PATCH/DELETE on an allowlisted route) is limited to 30 requests per token per 60-second window. GET/HEAD calls are unmetered. The limit is keyed on the token, not the account — two tokens on the same account (e.g. one for a CI job, one for local scripting) get independent budgets; a script hammering one token never starves the other. Exceeding it returns 429.

API reference

Generated from api/openapi/makers-v1.yaml in the ShortPlay repo — the single source this section renders from, so it cannot drift from the actual allowlist. Every call below needs the Authorization: Bearer sp_pat_… header; it's omitted from each entry for brevity.

POST /v1/uploads/init

Start an upload — returns an uploadId + a PUT url for the bundle zip.

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Request body:

Field Type Required
makerPageId string yes
title string yes
hook string yes
tag string yes

Responses:

Status Description
201 Upload record created.
401 No PAT
403 The token's account is not a member of makerPageId.
429 Per-token write rate limit exceeded.

PUT /v1/uploads/{uploadId}/bundle

Stream the built game bundle (zip) to the upload.

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Request body:

Field Type Required
(request body) binary — application/zip yes

Responses:

Status Description
204 Bundle stored.
401 No PAT
403 Not a member of the upload's studio.
404 Unknown uploadId.

POST /v1/uploads/{uploadId}/complete

Validate the uploaded bundle + confirm the manifest — creates a draft game.

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Request body:

Field Type Required
title string no
hook string no
tag string no

Responses:

Status Description
201 Draft game created.
401 No PAT
403 Not a member of the upload's studio.
422 Bundle failed validation (e.g. no index.html).

PATCH /v1/games/{gameId}

Edit a game's title / hook / tag / accent (partial update).

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Request body:

Field Type Required
title string no
hook string no
tag string no
accent string no

Responses:

Status Description
200 Updated game detail.
401 No PAT
403 Not a member of the game's studio.
404 Unknown gameId.

POST /v1/games/{gameId}/cover

Replace a game's cover image.

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Request body:

Field Type Required
(request body) binary — image/png, image/jpeg, image/webp, image/gif yes

Responses:

Status Description
200 Cover replaced.
401 No PAT
403 Not a member of the game's studio.
404 Unknown gameId.
415 Unsupported content-type.

POST /v1/games/{gameId}/publish

Publish the current draft — enters the live feed pipeline.

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Responses:

Status Description
200 Published (idempotent — re-publishing an already-live game also 200s).
401 No PAT
403 Not a member of the game's studio.
404 Unknown gameId.

POST /v1/games/{gameId}/unpublish

Take a published game back to draft (reversible; leaves the feed).

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Responses:

Status Description
200 Unpublished.
401 No PAT
403 Not a member of the game's studio.
404 Unknown gameId.

POST /v1/games/{gameId}/rollback

Re-point the live version to a prior published version.

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Request body:

Field Type Required
version string yes

Responses:

Status Description
200 Rolled back.
401 No PAT
403 Not a member of the game's studio.
404 Unknown gameId or version.

GET /v1/games/{gameId}/manage

The manage-view detail for one game (header + edit prefill + state).

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Responses:

Status Description
200 Manage-view detail.
401 No PAT
403 Not a member of the game's studio.
404 Unknown gameId.

GET /v1/games/{gameId}/versions

List every version for a game + which is live.

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Responses:

Status Description
200 Version list.
401 No PAT
403 Not a member of the game's studio.
404 Unknown gameId.

POST /v1/games/{gameId}/versions/link

Add a link (non-hosted) version to an existing game.

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Request body:

Field Type Required
linkUrl string (uri) yes

Responses:

Status Description
200 Link version added.
401 No PAT
403 Not a member of the game's studio.
404 Unknown gameId.

GET /v1/games/{gameId}/analytics

The per-game maker analytics payload (plays, hearts, saves, players).

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Responses:

Status Description
200 Analytics payload.
401 No PAT
403 Not a member of the game's studio.
404 Unknown gameId.

GET /v1/maker/page

The signed-in maker's own studio page + their games.

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Responses:

Status Description
200 The maker's console payload.
401 No PAT
404 The token's account is not a member of any studio.

POST /v1/submissions

Submit a link (e.g. an itch.io URL) for the verify queue.

Auth: Maker PAT (Authorization: Bearer sp_pat_<token>)

Request body:

Field Type Required
url string (uri) yes

Responses:

Status Description
201 Submission recorded.
401 No PAT
429 Per-submitter rate limit exceeded.

Publish a game by API

This walks the full upload → publish lifecycle with curl, the same sequence Getting started walks through in the Studio UI. Every command below is executed against a real local instance of the API as part of this repo's test suite, so it can't silently rot — if a route or field name changes, the example changes with it or the test goes red.

Creating the studio itself is a one-time, UI-only step — there's no API route for it (Studio → "Start a studio", or see Getting started). Everything below assumes you already have a studio and a PAT minted for it.

Set your token once:

# From Studio Settings → API tokens (see above). Never commit this to source control.
export SP_PAT="sp_pat_…"

Look up your studio's page id (you'll need it for the upload below) — GET /v1/maker/page is on the allowlist, so your PAT can fetch this itself:

MAKER_PAGE_ID=$(curl -sS -f https://api.shortplay.io/v1/maker/page \
  -H "Authorization: Bearer $SP_PAT" \
  | jq -r '.page.id')
echo "Studio page id: $MAKER_PAGE_ID"

1. Start the uploadPOST /v1/uploads/init:

UPLOAD_INIT=$(curl -sS -f -X POST https://api.shortplay.io/v1/uploads/init \
  -H "Authorization: Bearer $SP_PAT" \
  -H "Content-Type: application/json" \
  -d "{\"makerPageId\":\"$MAKER_PAGE_ID\",\"title\":\"My Game\",\"hook\":\"A fun tagline\",\"tag\":\"arcade\"}")
UPLOAD_ID=$(echo "$UPLOAD_INIT" | jq -r '.uploadId')
echo "Upload id: $UPLOAD_ID"

2. Stream the bundlePUT /v1/uploads/:id/bundle (a zip with index.html at its root; see Zip format & versions):

curl -sS -f -X PUT "https://api.shortplay.io/v1/uploads/$UPLOAD_ID/bundle" \
  -H "Authorization: Bearer $SP_PAT" \
  -H "Content-Type: application/zip" \
  --data-binary @game.zip \
  -w '\nHTTP %{http_code}\n'

3. Complete the uploadPOST /v1/uploads/:id/complete (validates the bundle, creates the draft game):

UPLOAD_COMPLETE=$(curl -sS -f -X POST "https://api.shortplay.io/v1/uploads/$UPLOAD_ID/complete" \
  -H "Authorization: Bearer $SP_PAT" \
  -H "Content-Type: application/json" \
  -d '{"title":"My Game","hook":"A fun tagline","tag":"arcade"}')
GAME_ID=$(echo "$UPLOAD_COMPLETE" | jq -r '.upload.gameId')
echo "Draft game id: $GAME_ID"

4. Publish itPOST /v1/games/:id/publish (enters the live feed pipeline after the next pool rebuild):

PUBLISH_RESULT=$(curl -sS -f -X POST "https://api.shortplay.io/v1/games/$GAME_ID/publish" \
  -H "Authorization: Bearer $SP_PAT")
echo "Publish result: $(echo "$PUBLISH_RESULT" | jq -r '.game.state')"

5. Edit metadataPATCH /v1/games/:id (partial update; only send the fields you're changing):

PATCH_RESULT=$(curl -sS -f -X PATCH "https://api.shortplay.io/v1/games/$GAME_ID" \
  -H "Authorization: Bearer $SP_PAT" \
  -H "Content-Type: application/json" \
  -d '{"hook":"An even better tagline"}')
echo "Updated hook: $(echo "$PATCH_RESULT" | jq -r '.game.hook')"

6. Replace the coverPOST /v1/games/:id/cover (raw image bytes, not multipart — png, jpeg, webp, or gif):

COVER_RESULT=$(curl -sS -f -X POST "https://api.shortplay.io/v1/games/$GAME_ID/cover" \
  -H "Authorization: Bearer $SP_PAT" \
  -H "Content-Type: image/png" \
  --data-binary @cover.png)
echo "Cover: $(echo "$COVER_RESULT" | jq -r '.game.cover')"

7. UnpublishPOST /v1/games/:id/unpublish (reversible; the game leaves the feed but the draft and its versions are untouched):

UNPUBLISH_RESULT=$(curl -sS -f -X POST "https://api.shortplay.io/v1/games/$GAME_ID/unpublish" \
  -H "Authorization: Bearer $SP_PAT")
echo "Unpublish result: $(echo "$UNPUBLISH_RESULT" | jq -r '.game.state')"