KUMODeck
日本語

Config reference (kumo.config.json)

Master data for one environment. Push it with kumodeck config push [file] --env <env> or edit it in the dashboard. The whole document is validated before anything changes; errors come back with a JSON path (multiplayer.modes[0].maxPlayers: …). Every section is optional, and every feature that costs money per use or changes what your users (called players in the API) see (external sign-in, sharing tools) is off until you enable it here.

{
  "version": 1,
  "resetTimeZone": "UTC",
  "features": { "saves": true, "hosting": true },
  "multiplayer": { "modes": [] },
  "auth": { "redirectUrls": [], "providers": {} },
  "web": { "allowedOrigins": [] },
  "share": {}
}

Common types#

TypeRule
keylowercase snake_case: ^[a-z][a-z0-9_]{0,47}$. Keys are unique within their section
texta string (1–120 chars) or a map of language code → string: { "en": "Coins", "ja": "コイン" }. Language codes look like en, ja, pt-BR
long textsame as text, up to 500 chars

Top level#

FieldTypeDefault
$schemastring—ignored (for editor tooling)
version11format version
resetTimeZoneIANA zone"UTC"e.g. Asia/Tokyo. Unknown zones are rejected

features#

Every feature is off until you turn it on. Only guest sign-in and reading the config always work. For a feature that is off, players and secret keys get 403 feature_disabled (with details.feature and details.configPath), and nothing is written — so nobody can run up your bill by calling an API your app does not use. The multiplayer WebSocket is closed with 4003 feature_disabled; a hosted app whose hosting is off is not served. Turn features on per environment (development and production have their own config): kumodeck features on <name> edits this file, then kumodeck config push --env <env>. kumodeck features shows what is on.

FeatureWhat it opensNeeds
emailLoginemail sign-up / sign-in / verification / password reset, linking an email to a guest
savescloud saves (kumo.saves)
statssubmitting and reading stats (kumo.stats). The per-minute submit cap (integrity.stats.maxSubmitsPerMinute) always applies
multiplayerrooms, matchmaking, the realtime WebSocket
realtimeChannelsrealtime channels (kumo.realtime): named channels that deliver messages instantly; nothing is stored. The WebSocket connects when either this or multiplayer is on
voicevoice calls in channels created with voice (up to 100 people: peer to peer up to 8, through a relay server from 9; nothing is recorded)realtimeChannels
hostingkumodeck deploy and serving your app on KUMODeck
statsVerificationvalue rules and the verification webhook in integrity.statsstats

Unknown names are rejected on push (a typo would otherwise leave a feature off without telling you). A feature that is not offered right now cannot be turned on (403 feature_unavailable). Banning a user (dashboard, or POST …/players/:playerId/ban) always works; it needs no feature.

Some features keep their own switch elsewhere in this file: sign-in providers → auth.providers.<name>.enabled, the X tools → share.<tool>.enabled. KUMODeck Functions are turned on with kumodeck functions enable.

The dashboard (logged in as you) can always read your data while a feature is off, but using a feature from there (deploying) needs it on, just like from a secret key.

{ "features": { "hosting": true, "saves": true, "multiplayer": true } }

stats[] (max 128)#

FieldTypeDefault
keykeyrequired
titletext—
aggregationsum | max | min | latestsumhow reports combine
maxPerSubmitnumber > 0—largest value accepted in one report (anti-tamper floor)

multiplayer.modes[] (max 16)#

FieldTypeDefault
keykeyrequiredused in rooms.quickMatch(key) / rooms.create(key)
titletext—
minPlayers1–642quick match starts when this many are waiting
maxPlayers1–644must be ≥ minPlayers
fillTimeoutSeconds0–30015start with fewer players after this long; 0 = wait forever
transportserver | p2pserverp2p = players exchange game messages directly over WebRTC (peer-to-peer mode). No referee: do not use it for competitive or prize matches
p2p.maxPlayers2–84mesh size limit; the mode's maxPlayers is capped to it and minPlayers must not exceed it. Joining a full mesh fails with p2p_room_full
p2p.relayOnlybooleantrueroute everyone through the TURN relay so players never see each other's IP address. Players under 13 are always relayed, whatever this says

With no modes at all, a built-in default mode (2–8 players, 15 s) is available. Once you define any mode, only defined modes work.

auth#

Sign-in settings (guide). Guest and email sign-in always work; every external provider is off until you enable it.

FieldTypeDefault
redirectUrlsURL[] (max 32)[]pages of your app allowed as return addresses for email links and OAuth. Exact URL (any query), or a trailing * for a path prefix: https://mygame.example.com/*. Localhost in development and your KUMODeck-hosted URLs are always allowed
providers.google.enabledbooleanfalseSign in with Google (needs your OAuth client in the dashboard)
providers.discord.enabledbooleanfalseSign in with Discord (needs your OAuth client in the dashboard)
providers.apple.enabledbooleanfalseSign in with Apple (needs your Services ID and key in the dashboard)
providers.x.enabledbooleanfalseSign in with X (needs your own X app's Client ID and Secret in the dashboard; where KUMODeck's shared X app is available, Use without setup works too; sign-in is free)

Provider credentials are never stored in this file (it is pushed from your machine and kept in history); they are entered in the dashboard and stored encrypted.

{ "auth": { "redirectUrls": ["https://mygame.example.com/*"], "providers": { "x": { "enabled": true } } } }

web#

FieldTypeDefault
allowedOriginsorigin[] (max 32)[]web origins allowed to use your publishable key. Empty = any origin

Your app can be hosted anywhere: on KUMODeck, on your own server, on itch.io. With the default (empty list) the SDK works from any origin. List origins to stop other sites from using your publishable key (you pay for usage): browser requests from other origins get 403 origin_not_allowed. Your KUMODeck-hosted URL and, in development, localhost are always allowed. Entries are origins without a path: https://mygame.example.com, https://*.itch.zone (any subdomain), http://localhost:5173, capacitor://localhost (your own app).

{ "web": { "allowedOrigins": ["https://mygame.example.com", "https://*.itch.zone"] } }

share#

Tools that help your app or game spread on X (guide). Everything is off by default; turn on only what you use.

FieldTypeDefault
images.enabledbooleanfalserender 1200×600 card images (default card + score / challenge cards)
images.titletext (≤ 60)—app name drawn on the card
images.backgroundpath in your deployment—PNG / JPEG (≤ 2 MB, 2:1 works best) behind the text
images.fontpath in your deployment—extra TTF / OTF (≤ 10 MB). The built-in font covers Latin only; add a font for Japanese etc.
images.theme{ background, accent, text }#0f1115 / #ffcc33 / #ffffff#rrggbb colors
tags.enabledbooleanfalseno longer used: KUMODeck does not add tags to your pages (a push says so in one line). Put the tags from kumodeck share tags in your <head>
tags.title / tags.description / tags.imageAlttext—card text
tags.imagepath in your deployment or https:// URL—fixed card image (otherwise the rendered default card)
tags.site@handle—twitter:site
links.enabledbooleanfalsekumo.share() issues share ids (?ks=) and challenges can be restored
links.texttext (≤ 200)—default post text; {score} are replaced
links.hashtags / links.viastring[] (max 5) / handle[] / —added to the X post
tracking.enabledbooleanfalsecount visits / new users / plays per share id (aggregates only)
inAppBrowser.enabledbooleanfalseallow "open in your browser" as the same guest from X's in-app browser

The card tags need an image: either tags.image or images.enabled.

{
  "share": {
    "images": { "enabled": true, "title": "Space Cats", "background": "/card-bg.png" },
    "tags": { "description": "A tiny space game", "site": "@spacecats" },
    "links": { "enabled": true, "text": "I scored {score} in Space Cats. Can you beat it?", "hashtags": ["spacecats"] },
    "tracking": { "enabled": true }
  }
}

audience#

general (default), mixed or kids: who uses your app or game. kids treats every user as under 13. mixed treats users whose age is unknown as children until they declare an age.

integrity#

Server-side checks for reported stats. They run before a stat is stored; rejected values come back in rejected from POST /v1/stats and are logged as signals (nothing is acted on automatically).

FieldTypeDefault
stats.rules[] (max 128){ stat, min?, max? }[]allowed range of a stat's value (e.g. a best time under 1 s is impossible → min: 1000)
stats.maxSubmitsPerMinute1–60060reports per user per minute
stats.webhook.urlhttps URL—your server decides (http://localhost allowed in development)
stats.webhook.timeoutMs100–50001500
stats.webhook.onFailureaccept | rejectacceptwhat happens when your server is down or slow

The webhook receives POST { type: "stats.verify", environmentId, playerId, stats, submittedAt } with a Kumo-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, t + "." + body)> header (the secret is shown in the dashboard and at GET /v1/admin/integrity/webhook-secret). Answer { "approve": true } or { "results": { "<stat>": true } }.

Other sections#

SectionDefinesAPI

See the REST API for request and response shapes.

Validation rules (cross-references)#

  • Duplicate keys within a section are rejected
  • minPlayers ≤ maxPlayers