← ドキュメント一覧
Doc 04

API設計

REST API Design — Hono + Cloudflare Workers · 41 エンドポイント

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 単体の入出力)。

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Access Token
有効期限 1 時間(3600 秒)。期限切れは 401 を返す。
Refresh Token
有効期限 30 日POST /auth/refresh で新しい Access Token を取得。

ページネーション / レスポンス形式

一覧系 GET は limit / offset クエリパラメータによるオフセットページネーションを使用します。省略時のデフォルトは limit=50&offset=0

{ "data": [ ... ], // レスポンス配列 "total": 127, // 条件に一致する全件数 "limit": 50, "offset": 0 }

エラーレスポンス(RFC 7807 準拠)

全エラーは RFC 7807 Problem Details 形式で返します。詳細は エラー一覧 を参照。

{ "type": "https://api.maruyama-coffee.example.com/errors/validation-error", "title": "Validation Error", "status": 400, "detail": "slug フィールドはすでに使用されています", "instance": "/v1/categories" }
設計判断 — Design Decisions
DN-01

JWT 二重トークン構成(Access + Refresh)

Access Token は短命(1h)にして万一の漏洩時リスクを最小化。 Refresh Token(30d)はサーバーサイドのブラックリストで即時無効化可能とし、ログアウトの確実性を担保。 POST /auth/logout は Refresh Token をブラックリストに追加し、 以降の /auth/refresh 呼び出しを無効にする。 関連要件: AUTH-005 NF-S003

DN-02

注文アイテムのバルク登録

POST /visitors/:visitor_id/ordersitems[] 配列を受け取り、 ORDER レコードと全 ORDER_ITEM をひとつのトランザクションで生成する。 1 アイテムずつ N 回 POST する設計は避け、ネットワークラウンドトリップを最小化する。 関連要件: POS-021 POS-022 UC-06

DN-03

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

DN-04

401 と 403 の使い分け

401 Unauthorized は「誰だか不明」— トークン未付与・形式不正・期限切れのいずれか。 403 Forbidden は「誰かは分かるが権限がない」— 認証済みスタッフが role: manager 限定エンドポイントを呼んだ場合。 403 応答には required_role フィールドを含め、フロントが適切なエラーメッセージを表示できるようにする。

DN-05

R2 画像アップロードパターン

PUT /products/:id/imagemultipart/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

DN-06

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
POST /auth/login ログイン(トークン発行) 認証不要

メールアドレスとパスワードで認証し、Access Token と Refresh Token を発行します。

▸ Request Body
{ "email": "tanaka@maruyama-coffee.jp", "password": "P@ssw0rd!" }
▸ Response 200
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "rt_a1b2c3d4e5f6g7h8i9j0...", "expires_in": 3600, "employee": { "employee_id": 3, "name": "田中 一郎", "role": "hall", "email": "tanaka@maruyama-coffee.jp" } }
▸ エラーレスポンス
ステータスtype発生条件
400validation-erroremail / password が未入力または形式不正
401invalid-credentialsメールアドレスまたはパスワードが不正
UC-01 AUTH-001 AUTH-002 AUTH-003
POST /auth/logout ログアウト(トークン無効化) 🔐 JWT必須

現在の Refresh Token をサーバーサイドでブラックリストに追加します。リクエストボディは不要です。

▸ Response 204 — No Content
// ボディなし
UC-03 AUTH-005
GET /auth/me 自分のプロフィール取得 🔐 JWT必須

現在ログイン中のスタッフ情報を返します。フロントエンドの初期ロードやトークン検証後のプロフィール確認に使用します。

▸ Response 200
{ "employee_id": 3, "name": "田中 一郎", "role": "hall", "email": "tanaka@maruyama-coffee.jp", "photo_url": null, "nearest_station": "渋谷", "created_at": "2024-03-15T09:00:00Z" }
AUTH-004
POST /auth/refresh Access Token 更新 Refresh Token を使用

有効な Refresh Token を受け取り、新しい Access Token を発行します。

▸ Request Body
{ "refresh_token": "rt_a1b2c3d4e5f6g7h8i9j0..." }
▸ Response 200
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 3600 }
▸ エラーレスポンス
ステータスtype発生条件
401invalid-refresh-tokenRefresh Token が無効・期限切れ・ブラックリスト済み
NF-S003
カテゴリ — Categories
GET /categories カテゴリ一覧取得 🔐 JWT必須

全カテゴリを一覧取得します。各カテゴリに紐づく有効商品数(product_count)を含みます。

▸ Response 200
{ "data": [ { "id": 1, "name": "コーヒー", "slug": "coffee", "product_count": 5 }, { "id": 2, "name": "ティー", "slug": "tea", "product_count": 3 }, { "id": 3, "name": "フード", "slug": "food", "product_count": 4 } ] }
UC-17 CAT-001
POST /categories カテゴリ作成 🔐 店長のみ

新しいカテゴリを作成します。slug は URL セーフな一意識別子です。既存スラッグと重複する場合は 409 を返します。

▸ Request Body
{ "name": "デザート", "slug": "dessert" }
▸ Response 201
{ "id": 5, "name": "デザート", "slug": "dessert", "product_count": 0 }
▸ エラーレスポンス
ステータスtype発生条件
400validation-errorname / slug が未入力、slug に使用不可文字が含まれる
403forbiddenrole が manager 以外(required_role: "manager")
409conflictslug が既存カテゴリと重複
UC-17 CAT-003
GET /categories/:id カテゴリ詳細取得 🔐 JWT必須

指定 ID のカテゴリ情報を取得します。

▸ Response 200
{ "id": 1, "name": "コーヒー", "slug": "coffee", "product_count": 5 }
▸ エラーレスポンス
ステータスtype発生条件
404not-found指定 ID のカテゴリが存在しない
CAT-001
PATCH /categories/:id カテゴリ更新 🔐 店長のみ

カテゴリ名・スラッグを更新します。変更したいフィールドのみ送信できます(部分更新)。

▸ Request Body(部分更新可)
{ "name": "コーヒー類", "slug": "coffee-drinks" }
▸ Response 200
{ "id": 1, "name": "コーヒー類", "slug": "coffee-drinks", "product_count": 5 }
▸ エラーレスポンス
ステータスtype発生条件
403forbiddenrole が manager 以外
404not-found指定 ID のカテゴリが存在しない
409conflict変更後の slug が既存カテゴリと重複
UC-17 CAT-004 CAT-005
DELETE /categories/:id カテゴリ削除 🔐 店長のみ

カテゴリを削除します。紐づく商品がある場合、その商品の category_id0(未分類)にリセットされます。

▸ Response 204 — No Content
// ボディなし
▸ エラーレスポンス
ステータスtype発生条件
403forbiddenrole が manager 以外
404not-found指定 ID のカテゴリが存在しない
UC-17 CAT-006
商品 — Products
GET /products 商品一覧取得 🔐 JWT必須

商品一覧を取得します。POS 画面・商品管理画面の両方で使用します。

▸ Query Parameters
パラメータ説明必須
category_idintegerカテゴリ ID で絞り込み任意
is_activebooleantrue で有効商品のみ、省略時は全件任意
qstring商品名でキーワード検索(部分一致)任意
limitinteger取得件数(デフォルト 50)任意
offsetintegerオフセット(デフォルト 0)任意
▸ Response 200
{ "data": [ { "product_id": 1, "product_name": "エスプレッソ", "price": 400, "category_id": 1, "category_name": "コーヒー", "is_active": true, "image_url": "https://assets.maruyama-coffee.example.com/products/1/image.jpg" }, { "product_id": 2, "product_name": "カフェラテ", "price": 500, "category_id": 1, "category_name": "コーヒー", "is_active": true, "image_url": null } ], "total": 12, "limit": 50, "offset": 0 }
UC-06 UC-14 PROD-001
POST /products 商品作成 🔐 店長のみ

新しい商品を登録します。画像は別途 PUT /products/:id/image でアップロードします。

▸ Request Body
{ "product_name": "カプチーノ", "price": 550, "category_id": 1, "is_active": true, "description": "濃厚なエスプレッソにスチームミルクとフォームミルクを合わせた一杯" }
▸ Response 201
{ "product_id": 13, "product_name": "カプチーノ", "price": 550, "category_id": 1, "is_active": true, "image_url": null, "description": "濃厚なエスプレッソにスチームミルクとフォームミルクを合わせた一杯", "created_at": "2026-05-28T10:30:00Z" }
▸ エラーレスポンス
ステータスtype発生条件
400validation-errorproduct_name / price が未入力、price が 0 以下
403forbiddenrole が manager 以外
404not-found指定 category_id が存在しない
UC-14 PROD-006 PROD-008
GET /products/:id 商品詳細取得 🔐 JWT必須

指定 ID の商品詳細を取得します。説明文・更新日時を含む全フィールドを返します。

▸ Response 200
{ "product_id": 1, "product_name": "エスプレッソ", "price": 400, "category_id": 1, "category_name": "コーヒー", "is_active": true, "image_url": "https://assets.maruyama-coffee.example.com/products/1/image.jpg", "description": "シングルショット エスプレッソ", "created_at": "2026-01-15T09:00:00Z", "updated_at": "2026-05-10T14:22:00Z" }
▸ エラーレスポンス
ステータスtype発生条件
404not-found指定 ID の商品が存在しない
PROD-001
PATCH /products/:id 商品更新 🔐 店長のみ

商品情報を部分更新します。価格変更は既存 BILL_ITEM には影響しません(スナップショット設計)。

▸ Request Body(部分更新可)
{ "price": 450, "is_active": true, "description": "シングルショット エスプレッソ(濃いめ)" }
▸ Response 200
{ "product_id": 1, "product_name": "エスプレッソ", "price": 450, "category_id": 1, "is_active": true, "image_url": "https://assets.maruyama-coffee.example.com/products/1/image.jpg", "updated_at": "2026-05-28T10:35:00Z" }
▸ エラーレスポンス
ステータスtype発生条件
400validation-errorprice が 0 以下
403forbiddenrole が manager 以外
404not-found指定 ID の商品が存在しない
UC-15 PROD-009 PROD-010
DELETE /products/:id 商品削除 🔐 店長のみ

商品を削除します。過去の BILL_ITEM はスナップショットを保持しているため売上履歴は保全されます。 未精算オーダーに含まれる場合は 422 を返します。

▸ Response 204 — No Content
// ボディなし
▸ エラーレスポンス
ステータスtype発生条件
403forbiddenrole が manager 以外
404not-found指定 ID の商品が存在しない
422unprocessable-entity未精算オーダーのアイテムに含まれている
UC-16 PROD-011 PROD-012
PUT /products/:id/image 商品画像アップロード(R2) 🔐 店長のみ

multipart/form-data で画像を受け取り Cloudflare R2 に保存します。 JPEG / PNG のみ対応、最大 2 MB。成功後 CDN URL を image_url に書き込みます。

▸ Request(multipart/form-data)
Content-Type: multipart/form-data; boundary=----FormBoundary ------FormBoundary Content-Disposition: form-data; name="image"; filename="espresso.jpg" Content-Type: image/jpeg <バイナリデータ> ------FormBoundary--
▸ Response 200
{ "product_id": 1, "image_url": "https://assets.maruyama-coffee.example.com/products/1/image.jpg" }
▸ エラーレスポンス
ステータスtype発生条件
400validation-errorimage フィールドが未設定、またはサポートされていない MIME タイプ
403forbiddenrole が manager 以外
404not-found指定 ID の商品が存在しない
413payload-too-largeファイルサイズが 2 MB を超える
UC-14 UC-15 PROD-007
従業員 — Employees
GET /employees 従業員一覧取得 🔐 店長のみ

全従業員を一覧取得します。パスワードハッシュは含みません。

▸ Response 200
{ "data": [ { "employee_id": 1, "name": "山田 花子", "role": "manager", "email": "yamada@maruyama-coffee.jp", "photo_url": "https://assets.maruyama-coffee.example.com/employees/1.jpg", "nearest_station": "表参道", "created_at": "2023-04-01T09:00:00Z" }, { "employee_id": 3, "name": "田中 一郎", "role": "hall", "email": "tanaka@maruyama-coffee.jp", "photo_url": null, "nearest_station": "渋谷", "created_at": "2024-03-15T09:00:00Z" } ] }
UC-18 EMP-001
POST /employees 従業員作成 🔐 店長のみ

新しいスタッフアカウントを作成します。password は初期パスワード(8文字以上、ハッシュ化して保存)。メールアドレスはシステム内で一意です。nearest_station(最寄り駅)は任意項目です。

▸ Request Body
{ "name": "鈴木 二郎", "role": "barista", "email": "suzuki@maruyama-coffee.jp", "password": "TempPass123!", "nearest_station": "原宿" }
▸ Response 201
{ "employee_id": 8, "name": "鈴木 二郎", "role": "barista", "email": "suzuki@maruyama-coffee.jp", "photo_url": null, "nearest_station": "原宿", "created_at": "2026-06-01T09:00:00Z" }
▸ エラーレスポンス
ステータスtype発生条件
400validation-errorname / email / password が未入力、email が不正な形式
403forbiddenrole が manager 以外
409conflictemail が既存スタッフと重複
UC-18 EMP-004 EMP-005
GET /employees/:id 従業員詳細取得 🔐 店長のみ

指定 ID の従業員情報を取得します。

▸ Response 200
{ "employee_id": 3, "name": "田中 一郎", "role": "hall", "email": "tanaka@maruyama-coffee.jp", "photo_url": null, "nearest_station": "渋谷", "created_at": "2024-03-15T09:00:00Z" }
▸ エラーレスポンス
ステータスtype発生条件
404not-found指定 ID の従業員が存在しない
EMP-001
PATCH /employees/:id 従業員情報更新 🔐 店長のみ

従業員の氏名・役職・メールアドレスを部分更新します。パスワード変更は別途パスワードリセットフローを使用してください。

▸ Request Body(部分更新可)
{ "name": "田中 一朗", "role": "manager" }
▸ Response 200
{ "employee_id": 3, "name": "田中 一朗", "role": "manager", "email": "tanaka@maruyama-coffee.jp", "photo_url": null, "nearest_station": "渋谷", "created_at": "2024-03-15T09:00:00Z" }
▸ エラーレスポンス
ステータスtype発生条件
403forbiddenrole が manager 以外
404not-found指定 ID の従業員が存在しない
409conflict変更後の email が既存スタッフと重複
UC-19 EMP-006
DELETE /employees/:id 従業員削除 🔐 店長のみ

従業員アカウントを削除します。自分自身は削除できません(422)。 最後の manager アカウントも削除できません(422)。

▸ Response 204 — No Content
// ボディなし
▸ エラーレスポンス
ステータスtype発生条件
403forbiddenrole が manager 以外
404not-found指定 ID の従業員が存在しない
422unprocessable-entity自分自身の削除、または最後の manager アカウントの削除
UC-19 EMP-007
テーブル — Tables
GET /tables テーブル一覧(空席状態付き) 🔐 JWT必須

全テーブルのマスタ情報(table_id / label / seats)を返します。 空席状態は別途 GET /visitors?status=seatedtable_id と突き合わせてフロント側で算出します。 フロアプラン表示・着席管理画面で使用します。

▸ Response 200
{ "data": [ { "table_id": 1, "label": "T1", "seats": 4 }, { "table_id": 2, "label": "T2", "seats": 2 }, { "table_id": 7, "label": "C1", "seats": 1 } ] }
UC-04 TABLE-001 TABLE-002
来店者 — Visitors
GET /visitors 来店者一覧取得 🔐 JWT必須

来店者の一覧を取得します。デフォルトは当日分。date パラメータで過去日付も参照できます。

▸ Query Parameters
パラメータ説明必須
datestring日付(YYYY-MM-DD)。省略時は当日任意
table_idintegerテーブル ID で絞り込み任意
statusstringseated / checked_out で絞り込み任意
▸ Response 200
{ "data": [ { "visitor_id": "v_20260528_001", "table_id": 1, "party": 3, "visit_date": "2026-05-28", "visit_sequence": 1, "status": "seated", "staff_called": 0, "bill_requested": 0, "created_at": "2026-05-28T10:00:00Z" }, { "visitor_id": "v_20260528_002", "table_id": 2, "party": 2, "visit_date": "2026-05-28", "visit_sequence": 2, "status": "checked_out", "staff_called": 0, "bill_requested": 0, "created_at": "2026-05-28T10:30:00Z" } ] }
UC-04 UC-05 TABLE-014 TABLE-015
POST /visitors 来店者作成(着席登録) 🔐 JWT必須

テーブルに来客を着席させ VISITOR レコードを生成します。 visitor_id はサーバーが採番(v_YYYYMMDD_NNN 形式)します。 既に着席中のテーブルを指定した場合は 422 を返します。

▸ Request Body
{ "table_id": 3, "party": 2 }
▸ Response 201
{ "visitor_id": "v_20260528_003", "table_id": 3, "party": 2, "visit_date": "2026-05-28", "visit_sequence": 3, "status": "seated", "created_at": "2026-05-28T11:30:00Z" }
▸ エラーレスポンス
ステータスtype発生条件
400validation-errortable_id 未入力、party が 1 未満またはテーブル最大席数超
404not-found指定 table_id が存在しない
422unprocessable-entity指定テーブルがすでに着席中
UC-04 TABLE-007 TABLE-008
DELETE /visitors/:visitor_id 退席処理(論理削除) 🔐 JWT必須

VISITOR のステータスを checked_out に更新します(物理削除ではありません)。 未精算のオーダーが残っている場合は 422 を返します。 通常は POST /visitors/:id/bills で精算する際に自動で退席処理されます。

▸ Response 204 — No Content
// ボディなし
▸ エラーレスポンス
ステータスtype発生条件
404not-found指定 visitor_id が存在しない
422unprocessable-entity未精算オーダーが残っている(先に精算してください)
UC-09 BILL-016
PATCH /visitors/:visitor_id/staff-call スタッフ呼び出しフラグ設定 🔐 JWT必須

テーブルのスタッフ呼び出しフラグを設定・解除します。 フラグは visitors.staff_called に永続化され、 POS の 30 秒ポーリング(GET /visitors?status=seated)で全デバイスに伝播します。

▸ Request Body
{ "active": true }
▸ Response 200
{ "visitor_id": "v_20260528_001", "staff_called": 1 }
▸ エラーレスポンス
ステータスtype発生条件
404not-found着席中の visitor_id が存在しない
UC-08 POS-025
PATCH /visitors/:visitor_id/bill-request 会計依頼フラグ設定 🔐 JWT必須

テーブルの会計依頼フラグを設定・解除します。 フラグは visitors.bill_requested に永続化され、 POS の 30 秒ポーリングで全スタッフデバイスに伝播します。 POST /visitors/:id/bills での精算完了時にフラグは自動クリアされます。

▸ Request Body
{ "active": true }
▸ Response 200
{ "visitor_id": "v_20260528_001", "bill_requested": 1 }
▸ エラーレスポンス
ステータスtype発生条件
404not-found着席中の visitor_id が存在しない
UC-09 POS-027
注文 — Orders
GET /visitors/:visitor_id/orders 来店者のオーダー一覧取得 🔐 JWT必須

指定来店者の全オーダーとアイテムを取得します。会計依頼・注文確認・Running Tab 表示に使用します。

▸ Response 200
{ "visitor_id": "v_20260528_001", "orders": [ { "order_id": 1, "status": "served", "note": null, "ordered_at": "2026-05-28T10:05:00Z", "items": [ { "order_item_id": 1, "product_id": 1, "product_name": "エスプレッソ", "quantity": 2, "unit_price": 400 } ] }, { "order_id": 2, "status": "preparing", "note": "アイスで", "ordered_at": "2026-05-28T10:30:00Z", "items": [ { "order_item_id": 3, "product_id": 2, "product_name": "カフェラテ", "quantity": 1, "unit_price": 500 } ] } ] }
▸ エラーレスポンス
ステータスtype発生条件
404not-found指定 visitor_id が存在しない
UC-05 UC-09 TABLE-010 TABLE-011
POST /visitors/:visitor_id/orders オーダー作成(バルク登録) 🔐 JWT必須

ORDER と ORDER_ITEM をひとつのトランザクションで生成します(DN-02)。 同一来店者に対して複数回呼ぶことができ、それぞれ別 ORDER レコードとなります。 is_active: false の商品は注文不可(422)。

▸ Request Body
{ "items": [ { "product_id": 1, "quantity": 2 }, { "product_id": 5, "quantity": 1 } ], "note": "アイスで" }
▸ Response 201
{ "order_id": 3, "visitor_id": "v_20260528_001", "status": "pending", "note": "アイスで", "ordered_at": "2026-05-28T10:45:00Z", "items": [ { "order_item_id": 5, "product_id": 1, "product_name": "エスプレッソ", "quantity": 2, "unit_price": 400 }, { "order_item_id": 6, "product_id": 5, "product_name": "チーズケーキ", "quantity": 1, "unit_price": 600 } ] }
▸ エラーレスポンス
ステータスtype発生条件
400validation-erroritems が空、quantity が 1 未満
404not-foundvisitor_id が存在しない、または items 内の product_id が存在しない
422unprocessable-entityVISITOR が checked_out 済み、または商品が非表示(is_active: false)
UC-06 UC-07 POS-021 POS-022
GET /orders/:order_id オーダー詳細取得 🔐 JWT必須

指定オーダー ID の詳細(アイテム含む)を取得します。キッチン表示・注文確認に使用します。

▸ Response 200
{ "order_id": 3, "visitor_id": "v_20260528_001", "table_id": 1, "status": "pending", "note": "アイスで", "ordered_at": "2026-05-28T10:45:00Z", "items": [ { "order_item_id": 5, "product_id": 1, "product_name": "エスプレッソ", "quantity": 2, "unit_price": 400 } ] }
▸ エラーレスポンス
ステータスtype発生条件
404not-found指定 order_id が存在しない
UC-05 TABLE-011
PATCH /orders/:order_id オーダーステータス更新 🔐 JWT必須

オーダーのステータスを更新します。キッチン側がステータスを進めるために使用します。 ステータス遷移: pendingpreparingserved。 逆行・スキップ遷移は 422 を返します。

▸ Request Body
{ "status": "preparing" }
▸ Response 200
{ "order_id": 3, "status": "preparing", "updated_at": "2026-05-28T10:50:00Z" }
▸ エラーレスポンス
ステータスtype発生条件
400validation-error無効な status 値
404not-found指定 order_id が存在しない
422unprocessable-entity許可されていないステータス遷移(例: served → pending)
UC-10 KIT-009
DELETE /orders/:order_id オーダーキャンセル 🔐 JWT必須

オーダーをキャンセルします。 ステータスが pending のオーダーのみキャンセル可能です。 preparing 以降は 422 を返します。

▸ Response 204 — No Content
// ボディなし
▸ エラーレスポンス
ステータスtype発生条件
404not-found指定 order_id が存在しない
422unprocessable-entityステータスが preparing / served のためキャンセル不可
UC-08
会計 — Bills
GET /bills 会計一覧取得 🔐 JWT必須

会計履歴を取得します。日付で絞り込めます。日次締め・レポート集計に使用します。

▸ Query Parameters
パラメータ説明必須
datestring日付(YYYY-MM-DD)。created_at の日付で絞り込み。省略時は全件任意
limitinteger取得件数(デフォルト 50、最大 200)任意
offsetintegerオフセット(デフォルト 0)任意
▸ Response 200
{ "data": [ { "bill_id": 1, "visitor_id": "v_20260528_001", "employee_id": 3, "subtotal_amount": 1400, "discount_amount": 0, "tax_amount": 140, "total_amount": 1540, "status": "settled", "payment_method": "cash", "void_reason": null, "voided_at": null, "voided_by": null, "created_at": "2026-05-28T12:00:00Z" }, { "bill_id": 2, "visitor_id": "v_20260528_002", "employee_id": 3, "subtotal_amount": 1637, "discount_amount": 0, "tax_amount": 164, "total_amount": 1801, "status": "settled", "payment_method": "credit", "void_reason": null, "voided_at": null, "voided_by": null, "created_at": "2026-05-28T12:15:00Z" } ], "total": 15, "limit": 50, "offset": 0 }
UC-11 BILLS-001
POST /visitors/:visitor_id/bills 会計精算(アトミック処理) 🔐 JWT必須

来店者の全 served オーダーを集計して会計を確定します(DN-03)。 BILL + BILL_ITEM 生成と VISITOR の checked_out 更新を単一トランザクションで実行します。 小計・税額・合計はサーバー側で DB から再計算し、employee_id は JWT から取得します(クライアント値は信頼しません)。 discount_amount(割引額・円)は任意で、省略時は 0 です。

▸ Request Body
{ "payment_method": "cash", "discount_amount": 0 }
▸ Response 201
{ "bill_id": 3, "visitor_id": "v_20260528_003", "employee_id": 3, "subtotal_amount": 1400, "discount_amount": 0, "tax_amount": 140, "total_amount": 1540, "status": "settled", "payment_method": "cash", "void_reason": null, "voided_at": null, "voided_by": null, "created_at": "2026-05-28T12:30:00Z", "items": [ { "bill_item_id": 7, "product_name": "エスプレッソ", "unit_price": 400, "tax_rate": 0.1, "quantity": 2, "subtotal": 800 }, { "bill_item_id": 8, "product_name": "チーズケーキ", "unit_price": 600, "tax_rate": 0.1, "quantity": 1, "subtotal": 600 } ] }
▸ エラーレスポンス
ステータスtype発生条件
400validation-errorpayment_method が cash / credit 以外、discount_amount が負の値または小計超過、請求対象オーダーが存在しない(全て pending / preparing 状態)
404not-found指定 visitor_id が存在しない
422unprocessable-entityVISITOR が checked_out 済み
UC-11 BILL-014 BILL-015
GET /bills/:bill_id 会計詳細取得 🔐 JWT必須

指定 bill_id の会計詳細(BILL_ITEM スナップショット含む)を取得します。領収書再発行・Void 前確認に使用します。

▸ Response 200
{ "bill_id": 3, "visitor_id": "v_20260528_003", "employee_id": 3, "subtotal_amount": 1400, "discount_amount": 0, "tax_amount": 140, "total_amount": 1540, "status": "settled", "payment_method": "cash", "void_reason": null, "voided_at": null, "voided_by": null, "created_at": "2026-05-28T12:30:00Z", "items": [ { "bill_item_id": 7, "product_name": "エスプレッソ", "unit_price": 400, "tax_rate": 0.1, "quantity": 2, "subtotal": 800 }, { "bill_item_id": 8, "product_name": "チーズケーキ", "unit_price": 600, "tax_rate": 0.1, "quantity": 1, "subtotal": 600 } ] }
▸ エラーレスポンス
ステータスtype発生条件
404not-found指定 bill_id が存在しない
BILLS-002
PATCH /bills/:bill_id/void 会計取消(Void) 🔐 店長のみ

会計を取消状態(voided)に変更します。売上集計から除外されます。 取消理由(reason)の記録が必須です。

▸ Request Body
{ "reason": "重複会計のため取消" }
▸ Response 200
{ "bill_id": 3, "status": "voided", "void_reason": "重複会計のため取消", "voided_at": "2026-05-28T13:00:00Z", "voided_by": 1 }
▸ エラーレスポンス
ステータスtype発生条件
400validation-errorreason が未入力
403forbiddenrole が manager 以外
404not-found指定 bill_id が存在しない
422unprocessable-entityすでに voided 状態
UC-12 BILLS-006
レポート — Reports
共通クエリパラメータ: 全レポートエンドポイントは period=1d|7d|30d または start_date + end_dateYYYY-MM-DD)で期間指定できます。省略時は period=7d
GET /reports/summary 売上サマリー取得 🔐 JWT必須

指定期間の売上合計・会計件数・平均客単価・商品販売点数を返します。ダッシュボードのKPIカードで使用します。

▸ Response 200
{ "period": { "start": "2026-05-22", "end": "2026-05-28" }, "total_revenue": 284500, "total_bills": 127, "avg_per_bill": 2240, "total_items_sold": 415 }
UC-20 UC-22 RPT-002
GET /reports/revenue-chart 売上推移チャートデータ取得 🔐 JWT必須

日別の売上金額・会計件数を時系列で返します。売上推移グラフの描画に使用します。settled 状態の BILL を日付で集計します(voided は除外)。

▸ Response 200
{ "period": "7d", "data": [ { "date": "05/22", "revenue": 38200, "bills": 16 }, { "date": "05/23", "revenue": 42100, "bills": 19 }, { "date": "05/24", "revenue": 35600, "bills": 14 }, { "date": "05/25", "revenue": 41800, "bills": 18 }, { "date": "05/26", "revenue": 39500, "bills": 17 }, { "date": "05/27", "revenue": 44200, "bills": 20 }, { "date": "05/28", "revenue": 43100, "bills": 23 } ] }
UC-20 RPT-006
GET /reports/category-sales カテゴリ別売上取得 🔐 JWT必須

カテゴリごとの売上金額・販売点数を返します。円グラフ(カテゴリ別売上)の描画に使用します。

▸ Response 200
{ "period": "7d", "data": [ { "id": 1, "name": "コーヒー", "slug": "coffee", "revenue": 128400, "quantity": 321 }, { "id": 2, "name": "ティー", "slug": "tea", "revenue": 48200, "quantity": 104 }, { "id": 3, "name": "フード", "slug": "food", "revenue": 62100, "quantity": 87 } ] }
UC-20 RPT-010
GET /reports/top-products 売上 TOP 商品取得 🔐 JWT必須

売上金額順の商品ランキングを返します。limit で取得件数を指定できます(デフォルト 5)。

▸ Query Parameters
パラメータ説明必須
periodstring1d / 7d / 30d任意
limitintegerランキング取得件数(デフォルト 5、最大 20)任意
▸ Response 200
{ "period": "7d", "data": [ { "rank": 1, "product_id": 2, "product_name": "カフェラテ", "revenue": 52500, "quantity": 105 }, { "rank": 2, "product_id": 1, "product_name": "エスプレッソ", "revenue": 40000, "quantity": 100 }, { "rank": 3, "product_id": 5, "product_name": "チーズケーキ", "revenue": 36000, "quantity": 72 }, { "rank": 4, "product_id": 3, "product_name": "カプチーノ", "revenue": 24750, "quantity": 45 }, { "rank": 5, "product_id": 4, "product_name": "アールグレイ", "revenue": 18200, "quantity": 52 } ] }
UC-21 RPT-012
GET /reports/payment-share 支払い方法別比率取得 🔐 JWT必須

支払い方法(現金 / クレジットカード)ごとの件数・金額・シェアを返します。支払い比率円グラフの描画に使用します。

▸ Response 200
{ "period": "7d", "data": [ { "payment_method": "cash", "label": "現金", "count": 72, "amount": 158400, "share": 0.557 }, { "payment_method": "credit", "label": "クレジットカード", "count": 55, "amount": 126100, "share": 0.443 } ] }
UC-20 RPT-008
日次締め — Daily Close
GET /daily-close/:date 日次締め情報取得 🔐 JWT必須

指定日の締め情報・売上サマリー・カテゴリ別集計を取得します。 日次締めページのデータロードに使用します。 dateYYYY-MM-DD 形式。

▸ Response 200
{ "date": "2026-05-28", "status": "open", "total_sales": 43100, "cash_sales": 24500, "card_sales": 18600, "bill_count": 23, "avg_per_bill": 1874, "memo": "", "category_sales": [ { "id": 1, "name": "コーヒー", "count": 48, "revenue": 22400 }, { "id": 2, "name": "ティー", "count": 18, "revenue": 7200 }, { "id": 3, "name": "フード", "count": 12, "revenue": 8400 } ] }
▸ エラーレスポンス
ステータスtype発生条件
400validation-errordate が不正な形式
UC-13 CLOSE-001
POST /daily-close/:date/close 日次締め実行 🔐 店長のみ

指定日の日次締めを実行し、ステータスを closed に変更します。 締め後は当日の BILL 編集が不可になります(Void は店長権限で引き続き可能)。 リクエストボディは不要です。

▸ Response 200
{ "date": "2026-05-28", "status": "closed", "closed_at": "2026-05-28T23:00:00Z", "closed_by": 1 }
▸ エラーレスポンス
ステータスtype発生条件
403forbiddenrole が manager 以外
422unprocessable-entityすでに closed 状態、または対象日が未来日
UC-13 CLOSE-008
PATCH /daily-close/:date 締めメモ更新 🔐 JWT必須

日次締めメモを更新します。締め前・締め後の両方で編集可能です。 機材トラブル・廃棄ロス・引継ぎ事項などを記録します。

▸ Request Body
{ "memo": "機材トラブルあり(エスプレッソマシン清掃対応)/ 廃棄ロス:クロワッサン 3 個" }
▸ Response 200
{ "date": "2026-05-28", "memo": "機材トラブルあり(エスプレッソマシン清掃対応)/ 廃棄ロス:クロワッサン 3 個", "updated_at": "2026-05-28T22:55:00Z" }
UC-13 CLOSE-010
エラー一覧 — Error Reference

全エラーは 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 は汎用メッセージのみ(内部情報を含まない) 予期しないエラー
403 エラーのレスポンス例(required_role フィールド)
{ "type": "https://api.maruyama-coffee.example.com/errors/forbidden", "title": "Forbidden", "status": 403, "detail": "このエンドポイントは manager ロールが必要です", "instance": "/v1/employees", "required_role": "manager" }
422 エラーのレスポンス例(会計精算時)
{ "type": "https://api.maruyama-coffee.example.com/errors/unprocessable-entity", "title": "Unprocessable Entity", "status": 422, "detail": "請求対象のオーダーが存在しません。served 状態のオーダーがある場合のみ精算できます", "instance": "/v1/visitors/v_20260528_003/bills" }