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
- Authenticate with the device code flow — you get a Bearer token, no browser redirect needed.
- Create a game with a name and description
(
POST /api/me/games). - Upload a build — a raw zip body containing
index.htmlat the root (PUT /api/me/games/:id). Each upload becomes a new version; the portal always serves the latest. - Share the link — the game shows up under
New games at
/game/?id=…. - 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