TSUMU SUPPORTゲームへ
SERVER INTEGRATION

あなたのサーバーを、
TSUMUにつなぐ。

iPhoneアプリの「接続先URL」に指定できるサーバーを作るための、公開APIガイドです。認証からゲームデータまで、アプリが期待する通信仕様をまとめました。

01

接続先を入力

iPhoneのログイン画面で、対応サーバーのHTTPS URLを入力します。

02

Discordで認証

サーバーが本人確認し、iPhoneへ一回限りのコードを返します。

03

共通データでプレイ

同じセッションでツム・設定を取得し、スコアや所持データをやり取りします。

遊ぶだけなら、サーバーは不要。「ログインせずに始める」で標準6種類・標準スキルをオフラインで利用できます。ゲストはランキング送信・保存・ガチャを利用しません。このAPIはDiscordログイン時に使います。
01 / CONNECTION

接続先に必要なもの

初期接続先は https://tsumu.tarohida.jp。別サーバーを使う場合は、以下のAPIとDiscord OAuthを実装してください。単にDiscord対応のサイトを入力しても接続できません。

接続先URLの例
https://games.example.com
https://games.example.com:8443
  • HTTPSと、iPhoneが信頼するTLS証明書。HTTPや証明書エラーを回避する設定はありません。
  • オリジン単位のURL。ホストと任意のポートを指定します。パス、ユーザー名・パスワード、クエリ、フラグメントは指定できません。末尾の / は除去、スキーム省略時はHTTPSを補完、443番ポートは省略形に正規化します。
  • API・画像・音声を同じオリジンで配信。スキーム・ホスト・ポートが一致しないURLや、別オリジンへのHTTPリダイレクトはiPhoneが拒否します。外部CDNのURLをそのまま返さないでください。
  • JSONを正しい型で返す。キーの大文字小文字も契約の一部です。数値を文字列にしないでください。エラーをHTTP 200のHTMLで返すことも避けます。

実装するエンドポイント

区分メソッド / パス役割
認証GET /auth/login
GET /auth/callback
ブラウザでDiscord認証
認証POST /auth/token
POST /auth/logout
コード交換・セッション失効
必須GET /api/game-dataiPhoneの初期データ一括取得
共通機能POST /api/rankings
POST /api/gacha/pull
スコア保存・ガチャ
条件付きPOST /api/skills/resolveカスタムスキルを割り当てる場合
Web・個別取得GET /api/me
GET /api/rankings
GET /api/user-tsums
ユーザー・ランキング・所持数
Web互換GET /api/tsums
GET /api/settings
GET /api/stages/active
GET /api/fan-btn
設定等の個別取得

iPhone 2.2は一覧・残高・設定を /api/game-data から取得します。/api/settings を除くゲームAPIは認証必須。管理API、スキル作成UI、旧Android専用認証はこの接続仕様の対象外です。

02 / AUTHENTICATION

Discord認証とセッション

DiscordのアクセストークンをiPhoneへ返すのではありません。本人確認はサーバーが行い、そのサーバー専用のTSUMUセッションを発行します。Discordのclient secretはサーバー内だけに置きます。

  1. iPhone → サーバー

    システム認証ブラウザで /auth/login?client=ios&code_challenge=…&client_state=… を開きます。

  2. サーバー → Discord → サーバー

    独立したランダムstateを保存し、oauth_state Cookieにも設定。Discordへリダイレクトし、/auth/callback で両方のstateを検証します。

  3. サーバー → iPhone

    成功時はHTTP 302で tsumu://auth?code=ONE_TIME_CODE&state=CLIENT_STATE へ。URLにセッショントークンを含めません。

  4. iPhone → サーバー

    POST /auth/token にコードとverifierを送信。交換したセッションをKeychainへ保存し、その後のAPIにBearerとして付けます。

1. GET /auth/login

パラメーターiOSでの条件
clientios を指定。省略時はWeb認証。
code_challenge43文字のBase64URL(A–Z / a–z / 0–9 / _ / -)。BASE64URL(SHA256(code_verifier))、パディングなし。
client_state32〜128文字のBase64URL文字集合。アプリが発行し、アプリへのコールバックで同じ値を返します。

S256固定です。code_challenge_method は送信しません。サーバー側stateは別に生成し、Cookieと紐づけて有効期間300秒・一回限りとします。Cookieは HttpOnly; Secure; SameSite=Lax。通常の成功応答はDiscordへのHTTP 307です。

サーバーのDiscord設定DiscordアプリのRedirect URIを https://あなたのホスト/auth/callback に一致させます。現行バックエンドでは DISCORD_CLIENT_ID、DISCORD_CLIENT_SECRET、DISCORD_REDIRECT_URI を設定し、identify スコープで本人確認します。ALLOWED_USERNAMES がある場合はそのアクセス制限も適用されます。リバースプロキシ配下でも外向きHTTPSを正しく認識させてください。

2. GET /auth/callback

Discordからの code と state を受け、Cookieのstate・サーバー保存のstateを照合して消費します。Discordユーザーを許可できたらTSUMUセッションと一回限りコードを発行します。

iOSへの成功リダイレクト
HTTP/1.1 302 Found
Location: tsumu://auth?code=ONE_TIME_CODE&state=ORIGINAL_CLIENT_STATE
Cache-Control: no-store
Referrer-Policy: no-referrer

認証拒否は tsumu://auth?error=access_denied&state=…、Discord通信エラーは error=discord_error を返します。state自体が不正な場合はアプリへ戻さず、/login.html?error=invalid_state へリダイレクトします。Web認証は session Cookieを発行して / へ戻します。

3. POST /auth/token

リクエスト · Content-Type: application/json
{
  "code": "SERVER_ISSUED_ONE_TIME_CODE",
  "code_verifier": "APP_GENERATED_VERIFIER_AT_LEAST_43_CHARACTERS_LONG"
}
200 OK · レスポンス
{
  "access_token": "TSUMU_SESSION_TOKEN",
  "token_type": "Bearer",
  "expires_in": 604800
}

code は20〜128文字、code_verifier は43〜128文字で A–Z a–z 0–9 . _ ~ - のみ。一回限りコードは60秒で失効し、正しいverifierと交換できた時だけ消費します。期限切れ・不一致・再使用はHTTP 400。セッションは作成から7日で失効し、refresh tokenはありません。

4. 認証済みAPI / ログアウト

iPhoneのリクエストヘッダー
GET /api/game-data HTTP/1.1
Authorization: Bearer TSUMU_SESSION_TOKEN
Accept: application/json

Webは同じセッションをHttpOnly Cookieで送ります。BearerとCookieを同じユーザー検証に接続し、Bearerが不正なときにCookieへフォールバックしないでください。個人データと認証応答には Cache-Control: no-store を付けます。

POST /auth/logout は現在のセッションを失効して {"status":"ok"} を返します。未認証でも同じ応答です。HTTP 401を受けたiPhoneは認証情報を破棄してログイン画面へ戻ります。保存するトークンは接続先ごとに分離されます。

03 / GAME DATA

GET /api/game-data

認証後に最初に読むスナップショットです。以下は架空の値による例です。/uploads/sample.png は例示URLで、ファイルを提供しているわけではありません。最小構成はデフォルトプリセットのJSONを参照してください。

JSON
{
  "version": 1,
  "user": {
    "discord_id": "demo-user",
    "username": "サンプル",
    "jewels": 100,
    "bestScore": 1200,
    "gamesPlayed": 3
  },
  "tsums": {
    "overrides": {
      "mickey": {
        "name": "サンプルツム",
        "imageUrl": "/uploads/sample.png",
        "skillId": "skill_example",
        "skillName": "タイムプラス"
      }
    }
  },
  "settings": {
    "combo_timeout": 3000,
    "game_duration": 60,
    "fever_duration": 8000,
    "fever_multiplier": 3,
    "min_chain": 3,
    "bomb_chain_threshold": 7,
    "connect_gap_tolerance": 26
  },
  "stage": null,
  "fan": {
    "url": null,
    "color": "#4fc3f7"
  },
  "collection": {
    "mickey": 2
  },
  "rankings": [
    {
      "name": "サンプル",
      "score": 1200,
      "tsumUsed": "mickey",
      "createdAt": 1790000000
    }
  ]
}
キー型 / 意味
version整数。現行レスポンスは 1。iPhone 2.2はバージョン交渉を行いません。
userdiscord_id・username は文字列、jewels・bestScore・gamesPlayed は整数。5項目必須。プレイ数は保存済みランキングの件数です。
tsumsoverrides オブジェクト必須。未設定なら {}。ツムIDごとに name / imageUrl / skillId / skillName を文字列で任意指定。未設定は省略またはnull。
settings数値の辞書。欠けた設定はiPhoneの標準値を使用。文字列の数値は不可。
stage未設定は null。設定時は文字列またはnullの値からなるオブジェクト。詳細は素材の節。
fancolor 文字列必須、url は画像URLまたはnull。iPhone 2.2は画像を使用し、色の指定はWeb向けです。
collectionツムID → 所持数(整数)の辞書。未所持は省略可。所持数によるプレイ制限は現在ありません。
rankings配列。各要素の name は文字列、score は整数、createdAt はUTC Unix秒(数値)。tsumUsed はツムIDまたはnull。スコア降順。

トップレベルの8キーは常に返してください。追加の未知フィールドはiPhoneで無視されます。skillCode / skillType が含まれていても、iPhoneはコードを実行せず skillId の有無でカスタムスキルを判定します。

標準ツムIDと typeIndex

typeIndexツムID標準名
0mickeyミッキー
1donaldドナルド
2goofyグーフィー
3plutoプルート
4daisyデイジー
5minnieミニー

名前・画像は変更できますが、IDとインデックスの順序は固定です。ツムの追加種類や基本スコアの変更をこのAPIだけで行うことはできません。

settings の単位と標準値

キー標準値単位 / 役割
game_duration60秒 / プレイ時間
combo_timeout3000ミリ秒 / コンボ期限
fever_duration8000ミリ秒 / フィーバー時間
fever_multiplier3倍率
min_chain3個 / 消去に必要なチェーン長
bomb_chain_threshold7個 / ボム生成チェーン長
connect_gap_tolerance26Web論理座標の距離 / 接続許容間隔

iPhoneでは安全のため、プレイ時間1〜86400秒、倍率1〜100、チェーン条件2〜50、接続許容間隔0〜300に丸めます。コンボ・フィーバー時間の下限は100msです。互換性を保つには標準値から始めてください。

04 / SAVE & COLLECTION

ランキングとガチャ

POST /api/rankings

スコア保存リクエスト
{
  "name": "サンプル",
  "score": 1200,
  "tsum_used": "mickey",
  "request_id": "11111111-1111-4111-8111-111111111111"
}

name は1〜20文字、score は0〜2,147,483,647の整数。tsum_used は標準ツムIDまたはnullです。成功時は {"status":"ok"} をHTTP 200で返します。現行実装はクライアント申告のスコアを保存し、サーバーによるプレイ内容の再検証はしません。

再試行しても、1回だけ保存。request_id はUUID。同じユーザーとIDの組み合わせでは最初の記録だけを保存します。同じIDで名前やスコアを変更しても更新されません。サーバーでは省略可能ですが、互換クライアントは必ず付けて再送時に同じ値を使ってください。

POST /api/gacha/pull

ガチャリクエスト
{
  "request_id": "22222222-2222-4222-8222-222222222222"
}
200 OK · レスポンス
{
  "tsumId": "donald",
  "jewelsRemaining": 0
}

1回100ジュエル、標準6種類から均等に選択、重複あり。残高減算・所持追加・処理済みIDの保存は1トランザクションで行います。残高不足はHTTP 400です。

同じユーザーと request_id の再送では二重消費せず、同じ tsumId を返します。jewelsRemaining は再送した時点の現在残高です。未確定IDは結果が判明するまで保存し、新しいガチャ操作にだけ新しいIDを発行してください。

スコア保存だけでジュエルは増えません。ゲストや旧オフライン版の残高・スコアはサーバーへ移行・合算しません。GET /api/user-tsums は {"mickey":2,"donald":1} のような辞書を返します。

一覧の個別取得

GET /api/me はスナップショットの user と同じ形式。GET /api/rankings は rankings と同じ配列で、現行APIはページングなしです。全件をスコア降順で返します。

05 / CUSTOM SKILLS

POST /api/skills/resolve

ツムに skillId を割り当てる場合に必要です。iPhoneは盤面を送信し、サーバーが返すアクションをネイティブで実行します。リクエストにJavaScriptコードを含めず、サーバー側でそのツムに割り当てられたスキルを選びます。

盤面リクエスト
{
  "myTsumIndex": 0,
  "active": [
    {
      "id": "piece-1",
      "typeIndex": 1,
      "body": {
        "x": 120,
        "y": 300
      }
    },
    {
      "id": "piece-2",
      "typeIndex": 0,
      "body": {
        "x": 170,
        "y": 330
      }
    }
  ]
}

active はボムを除く最大100個。id は1〜80文字で配列内で一意、typeIndex / myTsumIndex は0〜5の整数。座標は有限の数値(受理範囲−1000〜2000)です。左上原点、xは右向き、yは下向き。プレイエリアは {x:38,y:170,width:299,height:410}。iPhoneの盤面座標はこの論理座標に変換して送られます。

200 OK · 順番に適用するアクション
{
  "actions": [
    {
      "type": "time",
      "value": 3
    },
    {
      "type": "boost",
      "value": 5000
    },
    {
      "type": "clear",
      "ids": [
        "piece-1"
      ]
    }
  ]
}
type値意味
clearids: string[]該当ツムを消去し、通常の消去スコアを加算
transformids: string[]該当ツムを今回のマイツムに変更
bombx, y: number指定座標にボム生成。x: 0〜375、y: 0〜1000
timevalue: 0〜3600残り時間を追加(秒)
scorevalue: 0〜1,000,000スコアを直接加算
boostvalue: 0〜3,600,000スコア2倍の継続時間(ミリ秒)
fever追加フィールドなし未発動ならフィーバーを開始。このアクション自体では時間ボーナスを加えない

アクションは最大64個、対象IDはリクエストの盤面に存在するものだけを返します。通信中は両クライアントの盤面・タイマーを停止。成功した時だけゲージを消費し、失敗時は再試行できます。このAPIにはrequest_idによる同一結果の再送保証はありません。

既存バックエンドのスキル実装方式

制限付きQuickJSで割り当て済みコードを評価します。CPU 50ms・メモリ8MB・スタック256KB。ネットワーク、ファイル操作、Pythonコールバックは提供しません。

ctx.clearTsums / transformTsums / spawnBomb / addTime / addScore / setScoreBoost / activateFever と、getTsumsInArea / getTsumsInRect / randomTsums / getMyTsums / getNonMyTsums を提供します。戻り値形式の clear / transform / score_boost も互換対応しています。

別サーバーでQuickJSを採用する必要はありません。同じ入力とアクション契約を守れば、サーバー側実装言語は自由です。

06 / ASSETS

画像・音声の配信

URLは /uploads/your-file.png のようなルート相対URLを推奨します。同一オリジンの絶対URLも利用できます。素材が認証必須なら、APIと同じBearer / Cookieを受け付けてください。HTTP 200で素材のバイト列を返し、ログインHTMLへ転送しないでください。

stage の例
{
  "id": "stage_example",
  "name": "サンプルステージ",
  "bgUrl": "/uploads/background.png",
  "bgmUrl": "/uploads/music.m4a",
  "seconnectUrl": null,
  "seclearUrl": "/uploads/clear.wav",
  "sebombUrl": null,
  "seskillUrl": null,
  "sefeverUrl": null,
  "secomboUrl": null,
  "seresultUrl": null
}

SEキーは大文字小文字に注意してください。たとえば seclearUrl であって seClearUrl ではありません。未設定はnull。画像はPNG / JPEG、音声はWAV / AAC(M4A)を推奨します。Web用にアップロード可能な形式でも、iOSのUIImage / AVAudioPlayerがデコードできる形式である必要があります。

素材取得に失敗した場合は標準画像・同梱音声にフォールバックし、再読み込み案内を表示します。iPhoneの通信タイムアウトはリクエスト20秒・リソース全体40秒です。ログインURLのブラウザ遷移はこのAPI通信設定とは別です。

07 / TROUBLESHOOTING

困ったときは

症状 / ステータス確認すること
接続先URLを受け付けないHTTPSか、パス・クエリ・認証情報が含まれていないかを確認。ポートは1〜65535。
Discord認証後に戻れないRedirect URI、oauth_state Cookie、stateの300秒期限、tsumu://auth とclient_stateを確認。TLS・プロキシ設定も確認。
400 / コード交換失敗認可コードの60秒期限、再使用、verifierの不一致。ログインをやり直す。
401 / ログインし直してくださいTSUMUセッションの失効・未送信・接続先違い。Discordアクセストークンを送っていないか確認。
404 / カスタムスキルなしskillId とサーバーの割り当てを一致させる。スキル未設定ならskillIdを省略またはnullにする。
422 / 入力・スキルエラー名前・UUID・整数範囲・盤面を確認。スキルならコードの例外・実行時間・メモリ制限を確認。
正しいデータを取得できないHTTP 200の本文がJSONか、必須キー・型・nullが仕様通りか確認。
画像や音声だけ読み込めない同一オリジン、Bearer対応、HTTP応答、実ファイル形式、ファイルサイズと通信時間を確認。
ガチャ残高が足りないサーバー残高が100以上必要。ゲストや以前の端末内残高は使わない。
標準的なエラー本文
{"detail":"Unauthorized"}

通常の detail は文字列ですが、FastAPIの入力検証エラー(422)では配列になります。サーバーのログにclient secret、Bearer、Cookie、認可コード、verifierを記録・公開しないでください。スクリーンショットを共有するときも伏せてください。

08 / VERIFICATION

公開前のチェックリスト

  • HTTPSオリジンを入力し、Discordからアプリへ戻れる。
  • stateの不一致・期限切れ・再使用を拒否できる。
  • コードの再交換・誤ったverifierを拒否できる。
  • BearerとWeb Cookieが同じアカウント・残高を参照する。
  • 未認証のゲームAPIはHTMLでなく401 JSONを返す。
  • 最小構成のgame-dataで標準ツムのゲームが始まる。
  • 同じrequest_idのスコア送信が1件だけ保存される。
  • 同じrequest_idのガチャが1回だけ消費される。
  • 別オリジンに認証情報を送らず、素材を正しく読み込める。
  • ログアウト後、古いトークンがゲームAPIで使えない。
テストは検証用アカウント・サーバーで。スコア保存とガチャは実データを変更します。とくにガチャは実際にジュエルを消費します。本番の残高で試さないでください。
09 / RESOURCES

実装に使えるファイル

このページは2026年9月22日時点のiPhone 2.2 / Client API v1の実装に対応しています。OpenAPIの文書バージョンは1.0.0。実際の互換対象は game-data.version = 1 です。管理画面を含むサーバー全APIを公開するSwaggerではなく、クライアント接続に必要な契約を抜き出した文書です。