SUSHIRO TAIWAN RESEARCH

wwwroot/documents/sushiro/sushiro-api.md 於伺服器端產生。

台灣壽司郎 App API

本文件整理台灣壽司郎 Android App 2.2.4 使用的 CRM API。

僅供自有帳號的互通性研究與測試。這些是未公開 API,可能隨 App 或後端更新而改變。

索引

1. 證據與信心標示

標示 意義
✅ 封包確認 已從官方 App 或等效請求的實際 HTTP request/response 確認
🔬 Live 測試 已對正式環境進行有限、可回復的自有帳號測試
📦 APK 確認 URL 來自 APK environment.json 或程式碼;method/body 未必都有封包佐證

若靜態分析與實際封包不同,以封包結果為準。

2. 基本資訊

Production base URL:
https://crm-tw.akindo-sushiro.co.jp/api/2.0

Region:
TW

App 觀察到的 API User-Agent:

User-Agent: Dart/3.6 (dart:io)

POST body 主要使用:

Content-Type: application/x-www-form-urlencoded; charset=utf-8

不要測試 APK 中的 DEV/UAT 主機;本文件只涵蓋 production 與自有帳號。

3. 驗證模型

API 依路徑分成三層:

路徑 驗證方式 用途
/info/* 通常不需驗證 店舖、時段、座位設定、系統設定
/remote/* guid 或 customer context 登入、即時候位、公開票券狀態
/remote_auth/* HTTP Basic 會員資料、預約訂位、會員票券

remote_auth 的 Basic credential 是:

base64("<email>:<password>")

範例:

Authorization: Basic <REDACTED>

X-SushiroApp 可由官方 App 帶入,但目前 live 測試並未證明它是必要欄位。

4. 核心流程

flowchart LR
    A[取得或保存裝置 GUID] --> B[POST remote/login]
    B --> C[取得 customerid]
    C --> D[查詢店舖、seat config 與 reservation timeslots]
    D --> E[取得目前 FCM registration token]
    E --> F[POST remote_auth/newreservation]
    F --> G[GET remote_auth/opentickets]
    G --> H[POST remote_auth/cancel]
    H --> I[再次查詢 opentickets 確認清空]

5. 已確認 API

通用功能

章節/用途 Method/Endpoint 證據
5.1 登入 POST /remote/login ✅/🔬
5.2 查詢店舖 GET /info/storelist 📦/部分 🔬
5.6 查詢會員票券/預約 GET /remote_auth/opentickets ✅/🔬
5.6 查詢裝置票券 GET /remote/opentickets 📦
5.10 查詢各類候位佇列 GET /remote/groupqueues ✅/🔬
5.10 查詢票號前方組數 GET /remote/storequeuecount ✅/🔬
5.11 綁定/關注店內票券 PUT /remote/watchticket ✅/🔬(E072

預約訂位

章節/用途 Method/Endpoint 證據
5.3 查詢座位設定 GET /info/seatconfig ✅/🔬
5.4 查詢預約時段 GET /info/reservationtimeslots ✅/🔬
5.5 建立預約 POST /remote_auth/newreservation ✅/🔬
5.7 取消預約 POST /remote_auth/cancel ✅/🔬

即時候位

章節/用途 Method/Endpoint 證據
5.8 建立即時候位票券 POST /remote/newticket ✅/🔬
5.9 取消即時候位票券 POST /remote/cancel ✅/🔬

5.1 登入

✅ 封包/🔬 Live 測試

POST /api/2.0/remote/login
Content-Type: application/x-www-form-urlencoded

Body:

email=<EMAIL>&password=<PASSWORD>&region=TW

成功:

{
  "status": "SUCCESS",
  "confirmed": "YES",
  "customerid": "<CUSTOMER_ID>",
  "kana": "",
  "privacyAgree": "true",
  "termsAgree": "true"
}

錯誤帳密通常回 HTTP 401。登入回應不應完整記錄,因為包含 customer ID 與帳號狀態。

5.2 查詢店舖

📦 APK 確認/部分 🔬 Live 測試

GET /api/2.0/info/storelist?guid=<GUID>&region=TW

附近店舖:

GET /api/2.0/info/storelist?latitude=<LAT>&longitude=<LNG>&numresults=<N>&region=TW

5.3 查詢座位設定

✅ 官方 App 封包確認/🔬 Live 成功測試

GET /api/2.0/info/seatconfig?storeid=<STORE_ID>&date=<YYYYMMDD>&region=TW HTTP/2
Host: crm-tw.akindo-sushiro.co.jp
User-Agent: Dart/3.6 (dart:io)

實際捕獲的成功 request 沒有 Authorization。成功回應:

HTTP/2 200 OK
Content-Type: application/json;charset=UTF-8
{
  "seatConfig": 1
}

seatConfig 是 integer;目前只有確認回傳值與型別,尚未確認各數值對應哪些 tabletype 或 UI 配置,不應自行建立未經封包/App 行為驗證的映射。

5.4 查詢預約時段

✅ 官方 App 封包確認/🔬 Live 成功測試

GET /api/2.0/info/reservationtimeslots?storeid=<STORE_ID>&numpersons=<N>&guid=<GUID>&tabletype=<TYPE>&region=TW HTTP/2
Host: crm-tw.akindo-sushiro.co.jp
User-Agent: Dart/3.6 (dart:io)
Authorization: Basic <REDACTED>

官方 App 的成功 request 有帶 Basic Authorization,但目前封包只能證明「有帶」,尚不能證明此 endpoint 在完全省略 Authorization 時一定失敗。

回應範例:

[
  {
    "storeId": "20",
    "date": "20260902",
    "start": "200000",
    "end": "201500",
    "availability": "AVAILABLE"
  }
]

在呼叫 newreservation 前,應先確認目標時段仍為 AVAILABLE

5.5 建立預約訂位

✅ 官方 App 封包確認/🔬 Live 成功測試

POST /api/2.0/remote_auth/newreservation
Authorization: Basic <REDACTED>
User-Agent: Dart/3.6 (dart:io)
Content-Type: application/x-www-form-urlencoded; charset=utf-8

Body:

guid=<GUID>&storeid=<STORE_ID>&adult=<ADULT>&child=<CHILD>&tabletype=<TYPE>&date=<YYYYMMDD>&time=<HHMMSS>&region=TW&googleregistrationid=<FCM_TOKEN>
欄位 型別 說明
guid string App 每次安裝產生並保存的裝置 UUID
storeid string 店舖 ID
adult integer 成人數
child integer 兒童數
tabletype string 座位類型;已知 TCP
date string yyyyMMdd
time string HHmmss
region string 台灣為 TW
googleregistrationid string App 的 FCM registration token

成功回應:

{
  "ticketId": 0,
  "storeId": "20",
  "queueDate": "20260902",
  "queueTime": "200000",
  "number": "<REDACTED>",
  "numAdult": 2,
  "numChild": 0,
  "tableType": "T",
  "wait": 5,
  "status": "WAITING",
  "checkedIn": false,
  "agsQRCodeURL": "",
  "waitingGroup": 0,
  "waitGroup": 0
}

waitwaitingGroupwaitGroup 的精確語意仍未完全確認,應保留後端原值。

googleregistrationid 注意事項

  • 官方 App request 有帶此欄位。
  • 完全省略可能造成 E010/HTTP 500。
  • 後端可能先建立資料,再於推播階段失敗;收到 500 後不可直接盲目重試。
  • 應先查詢 opentickets,確認是否已產生預約,再決定重試或取消。
  • 隨意產生的 Firebase token 可能不屬於壽司郎的 Sender ID,無法可靠接收推播。

5.6 查詢目前票券/預約

✅ 官方 App 封包確認/🔬 Live 成功測試

會員版本:

GET /api/2.0/remote_auth/opentickets?region=TW HTTP/2
Host: crm-tw.akindo-sushiro.co.jp
User-Agent: Dart/3.6 (dart:io)
Authorization: Basic <REDACTED>

成功回應:

HTTP/2 200 OK
Content-Type: application/json;charset=UTF-8

已捕獲的回應頂層包含三個 array。這筆封包同時觀察到 TICKETSRESERVATIONS 各有資料,而 LOCALTICKETS 為空:

{
  "TICKETS": [
    {
      "TICKET_DETAIL": {},
      "STORE_INFO": {}
    }
  ],
  "LOCALTICKETS": [],
  "RESERVATIONS": [
    {
      "TICKET_DETAIL": {},
      "STORE_INFO": {}
    }
  ]
}

頂層語意:

欄位 型別 本次觀察
TICKETS array 即時候位票券;捕獲到 MISSED 狀態
LOCALTICKETS array 本次為空,因此元素 schema 尚未確認
RESERVATIONS array 預約訂位;捕獲到 WAITING 狀態

每個已觀察到的 array item 都包含 TICKET_DETAILSTORE_INFO

TICKET_DETAIL schema

欄位 型別 說明/觀察
ticketId integer 後端票券 ID;敏感值
storeId string 店舖 ID;注意此處不是 integer
queueDate string yyyyMMdd
queueTime string HHmmss
number string 顯示票號;可能有前導零
numAdultnumChild integer 成人與兒童人數
tableType string 座位類型
wait integer 等待值;精確語意仍待確認
status string 已觀察到 WAITINGMISSED
checkedIn boolean 是否已 check-in
agsQRCodeURL string QR code URL;本次為空字串
waitingGroupwaitGroup integer 已觀察到 01-1;精確語意仍待確認
startend string RESERVATIONS 觀察到的預約起訖時間,格式 HHmmss;即時票券未出現

STORE_INFO schema

同一份店舖資料會附在每張票券內。這筆封包觀察到的欄位型別:

型別 欄位
integer idsortOrderwaitwaitTimeCounterwaitTimeCapcancellationMobileMinutescancellationReservationMinutestablesCapacitycountersCapacity、所有 minCustomers*maxCustomers*waitingGroupseatConfigwaitShowTypewaitingGroupTablewaitingGroupCounterwaitingGroupPair
number latitudelongitude
string storeStatusnamenameKananameEnaddressareatimezonedistancenetTicketStatusremoteTicketingManualStatusreservationStatuscheckinStatuscommencementDatelocalTicketingStatusclientVersionregion
boolean requireNetTicketLoginforceLocalModecounterReservationsAllowedisAgspairReservationsAllowedshowCheckinCodeshowCheckinCodeDialog
nullable openDate;本次為 null

值得注意:TICKET_DETAIL.storeId 是 string,但 STORE_INFO.id 是 integer;STORE_INFO.distance 在本次封包也是 string。資料模型應依實際型別解析,不要因欄位語意相近而強制共用型別。

裝置版本:

GET /api/2.0/remote/opentickets?guid=<GUID>&region=TW

建立預約或即時候位前應先檢查三個陣列,避免覆蓋使用者既有票券。取消預約後確認 RESERVATIONS 已清空;取消即時候位後則確認對應項目已從 TICKETSLOCALTICKETS 消失。

5.7 取消預約訂位

✅ 官方 App 封包確認/🔬 Live 成功測試

POST /api/2.0/remote_auth/cancel
Authorization: Basic <REDACTED>
User-Agent: Dart/3.6 (dart:io)
Content-Type: application/x-www-form-urlencoded; charset=utf-8

Body:

guid=<GUID>&region=TW

實際捕獲的 request 沒有

ticketId
storeId
date
time
number

後端似乎依 Basic 帳號、guidregion 找出有效預約。不要自行添加未經證實的 ticketId

成功回應:

{
  "status": "SUCCESS"
}

5.8 即時候位取號

✅ 官方 App 封包確認/🔬 Live 成功測試

POST /api/2.0/remote/newticket HTTP/2
Host: crm-tw.akindo-sushiro.co.jp
User-Agent: Dart/3.6 (dart:io)
Content-Type: application/x-www-form-urlencoded; charset=utf-8

實際捕獲的成功 request 沒有 Authorization: Basic。Body:

guid=<GUID>&storeid=20&adult=2&child=0&tabletype=T&region=TW&googleregistrationid=<URL_ENCODED_FCM_TOKEN>&activecustomerid=<CUSTOMER_ID>

已確認的 body 欄位:

欄位 範例/型別 說明
guid UUID string App/裝置識別值
storeid 20 店舖 ID
adult 2 成人數量
child 0 兒童數量
tabletype T 座位類型;實際可用值依店舖設定
region TW 區域
googleregistrationid URL-encoded string FCM registration token;例如 token 中的 : 會編碼成 %3A
activecustomerid integer-like string 目前登入會員的 customer ID

早期測試未帶 googleregistrationid 時持續得到 E010activecustomerid 在欄位驗證測試中不是語法上的強制欄位,但官方 App 的成功會員流程會帶入;重現正常流程時不應任意省略。

成功回應:

HTTP/2 200 OK
Content-Type: application/json;charset=UTF-8

以下已將 ticket ID、票號與精確日期時間去敏;欄位與型別來自實際回應:

{
  "ticketId": 0,
  "storeId": "20",
  "queueDate": "<YYYYMMDD>",
  "queueTime": "<HHMMSS>",
  "number": "<QUEUE_NUMBER>",
  "numAdult": 2,
  "numChild": 0,
  "tableType": "T",
  "wait": 5,
  "status": "WAITING",
  "checkedIn": false,
  "agsQRCodeURL": "",
  "waitingGroup": 0,
  "waitGroup": 1
}

其中 ticketId: 0 是去敏佔位值,實際型別為 integer。其他觀察:

  • storeIdnumberqueueDatequeueTime 是 string;number 可能有前導零,不應轉成 integer。
  • 新建立的票券狀態為 WAITING,此例 checkedInfalse
  • waitwaitingGroupwaitGroup 是建立當下的快照;三者的精確業務定義仍待確認。
  • agsQRCodeURL 在這筆建立票券回應中是空字串,不能據此假設所有狀態都為空。

5.9 取消即時候位取號

✅ 官方 App 封包確認/🔬 Live 成功測試

POST /api/2.0/remote/cancel HTTP/2
Host: crm-tw.akindo-sushiro.co.jp
User-Agent: Dart/3.6 (dart:io)
Content-Type: application/x-www-form-urlencoded; charset=utf-8

Body:

guid=<GUID>&region=TW

實際捕獲的成功 request 沒有

Authorization
ticketId
storeId
number
activecustomerid
googleregistrationid

成功回應:

HTTP/2 200 OK
Content-Type: application/json;charset=UTF-8
{
  "status": "SUCCESS"
}

後端似乎依 guidregion 與目前有效的即時候位狀態找到要取消的票券。這是根據成功封包作出的推論;不要自行加入未經證實的 ticketId 或其他欄位。

此 endpoint 與預約訂位取消不同:

用途 Endpoint Basic Authorization
取消即時候位 POST /remote/cancel 實際封包沒有
取消預約訂位 POST /remote_auth/cancel 實際封包有

5.10 店舖候位狀態

✅ 官方 App 封包確認/🔬 Live 成功測試

5.10.1 各類候位佇列

GET /api/2.0/remote/groupqueues?storeid=<STORE_ID>&region=TW HTTP/2
Host: crm-tw.akindo-sushiro.co.jp
User-Agent: Dart/3.6 (dart:io)

實際捕獲的成功 request 沒有 Authorization。成功回應:

{
  "boothQueue": [],
  "counterQueue": [],
  "mixedQueue": [],
  "reservationQueue": [],
  "reservationCounterQueue": [],
  "reservationBoothQueue": [],
  "storeQueue": [],
  "storeCounterQueue": [],
  "storeBoothQueue": [],
  "separateQueue": 0
}

這筆回應中的九個 *Queue 欄位都是 array,separateQueue 是 integer。由於捕獲時所有 array 都是空的,目前尚未確認各 array 元素的 schema,也不能從單一空結果推斷它們永遠為空。

5.10.2 特定票號前方組數

GET /api/2.0/remote/storequeuecount?storeid=<STORE_ID>&ticketNo=<QUEUE_NUMBER>&region=TW HTTP/2
Host: crm-tw.akindo-sushiro.co.jp
User-Agent: Dart/3.6 (dart:io)

實際捕獲的成功 request 同樣沒有 Authorization。成功回應是 JSON number primitive,不是 object:

HTTP/2 200 OK
Content-Type: application/json;charset=UTF-8
0

此例回傳 integer 0。依 endpoint 用途推測它代表指定票號前方的等待組數,但是否包含正在叫號/過號票券仍待 App 行為或更多封包確認。ticketNo 應沿用 App/後端提供的值,不要自行轉型而遺失可能的前導零。

官方 App 的候位查詢輪詢間隔約為 60 秒;不要高頻輪詢。

5.11 綁定/關注店內票券

✅ 官方 App 封包確認/🔬 Live 錯誤路徑確認

依 endpoint 名稱與 request schema 推測,此 API 用於把既有店內號碼牌加入 App 的關注/追蹤狀態。Method 已由封包確認為 PUT,不是 endpoint 索引先前推測的 POST

PUT /api/2.0/remote/watchticket HTTP/2
Host: crm-tw.akindo-sushiro.co.jp
User-Agent: Dart/3.6 (dart:io)
Content-Type: application/x-www-form-urlencoded; charset=utf-8

實際捕獲的 request 沒有 Authorization。Body:

guid=<GUID>&region=TW&googleregistrationid=<URL_ENCODED_FCM_TOKEN>&storeid=<STORE_ID>&ticketnum=<TICKET_NUMBER>
欄位 型別 說明
guid UUID string App/裝置識別值
region string 台灣為 TW
googleregistrationid URL-encoded string FCM registration token
storeid string 號碼牌所屬店舖 ID
ticketnum string 店內號碼牌;應保留可能的前導零

以不存在/無效票號測試時,回應:

HTTP/2 400 Bad Request
Content-Type: application/json;charset=UTF-8
{
  "message": "查無此號碼牌號碼,請再次確認您手中的號碼牌\n\nPlease check your ticket number.",
  "code": "E072"
}

這筆封包只確認 request schema 與 E072 錯誤路徑,尚未確認有效票號的成功 response。不要為了探測號碼而批次嘗試 ticket number。

6. APK 內的完整端點索引

以下 URL 來自 APK 設定。沒有逐一列出 body 的端點,仍需以實際封包確認 method、欄位名稱與必要性。

/info 公開資訊

用途 Endpoint 證據
店舖列表/搜尋 GET /info/storelist 📦/部分 🔬
單店資料 GET /info/store 📦
多店資料 GET /info/multiplestoredetails 📦
預約時段 GET /info/reservationtimeslots ✅/🔬
座位設定 GET /info/seatconfig ✅/🔬
App/CRM 設定 GET /info/config 📦

/remote 裝置或一般會員流程

用途 Endpoint 證據
登入 POST /remote/login ✅/🔬
建立帳號 POST /remote/createaccount 📦
忘記密碼 POST /remote/requestreset 📦
變更 Email POST /remote/applychangeemailaddress 📦
即時候位取號 POST /remote/newticket ✅/🔬
綁定/關注店內票券 PUT /remote/watchticket ✅/🔬(E072
取消即時候位 POST /remote/cancel ✅/🔬
裝置票券狀態 GET /remote/opentickets 📦
各類候位組數 GET /remote/groupqueues ✅/🔬
特定票號等待數 GET /remote/storequeuecount ✅/🔬
店舖叫號狀態 GET /remote/storequeue 📦
我的最愛列表 GET /remote/favoritestore 📦
新增最愛 POST /remote/createfavoritestore 📦
刪除最愛 POST /remote/deletefavoritestore 📦
優惠券 GET /remote/couponlist 📦
Maido/外部服務 SSO GET /remote/autologinurl 📦
隱私政策同意 POST /remote/updatepolicyagree 📦
使用條款同意 POST /remote/updatetermsagree 📦

/remote_auth HTTP Basic 會員流程

用途 Endpoint 證據
會員資料 GET /remote_auth/accountdetails 🔬
更新會員資料 POST /remote_auth/updateaccount 🔬
變更密碼 POST /remote_auth/changepwd 🔬
重寄確認信 POST /remote_auth/requestconfirmation 📦
刪除帳號 POST /remote_auth/removeaccount 📦
建立預約訂位 POST /remote_auth/newreservation ✅/🔬
取消預約訂位 POST /remote_auth/cancel ✅/🔬
會員票券狀態 GET /remote_auth/opentickets ✅/🔬
發送 SMS 驗證碼 POST /remote_auth/sendsmsauthenticationcode 📦
驗證 SMS 驗證碼 POST /remote_auth/checksmsauthenticationcode 📦
SMS 驗證狀態 GET /remote_auth/smsverificationstatus 📦

7. 已知錯誤

HTTP/Code 意義 建議處理
401 登入或 Basic credential 無效 重新登入;不要重複高速嘗試
E010/500 後端一般錯誤;曾由缺少或無法使用的推播 token 觸發 先查 opentickets,避免重複建立
E011 欄位值無效,例如未知 tabletype 修正請求,不要原樣重試
E012 缺少必要欄位 property 修正 body
E052 已存在預約/重複限制 查詢現有預約,不要再次建立
E072/400 watchticket 查無指定店內號碼牌 核對店舖與票號;不要批次枚舉
E096/503 台灣時間約 02:00–06:00 系統維護 維護時間後再試;不是 proxy/TLS 故障

E096 範例:

{
  "message": "目前為系統維修時間,請稍候再嘗試使用(2:00~6:00)\nYou cannot use the system at this time(2:00~6:00)",
  "code": "E096"
}

8. 安全與操作規則

下列資料視為敏感資訊:

email / password
Authorization: Basic ...
guid
customerid
googleregistrationid / FCM token
ticketId / number
  • 不得提交到 Git。
  • 不得輸出至一般 application log、Burp 匯出範例或錯誤頁面。
  • 使用環境變數、secret store 或受保護的本機設定。
  • 建立預約前先查 opentickets,避免覆蓋或取消原有訂位。
  • 測試預約建立後應立即取消,並再次確認 RESERVATIONS 為空。
  • 輪詢至少維持官方 App 約 60 秒的間隔。
  • 不進行大量取號、搶位或非自有帳號操作。

9. 尚待捕獲

  • check-in request 與 QR code 流程
  • WAITINGMISSED 以外的完整 ticket/reservation status enum
  • waitwaitingGroupwaitGroup 精確語意
  • groupqueues 非空 array 的元素 schema 與各 queue 欄位的精確分類規則
  • storequeuecount 是否包含正在叫號、過號或其他特殊狀態
  • watchticket 使用有效店內票號時的成功 response schema
  • TCP 在台灣各店的實際對應座位名稱
  • 預約歷史
  • SMS 驗證各種錯誤回應
  • FCM token 失效、Sender ID 不符時 CRM 的完整錯誤映射

10. 工作區參考資料