API設計
API概要
| Base URL | https://api.maruyama-coffee.example.com/v1 |
| プロトコル | HTTPS のみ |
| フォーマット | Content-Type: application/json(リクエスト・レスポンス共通) |
| 認証 | JWT Bearer Token — Authorization: Bearer <token> |
| バージョニング | URL パスによるバージョン管理(/v1/) |
| 文字コード | UTF-8 |
認証スキーム
全エンドポイント(/auth/login および /auth/refresh を除く)は
JWT Bearer Token が必要です。
トークンは POST /auth/login で取得し、
リクエストヘッダに付与します。
なお、ブラウザからは要件 NF-S003 のとおりトークンを直接保持せず、Next.js プロキシが
HTTPOnly Cookie(pos_access / pos_refresh)を
Bearer ヘッダに変換して本 API へ転送します(本ドキュメントの JSON ボディ例は Worker API 単体の入出力)。
POST /auth/refresh で新しい Access Token を取得。
ページネーション / レスポンス形式
一覧系 GET は limit / offset
クエリパラメータによるオフセットページネーションを使用します。省略時のデフォルトは limit=50&offset=0。
エラーレスポンス(RFC 7807 準拠)
全エラーは RFC 7807 Problem Details 形式で返します。詳細は エラー一覧 を参照。
JWT 二重トークン構成(Access + Refresh)
Access Token は短命(1h)にして万一の漏洩時リスクを最小化。
Refresh Token(30d)はサーバーサイドのブラックリストで即時無効化可能とし、ログアウトの確実性を担保。
POST /auth/logout は Refresh Token をブラックリストに追加し、
以降の /auth/refresh 呼び出しを無効にする。
関連要件: AUTH-005 NF-S003
注文アイテムのバルク登録
POST /visitors/:visitor_id/orders は
items[] 配列を受け取り、
ORDER レコードと全 ORDER_ITEM をひとつのトランザクションで生成する。
1 アイテムずつ N 回 POST する設計は避け、ネットワークラウンドトリップを最小化する。
関連要件: POS-021 POS-022 UC-06
BILL 精算のアトミック処理
POST /visitors/:visitor_id/bills は単一 D1 トランザクションで
①BILL 生成・②BILL_ITEM スナップショット作成・③VISITOR ステータスを checked_out に更新
の3操作を行う。途中失敗時はロールバックし、二重請求を防ぐ。
BILL_ITEM には精算時点の product_name / unit_price をコピーする(スナップショット設計)。
関連要件: BILL-014 BILL-015 UC-11
401 と 403 の使い分け
401 Unauthorized は「誰だか不明」— トークン未付与・形式不正・期限切れのいずれか。
403 Forbidden は「誰かは分かるが権限がない」— 認証済みスタッフが
role: manager 限定エンドポイントを呼んだ場合。
403 応答には required_role フィールドを含め、フロントが適切なエラーメッセージを表示できるようにする。
R2 画像アップロードパターン
PUT /products/:id/image は
multipart/form-data で画像を受け取り、
Workers ランタイムから Cloudflare R2 に直接 PUT する(クライアント → API Worker → R2)。
ファイルサイズ上限 2 MB、MIME は image/jpeg / image/png のみ許可。
保存後、CDN 配信 URL を PRODUCT テーブルの image_url に書き込む。
関連要件: PROD-007 UC-14 UC-15
VISITOR の論理削除と ID 採番規則
DELETE /visitors/:visitor_id は物理削除ではなく
status = "checked_out" への更新(論理削除)。
BILL の visitor_id が文字列参照のため、レコードを消すと会計履歴が断絶する。
VISITOR ID は v_YYYYMMDD_NNN 形式で、
当日の seated + checked_out 件数の合計をシーケンス番号とする。
関連要件: TABLE-003 TABLE-004 UC-04
/auth/login
ログイン(トークン発行)
認証不要
メールアドレスとパスワードで認証し、Access Token と Refresh Token を発行します。
▸ Request Body
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 400 | validation-error | email / password が未入力または形式不正 |
| 401 | invalid-credentials | メールアドレスまたはパスワードが不正 |
/auth/logout
ログアウト(トークン無効化)
🔐 JWT必須
現在の Refresh Token をサーバーサイドでブラックリストに追加します。リクエストボディは不要です。
▸ Response 204 — No Content
/auth/me
自分のプロフィール取得
🔐 JWT必須
現在ログイン中のスタッフ情報を返します。フロントエンドの初期ロードやトークン検証後のプロフィール確認に使用します。
▸ Response 200
/auth/refresh
Access Token 更新
Refresh Token を使用
有効な Refresh Token を受け取り、新しい Access Token を発行します。
▸ Request Body
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 401 | invalid-refresh-token | Refresh Token が無効・期限切れ・ブラックリスト済み |
/categories
カテゴリ一覧取得
🔐 JWT必須
全カテゴリを一覧取得します。各カテゴリに紐づく有効商品数(product_count)を含みます。
▸ Response 200
/categories
カテゴリ作成
🔐 店長のみ
新しいカテゴリを作成します。slug は URL セーフな一意識別子です。既存スラッグと重複する場合は 409 を返します。
▸ Request Body
▸ Response 201
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 400 | validation-error | name / slug が未入力、slug に使用不可文字が含まれる |
| 403 | forbidden | role が manager 以外(required_role: "manager") |
| 409 | conflict | slug が既存カテゴリと重複 |
/categories/:id
カテゴリ詳細取得
🔐 JWT必須
指定 ID のカテゴリ情報を取得します。
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 404 | not-found | 指定 ID のカテゴリが存在しない |
/categories/:id
カテゴリ更新
🔐 店長のみ
カテゴリ名・スラッグを更新します。変更したいフィールドのみ送信できます(部分更新)。
▸ Request Body(部分更新可)
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 403 | forbidden | role が manager 以外 |
| 404 | not-found | 指定 ID のカテゴリが存在しない |
| 409 | conflict | 変更後の slug が既存カテゴリと重複 |
/categories/:id
カテゴリ削除
🔐 店長のみ
カテゴリを削除します。紐づく商品がある場合、その商品の category_id は
0(未分類)にリセットされます。
▸ Response 204 — No Content
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 403 | forbidden | role が manager 以外 |
| 404 | not-found | 指定 ID のカテゴリが存在しない |
/products
商品一覧取得
🔐 JWT必須
商品一覧を取得します。POS 画面・商品管理画面の両方で使用します。
▸ Query Parameters
| パラメータ | 型 | 説明 | 必須 |
|---|---|---|---|
| category_id | integer | カテゴリ ID で絞り込み | 任意 |
| is_active | boolean | true で有効商品のみ、省略時は全件 | 任意 |
| q | string | 商品名でキーワード検索(部分一致) | 任意 |
| limit | integer | 取得件数(デフォルト 50) | 任意 |
| offset | integer | オフセット(デフォルト 0) | 任意 |
▸ Response 200
/products
商品作成
🔐 店長のみ
新しい商品を登録します。画像は別途 PUT /products/:id/image でアップロードします。
▸ Request Body
▸ Response 201
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 400 | validation-error | product_name / price が未入力、price が 0 以下 |
| 403 | forbidden | role が manager 以外 |
| 404 | not-found | 指定 category_id が存在しない |
/products/:id
商品詳細取得
🔐 JWT必須
指定 ID の商品詳細を取得します。説明文・更新日時を含む全フィールドを返します。
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 404 | not-found | 指定 ID の商品が存在しない |
/products/:id
商品更新
🔐 店長のみ
商品情報を部分更新します。価格変更は既存 BILL_ITEM には影響しません(スナップショット設計)。
▸ Request Body(部分更新可)
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 400 | validation-error | price が 0 以下 |
| 403 | forbidden | role が manager 以外 |
| 404 | not-found | 指定 ID の商品が存在しない |
/products/:id
商品削除
🔐 店長のみ
商品を削除します。過去の BILL_ITEM はスナップショットを保持しているため売上履歴は保全されます。 未精算オーダーに含まれる場合は 422 を返します。
▸ Response 204 — No Content
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 403 | forbidden | role が manager 以外 |
| 404 | not-found | 指定 ID の商品が存在しない |
| 422 | unprocessable-entity | 未精算オーダーのアイテムに含まれている |
/products/:id/image
商品画像アップロード(R2)
🔐 店長のみ
multipart/form-data で画像を受け取り Cloudflare R2 に保存します。
JPEG / PNG のみ対応、最大 2 MB。成功後 CDN URL を image_url に書き込みます。
▸ Request(multipart/form-data)
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 400 | validation-error | image フィールドが未設定、またはサポートされていない MIME タイプ |
| 403 | forbidden | role が manager 以外 |
| 404 | not-found | 指定 ID の商品が存在しない |
| 413 | payload-too-large | ファイルサイズが 2 MB を超える |
/employees
従業員一覧取得
🔐 店長のみ
全従業員を一覧取得します。パスワードハッシュは含みません。
▸ Response 200
/employees
従業員作成
🔐 店長のみ
新しいスタッフアカウントを作成します。password は初期パスワード(8文字以上、ハッシュ化して保存)。メールアドレスはシステム内で一意です。nearest_station(最寄り駅)は任意項目です。
▸ Request Body
▸ Response 201
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 400 | validation-error | name / email / password が未入力、email が不正な形式 |
| 403 | forbidden | role が manager 以外 |
| 409 | conflict | email が既存スタッフと重複 |
/employees/:id
従業員詳細取得
🔐 店長のみ
指定 ID の従業員情報を取得します。
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 404 | not-found | 指定 ID の従業員が存在しない |
/employees/:id
従業員情報更新
🔐 店長のみ
従業員の氏名・役職・メールアドレスを部分更新します。パスワード変更は別途パスワードリセットフローを使用してください。
▸ Request Body(部分更新可)
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 403 | forbidden | role が manager 以外 |
| 404 | not-found | 指定 ID の従業員が存在しない |
| 409 | conflict | 変更後の email が既存スタッフと重複 |
/employees/:id
従業員削除
🔐 店長のみ
従業員アカウントを削除します。自分自身は削除できません(422)。 最後の manager アカウントも削除できません(422)。
▸ Response 204 — No Content
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 403 | forbidden | role が manager 以外 |
| 404 | not-found | 指定 ID の従業員が存在しない |
| 422 | unprocessable-entity | 自分自身の削除、または最後の manager アカウントの削除 |
/tables
テーブル一覧(空席状態付き)
🔐 JWT必須
全テーブルのマスタ情報(table_id / label / seats)を返します。
空席状態は別途 GET /visitors?status=seated の table_id と突き合わせてフロント側で算出します。
フロアプラン表示・着席管理画面で使用します。
▸ Response 200
/visitors
来店者一覧取得
🔐 JWT必須
来店者の一覧を取得します。デフォルトは当日分。date パラメータで過去日付も参照できます。
▸ Query Parameters
| パラメータ | 型 | 説明 | 必須 |
|---|---|---|---|
| date | string | 日付(YYYY-MM-DD)。省略時は当日 | 任意 |
| table_id | integer | テーブル ID で絞り込み | 任意 |
| status | string | seated / checked_out で絞り込み | 任意 |
▸ Response 200
/visitors
来店者作成(着席登録)
🔐 JWT必須
テーブルに来客を着席させ VISITOR レコードを生成します。
visitor_id はサーバーが採番(v_YYYYMMDD_NNN 形式)します。
既に着席中のテーブルを指定した場合は 422 を返します。
▸ Request Body
▸ Response 201
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 400 | validation-error | table_id 未入力、party が 1 未満またはテーブル最大席数超 |
| 404 | not-found | 指定 table_id が存在しない |
| 422 | unprocessable-entity | 指定テーブルがすでに着席中 |
/visitors/:visitor_id
退席処理(論理削除)
🔐 JWT必須
VISITOR のステータスを checked_out に更新します(物理削除ではありません)。
未精算のオーダーが残っている場合は 422 を返します。
通常は POST /visitors/:id/bills で精算する際に自動で退席処理されます。
▸ Response 204 — No Content
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 404 | not-found | 指定 visitor_id が存在しない |
| 422 | unprocessable-entity | 未精算オーダーが残っている(先に精算してください) |
/visitors/:visitor_id/staff-call
スタッフ呼び出しフラグ設定
🔐 JWT必須
テーブルのスタッフ呼び出しフラグを設定・解除します。
フラグは visitors.staff_called に永続化され、
POS の 30 秒ポーリング(GET /visitors?status=seated)で全デバイスに伝播します。
▸ Request Body
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 404 | not-found | 着席中の visitor_id が存在しない |
/visitors/:visitor_id/bill-request
会計依頼フラグ設定
🔐 JWT必須
テーブルの会計依頼フラグを設定・解除します。
フラグは visitors.bill_requested に永続化され、
POS の 30 秒ポーリングで全スタッフデバイスに伝播します。
POST /visitors/:id/bills での精算完了時にフラグは自動クリアされます。
▸ Request Body
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 404 | not-found | 着席中の visitor_id が存在しない |
/visitors/:visitor_id/orders
来店者のオーダー一覧取得
🔐 JWT必須
指定来店者の全オーダーとアイテムを取得します。会計依頼・注文確認・Running Tab 表示に使用します。
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 404 | not-found | 指定 visitor_id が存在しない |
/visitors/:visitor_id/orders
オーダー作成(バルク登録)
🔐 JWT必須
ORDER と ORDER_ITEM をひとつのトランザクションで生成します(DN-02)。
同一来店者に対して複数回呼ぶことができ、それぞれ別 ORDER レコードとなります。
is_active: false の商品は注文不可(422)。
▸ Request Body
▸ Response 201
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 400 | validation-error | items が空、quantity が 1 未満 |
| 404 | not-found | visitor_id が存在しない、または items 内の product_id が存在しない |
| 422 | unprocessable-entity | VISITOR が checked_out 済み、または商品が非表示(is_active: false) |
/orders/:order_id
オーダー詳細取得
🔐 JWT必須
指定オーダー ID の詳細(アイテム含む)を取得します。キッチン表示・注文確認に使用します。
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 404 | not-found | 指定 order_id が存在しない |
/orders/:order_id
オーダーステータス更新
🔐 JWT必須
オーダーのステータスを更新します。キッチン側がステータスを進めるために使用します。
ステータス遷移: pending → preparing → served。
逆行・スキップ遷移は 422 を返します。
▸ Request Body
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 400 | validation-error | 無効な status 値 |
| 404 | not-found | 指定 order_id が存在しない |
| 422 | unprocessable-entity | 許可されていないステータス遷移(例: served → pending) |
/orders/:order_id
オーダーキャンセル
🔐 JWT必須
オーダーをキャンセルします。
ステータスが pending のオーダーのみキャンセル可能です。
preparing 以降は 422 を返します。
▸ Response 204 — No Content
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 404 | not-found | 指定 order_id が存在しない |
| 422 | unprocessable-entity | ステータスが preparing / served のためキャンセル不可 |
/bills
会計一覧取得
🔐 JWT必須
会計履歴を取得します。日付で絞り込めます。日次締め・レポート集計に使用します。
▸ Query Parameters
| パラメータ | 型 | 説明 | 必須 |
|---|---|---|---|
| date | string | 日付(YYYY-MM-DD)。created_at の日付で絞り込み。省略時は全件 | 任意 |
| limit | integer | 取得件数(デフォルト 50、最大 200) | 任意 |
| offset | integer | オフセット(デフォルト 0) | 任意 |
▸ Response 200
/visitors/:visitor_id/bills
会計精算(アトミック処理)
🔐 JWT必須
来店者の全 served オーダーを集計して会計を確定します(DN-03)。
BILL + BILL_ITEM 生成と VISITOR の checked_out 更新を単一トランザクションで実行します。
小計・税額・合計はサーバー側で DB から再計算し、employee_id は JWT から取得します(クライアント値は信頼しません)。
discount_amount(割引額・円)は任意で、省略時は 0 です。
▸ Request Body
▸ Response 201
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 400 | validation-error | payment_method が cash / credit 以外、discount_amount が負の値または小計超過、請求対象オーダーが存在しない(全て pending / preparing 状態) |
| 404 | not-found | 指定 visitor_id が存在しない |
| 422 | unprocessable-entity | VISITOR が checked_out 済み |
/bills/:bill_id
会計詳細取得
🔐 JWT必須
指定 bill_id の会計詳細(BILL_ITEM スナップショット含む)を取得します。領収書再発行・Void 前確認に使用します。
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 404 | not-found | 指定 bill_id が存在しない |
/bills/:bill_id/void
会計取消(Void)
🔐 店長のみ
会計を取消状態(voided)に変更します。売上集計から除外されます。
取消理由(reason)の記録が必須です。
▸ Request Body
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 400 | validation-error | reason が未入力 |
| 403 | forbidden | role が manager 以外 |
| 404 | not-found | 指定 bill_id が存在しない |
| 422 | unprocessable-entity | すでに voided 状態 |
period=1d|7d|30d または
start_date + end_date(YYYY-MM-DD)で期間指定できます。省略時は period=7d。
/reports/summary
売上サマリー取得
🔐 JWT必須
指定期間の売上合計・会計件数・平均客単価・商品販売点数を返します。ダッシュボードのKPIカードで使用します。
▸ Response 200
/reports/revenue-chart
売上推移チャートデータ取得
🔐 JWT必須
日別の売上金額・会計件数を時系列で返します。売上推移グラフの描画に使用します。settled 状態の BILL を日付で集計します(voided は除外)。
▸ Response 200
/reports/category-sales
カテゴリ別売上取得
🔐 JWT必須
カテゴリごとの売上金額・販売点数を返します。円グラフ(カテゴリ別売上)の描画に使用します。
▸ Response 200
/reports/top-products
売上 TOP 商品取得
🔐 JWT必須
売上金額順の商品ランキングを返します。limit で取得件数を指定できます(デフォルト 5)。
▸ Query Parameters
| パラメータ | 型 | 説明 | 必須 |
|---|---|---|---|
| period | string | 1d / 7d / 30d | 任意 |
| limit | integer | ランキング取得件数(デフォルト 5、最大 20) | 任意 |
▸ Response 200
/reports/payment-share
支払い方法別比率取得
🔐 JWT必須
支払い方法(現金 / クレジットカード)ごとの件数・金額・シェアを返します。支払い比率円グラフの描画に使用します。
▸ Response 200
/daily-close/:date
日次締め情報取得
🔐 JWT必須
指定日の締め情報・売上サマリー・カテゴリ別集計を取得します。
日次締めページのデータロードに使用します。
date は YYYY-MM-DD 形式。
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 400 | validation-error | date が不正な形式 |
/daily-close/:date/close
日次締め実行
🔐 店長のみ
指定日の日次締めを実行し、ステータスを closed に変更します。
締め後は当日の BILL 編集が不可になります(Void は店長権限で引き続き可能)。
リクエストボディは不要です。
▸ Response 200
▸ エラーレスポンス
| ステータス | type | 発生条件 |
|---|---|---|
| 403 | forbidden | role が manager 以外 |
| 422 | unprocessable-entity | すでに closed 状態、または対象日が未来日 |
/daily-close/:date
締めメモ更新
🔐 JWT必須
日次締めメモを更新します。締め前・締め後の両方で編集可能です。 機材トラブル・廃棄ロス・引継ぎ事項などを記録します。
▸ Request Body
▸ Response 200
全エラーは RFC 7807 Problem Details 形式で返します。
type は一意のエラー識別 URI、
detail には人間が読める説明を含めます。
| HTTP | type(末尾) | 説明 | 主な発生箇所 |
|---|---|---|---|
| 200 | — | 成功(GET / PATCH) | — |
| 201 | — | 作成成功(POST) | — |
| 204 | — | 成功・ボディなし(DELETE / POST /logout) | — |
| 400 | validation-error |
リクエストボディ・クエリパラメータのバリデーション失敗 | 全 POST / PATCH |
| 401 | unauthorized |
トークン未付与・形式不正・期限切れ | 認証必須エンドポイント全般 |
| 401 | invalid-credentials |
メール / パスワードの認証失敗 | POST /auth/login |
| 401 | invalid-refresh-token |
Refresh Token が無効・期限切れ・ブラックリスト済み | POST /auth/refresh |
| 403 | forbidden |
認証済みだがロール権限不足。required_role フィールドを含む |
店長限定エンドポイント |
| 404 | not-found |
対象リソースが存在しない | GET / PATCH / DELETE |
| 409 | conflict |
一意制約違反(slug 重複・email 重複) | POST/PATCH カテゴリ・従業員 |
| 413 | payload-too-large |
アップロードファイルが 2 MB 超 | PUT /products/:id/image |
| 422 | unprocessable-entity |
ビジネスルール違反(不正な状態遷移・整合性エラー) | 注文キャンセル・会計精算・日次締めなど |
| 500 | internal-server-error |
サーバー内部エラー。detail は汎用メッセージのみ(内部情報を含まない) |
予期しないエラー |