Dweller Games API v1
Base URL: https://dweller.games/api/v1 (local dev: http://localhost:8080/api/v1).
All bodies are JSON (Content-Type: application/json). Times are RFC 3339 UTC strings.
Errors always look like {"error": "machine_code", "message": "Human readable"} with a 4xx/5xx status.
Authentication
Send Authorization: Bearer <token>. Tokens start with dg_. Get one by:
POST /auth/loginwith a username/email and password (launcher sign-in form), or- the device flow (
POST /auth/device, then pollPOST /auth/device/token) used by the CLI and by
the launcher's "Sign in with browser" button, or - creating a personal token at
https://dweller.games/settings/tokens(CI, GitHub Actions).
Endpoints marked π need a token. Creator endpoints need the account to be an approved creator.
POST /auth/login
{"login": "alice", "password": "hunter22", "device_name": "Alice's PC (launcher)"}
β 200 {"token": "dg_β¦", "user": User} Β· 401 {"error":"invalid_credentials"} Β· 403 {"error":"banned"} Β· 429 rate limited.
POST /auth/logout π
Revokes the token used for the call. β 204.
POST /auth/device
{"client": "launcher", "device_name": "Alice's Mac"}
β 200
{"device_code": "long-secret", "user_code": "WXYZ-2345",
"verification_uri": "https://dweller.games/activate",
"verification_uri_complete": "https://dweller.games/activate?code=WXYZ-2345",
"interval": 3, "expires_in": 600}
POST /auth/device/token
{"device_code": "long-secret"}
β 200 {"token": "dg_β¦", "user": User} once the user approves in the browser.
β 428 {"error":"authorization_pending"} keep polling every interval seconds.
β 400 {"error":"expired_token"} or {"error":"access_denied"} stop.
(Clients should also treat {"error":"slow_down"} as "add 2 seconds to the interval".)
GET /me π
β 200 User
Objects
// User
{"id": 12, "username": "alice", "display_name": "Alice", "avatar_url": "https://β¦" /* or "" */,
"role": "user" /* user | creator | admin */, "creator": true, "verified": false}
// Creator (embedded)
{"username": "phastings", "display_name": "Dweller Studio", "avatar_url": "", "verified": true}
// GameSummary
{"id": 3, "slug": "drift-kings", "title": "Drift Kings", "tagline": "Open-world night racingβ¦",
"creator": Creator, "cover_url": "https://cdnβ¦/cover.jpg", "header_url": "https://cdnβ¦/header.jpg",
"accent": "#ff3d7f", "tags": ["racing", "open world"],
"platforms": ["windows", "linux", "macos"], "playable_in_browser": false,
"latest_version": "1.0.0", "updated_at": "2026-11-13T19:00:00Z", "released_at": "2026-11-01T12:00:00Z",
"rating": {"positive": 10, "total": 12, "percent": 83}, "downloads": 1234,
"ai_tools": ["Claude Code"], "url": "https://dweller.games/games/drift-kings"}
// Build: one downloadable file for one platform
{"id": 77, "platform": "windows" /* windows | macos | linux | android | web | gba */,
"arch": "x64" /* x64 | arm64 | universal | any */,
"kind": "zip" /* zip | exe | apk | rom | web | appimage | dmg | tar.gz */,
"filename": "DriftKings-1.0.0-windows-x64.zip", "size": 109831632,
"sha256": "hexβ¦", "exec": "DriftKings.exe",
"url": "https://cdn.dweller.games/builds/β¦", // direct CDN file (use for downloading)
"download_url": "https://dweller.games/dl/77", // counted redirect (use in browsers)
"play_url": "" // set for web/gba builds
}
// Release
{"id": 9, "version": "1.0.0", "channel": "stable", "released_at": "β¦", "notes_md": "β¦", "notes_html": "β¦",
"builds": [Build]}
// GameDetail = GameSummary + these fields
{"description_md": "β¦", "description_html": "β¦", "screenshots": ["https://β¦"],
"controls": "β¦", "engine": "Godot 4.7", "website": "", "github_repo": "owner/name",
"multiplayer": false, "latest": Release /* or null */, "releases": [Release /* without builds */],
"in_library": true /* only when called with a token */, "in_wishlist": false}
exec rules for installers: for zip/tar.gz builds, extract the archive into the install folder and
run exec (a path relative to the install folder, forward slashes). For exe/appimage builds the
file itself is the program (exec equals filename); on Linux/macOS mark it executable. For macOS
zips, exec points at the .app bundle (Game.app) β open it with open -a. apk is for Android
phones (offer a download), web/gba builds are played at play_url.
Store
GET /home
Everything the launcher's store front page needs in one call.
β 200 {"featured": [GameSummary], "new": [GameSummary], "updated": [GameSummary],
"popular": [GameSummary], "browser": [GameSummary /* playable in the browser */],
"tags": [{"tag": "rpg", "count": 4}], "announcement": ""}
GET /catalog?q=&tag=&platform=&sort=&page=
sort = popular (default) | new | updated | rating | title. 30 per page, page starts at 1.
β 200 {"games": [GameSummary], "page": 1, "pages": 3, "total": 61}
GET /games/{slug}
β 200 GameDetail Β· 404
GET /games/{slug}/latest?platform=windows&arch=x64
Best build for that platform (arch falls back to universal, then any).
β 200 {"version": "1.0.0", "release_id": 9, "build": Build} Β· 404 {"error":"no_build"}
POST /updates/check
{"platform": "windows", "arch": "x64",
"installed": [{"slug": "drift-kings", "version": "0.9.0"}, {"slug": "terald", "version": "1.2.0"}]}
β 200 {"updates": [{"slug": "drift-kings", "from": "0.9.0", "to": "1.0.0", "build": Build, "notes_md": "β¦"}]}
Only games with a newer release that has a build for the platform are listed.
Library π (synced between the website and the launcher)
GET /library
{"games": [{"game": GameSummary, "added_at": "β¦", "last_played_at": "β¦" /* or null */,
"playtime_seconds": 3600, "latest": Release /* with builds, or null */}]}
PUT /library/{slug} β 204 (add) Β· DELETE /library/{slug} β 204 (remove)
PUT /wishlist/{slug} β 204 Β· DELETE /wishlist/{slug} β 204 Β· GET /wishlist β {"games":[GameSummary]}
POST /library/{slug}/played
{"seconds": 1200} β 204. Adds playtime and sets last_played_at (the launcher calls it when a game exits).
Creator API π
GET /creator/games
β {"games": [GameSummary + {"status": "draft|published|unlisted|removed"}]}
POST /creator/games
{"title": "My Game", "slug": "my-game" /*optional*/, "tagline": "", "description_md": "", "tags": ["arcade"],
"ai_tools": ["Claude Code"], "engine": "Godot 4.7"} β 201 GameDetail
PATCH /creator/games/{slug}
Any GameDetail editable field, plus "status": "published". β 200 GameDetail
POST /creator/games/{slug}/uploads
Ask for a direct upload URL for one build file.
{"filename": "MyGame-windows.zip", "size": 123456, "sha256": "hex", "platform": "windows", "arch": "x64"}
β 200 {"upload_id": "u_β¦", "put_url": "https://β¦", "headers": {"Content-Type": "application/zip", "x-amz-acl": "public-read"}, "expires_in": 3600}
PUT the raw file bytes to put_url with exactly those headers.
POST /creator/games/{slug}/releases
{"version": "1.2.0", "notes_md": "What's newβ¦", "channel": "stable", "publish": true,
"builds": [{"upload_id": "u_β¦", "platform": "windows", "arch": "x64", "exec": "MyGame.exe"}]}
β 201 Release. kind is inferred from the file name; exec defaults to the only executable in the archive
or to the file itself. Web builds (platform: "web", a zip with index.html) are unpacked for browser play.
POST /creator/games/{slug}/media (multipart form: kind = cover|header|screenshot, file)
Images are resized server-side: header is cropped to 1280Γ600 (wide, used for cards, the hero and page backgrounds),
cover to 600Γ900 (portrait library art), screenshots fit within 1920Γ1080 with a 480Γ270 thumbnail.
β 201 {"url": "β¦"}
Launcher self-update
GET /launcher/update/{target}/{arch}/{current_version}
Tauri v2 dynamic update server. target = windows|darwin|linux, arch = x86_64|aarch64.
β 204 when up to date, otherwise
200 {"version": "0.2.0", "notes": "β¦", "pub_date": "β¦", "url": "https://cdnβ¦", "signature": "β¦"}
GET /launcher/latest
β 200 {"version": "0.2.0", "pub_date": "β¦", "notes": "β¦", "downloads": {"windows-x64": {"url": "β¦", "size": 1}, "macos-universal": {β¦}, "linux-x64": {β¦}, "linux-x64-deb": {β¦}}}
GitHub
POST /hooks/github/{link_id}: GitHub webhook receiver (release events, HMAC-SHA256 signed). Set up from
the game's GitHub tab on the creator dashboard.
View as Markdown (handy for feeding to an AI)