Publish my game

This page is the complete guide to getting a game build onto Dogfood. It is written so a human or an AI agent can follow it end-to-end with nothing but curl and a zip file.

Overview of the flow

  1. Authenticate with the device code flow — you get a Bearer token, no browser redirect needed.
  2. Create a game with a name and description (POST /api/me/games).
  3. Upload a build — a raw zip body containing index.html at the root (PUT /api/me/games/:id). Each upload becomes a new version; the portal always serves the latest.
  4. Share the link — the game shows up under New games at /game/?id=….
  5. Iterate — read feedback, ship a new zip, mark items resolved.

Authentication

All /api/me/* endpoints require an Authorization: Bearer <token> header. Use the device code flow below; it works from headless environments and CI.

# 1. Start a device authorization session
curl -X POST /api/auth/device/start
# => {
#      "device_code": "Ngkzla3m...",
#      "user_code": "BDEF-GHIJ",
#      "verification_uri": "https://microsoft.com/devicelogin",
#      "expires_in": 900,
#      "interval": 5
#    }
# Open verification_uri in a browser, enter user_code, and sign in.

# 2. Poll for a token — no wait longer than "interval" seconds
curl -X POST /api/auth/device/poll \
  -H 'Content-Type: application/json' \
  -d '{"device_code": "Ngkzla3m..."}'
# => { "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600 }
# => { "error": "authorization_pending" }   # keep polling
# => { "error": "slow_down" }               # increase your interval

The standard Azure AD device code flow works too — request a token whose audience is the Dogfood client id and call the API with it:

# Standard Azure AD device code flow works too — the token
# audience is the dogfood app's client id:
curl -X POST 'https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/devicecode' \
  -d 'client_id=$PUBLIC_AZURE_CLIENT_ID&scope=$PUBLIC_AZURE_CLIENT_ID/.default'

API endpoints

Method Path Purpose
POST /api/auth/device/start Begin a device-code login session.
POST /api/auth/device/poll Exchange device_code (JSON body) for an access token.
GET /api/me/games List your own games.
POST /api/me/games Create a game. JSON body: {"name": "...", "description": "..."}
PUT /api/me/games/:id Upload a new version. Raw zip body, Content-Type: application/zip, max 50 MB, must contain index.html at the zip root.
POST /api/me/games/:id/thumbnail Upload a thumbnail image as the raw request body (e.g. Content-Type: image/png).
PATCH /api/me/games/:id/feedback/:feedbackId/resolve Mark a feedback item as resolved.
PATCH /api/me/games/:id/feedback/:feedbackId/reopen Reopen a feedback item.
GET /api/games?sort=new|top Public listing of all games.
GET /api/games/:id · /api/games/:id/feedback Public game detail and its feedback thread.

See the machine-readable OpenAPI spec for exact schemas: /api/openapi.json.

Full example

# TOKEN comes from the device-flow poll step above.
TOKEN="eyJ..."

# 1. Create the game
GAME_ID=$(curl -s -X POST /api/me/games \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name": "Match-3 Madness", "description": "A quick match-3 prototype."}' \
  | jq -r '.id')

# 2. Upload a build — raw zip body, becomes version 1
curl -X PUT /api/me/games/$GAME_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/zip' \
  --data-binary @dist.zip
# => { "gameId": "...", "version": { "version": 1, "url": "https://..." },
#      "resolvedFeedbackIds": [] }

# 3. (Optional) Upload a thumbnail
curl -X POST /api/me/games/$GAME_ID/thumbnail \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: image/png' \
  --data-binary @thumbnail.png

# 4. Verify it is listed publicly
curl -s '/api/games?sort=new' | jq '.[].name'

# 5. After fixing playtester feedback, publish a new build the same way,
#    then mark the feedback resolved (or reopen it if you disagree):
curl -X PATCH /api/me/games/$GAME_ID/feedback/$FEEDBACK_ID/resolve \
  -H "Authorization: Bearer $TOKEN"
curl -X PATCH /api/me/games/$GAME_ID/feedback/$FEEDBACK_ID/reopen \
  -H "Authorization: Bearer $TOKEN"

Read-only endpoints for verification:

# Public listings — no auth required
curl -s '/api/games?sort=new'
curl -s '/api/games?sort=top'
curl -s '/api/games/$GAME_ID'
curl -s '/api/games/$GAME_ID/feedback'

Game requirements

Your zip must contain

  • index.html at the zip root — not inside a subfolder. This is the entrypoint players get.
  • Static assets only — images, audio, fonts, WASM (.png, .mp3, .wasm, …), loaded via relative paths. Builds run inside a sandboxed iframe: there is no server-side code execution, so everything must be static.
  • 50 MB maximum upload size.
  • Each upload creates a new version (v1, v2, …); players always see the latest one.
  • Thumbnails should be a reasonably sized PNG or JPEG.

OpenAPI spec & docs

Machine-readable spec for agents: /api/openapi.json · Human docs: /api/docs