本文へスキップ

ZPL-0485 Python開発 実測 24/25

Python HTTP APIのエンドポイントとエラー・権限を設計するPrompt

API設計を「考え始める」状態から「次の行動が決まる」状態へ。

操作・所有関係・入力制約から、成功/失敗レスポンスと他人ID検査をそろえます。

  • SAVE VALUE 5
  • 中級
  • 5〜20分
  • ChatGPT / Claude / Gemini

このPromptでできること

  • 利用者とresource、所有権、操作、フィールド制約を入力すると、endpoint契約、status/error matrix、権限/他人IDのテストを作り、実装前の曖昧さを露出します。

必要なもの(入力項目)

  • API利用者/認証方式・役割
  • resourceと所有者/組織の関係
  • 必要操作と状態遷移
  • path/query/bodyの実フィールド・validation
  • 返してよいresponse fields
  • 既存APIのstatus/error形式
  • 再試行・idempotency方針
  • 使用framework/versionと公開範囲

解決できる悩み

  • API設計で何を決めるべきか分からない
  • 一般論しか出ず、自分のケースに使えない
  • 重要な前提やリスクを見落とす
  • 結果を実行可能な形へ落とし込めない

こんな人向け

  • Pythonプログラマーの初心者
  • 実務を時短したい担当者
  • AIの出力を判断材料として使いたい人

使うタイミング

API設計に着手する直前、既存案を見直すとき、または公開・実行前に抜け漏れを確認するとき。

プロンプト本文

{{ }} の部分を自分の情報に置き換えてから使ってください。

PROMPT
Python製HTTP APIを実装する前に、endpointごとの正常・失敗・権限契約を作ってください。コードは契約の後です。

利用者、認証方式、役割:{{利用者・認証}}
resourceと所有者/組織の関係:{{resource・所有関係}}
必要操作と状態遷移:{{操作・状態遷移}}
path/query/bodyの実フィールドと検証制約:{{request fields・検証}}
レスポンスに公開可能なフィールドと秘密:{{response fields・秘匿}}
既存APIのstatus/error body方針:{{既存status・error}}
再試行・idempotencyの要件:{{再試行方針}}
framework/versionと公開範囲:{{framework・公開範囲}}

resourceごとにmethod/path、request schema、success status/body、error status/body、必要roleと所有権チェックを表にする。認証済みでも他人のobject IDを与えた場合の試験を必ず含める。validation、未認証、権限不足、存在しないID、状態競合のどれを返すかは既存契約と秘匿方針を優先し、曖昧なら候補と確認質問を出す。FastAPIを使う場合のみPydantic/HTTPException/response modelの実装例を任意追加。RFC9457のproblem+jsonは採用時だけ適用し、内部traceや秘密情報をerror bodyへ出さない。GETに不必要なbodyを設計しない。

出力:endpoint契約表、正常/失敗JSONの短い例、error matrix、認証/所有権/validationテスト、未確定事項。HTTP statusとbodyの矛盾を検査する。

使い方

返ってくるもの

  • method/pathとschema、success/error matrix、JSON例、role/所有権チェック、テスト

調整のしかた

「目的・利用者」を変更すると対象が変わり、「完成イメージ・成功指標」を具体化すると提案精度が上がります。

追加で投げると効く指示

  • 初心者向けに手順を細かくしてください
  • 上位3案を比較表にしてください
  • 不足情報だけ質問してください
  • リスクの高い順に並べてください

推奨モデル

コーディング能力・高推論モデル推奨

実測の入力と出力

下は実際にAIへ通したときの入力と、そのとき返ってきた出力です(モデル: gpt-5.1-thinking / 2026-09-16)。作例ではありません。

このとき入力した内容を見る
Python製HTTP APIを実装する前に、endpointごとの正常・失敗・権限契約を作ってください。コードは契約の後です。

利用者、認証方式、役割:社内の業務アプリ(Web フロント)から使う。認証は社内 SSO で発行された JWT を Authorization ヘッダで受け取る。役割は member と admin の2つ。
resourceと所有者/組織の関係:resource は「経費申請(expense)」。申請は作成した社員(owner)のもの。admin は同じ組織(org_id)の全申請を見られる。
必要操作と状態遷移:作成(draft)→ 申請(submitted)→ 承認(approved)または差し戻し(returned)。returned は owner が編集して再申請できる。approved 後は編集・削除不可。承認・差し戻しは admin のみ。
path/query/bodyの実フィールドと検証制約:作成:amount(整数・1〜1,000,000)、category(travel / meal / other)、memo(0〜200文字)、receipt_url(任意・https のみ)。一覧:status と month(YYYY-MM)で絞り込み。
レスポンスに公開可能なフィールドと秘密:返してよい:id, amount, category, memo, status, created_at, owner_name。返さない:owner の社員番号、他の社員の申請の存在。
既存APIのstatus/error body方針:既存の社内API は、エラー時に {"error": {"code": "...", "message": "..."}} を返している。404 と 403 の使い分けは決まっていない。
再試行・idempotencyの要件:フロントが通信エラーで作成を再送することがある。二重作成は避けたい。
framework/versionと公開範囲:FastAPI 0.110 / Python 3.12。社内ネットワークのみに公開。

resourceごとにmethod/path、request schema、success status/body、error status/body、必要roleと所有権チェックを表にする。認証済みでも他人のobject IDを与えた場合の試験を必ず含める。validation、未認証、権限不足、存在しないID、状態競合のどれを返すかは既存契約と秘匿方針を優先し、曖昧なら候補と確認質問を出す。FastAPIを使う場合のみPydantic/HTTPException/response modelの実装例を任意追加。RFC9457のproblem+jsonは採用時だけ適用し、内部traceや秘密情報をerror bodyへ出さない。GETに不必要なbodyを設計しない。

出力:endpoint契約表、正常/失敗JSONの短い例、error matrix、認証/所有権/validationテスト、未確定事項。HTTP statusとbodyの矛盾を検査する。
返ってきた出力を見る
以下を「実装前のAPI契約 v0.1」とします。今回は契約を固める段階なので、FastAPI/Pydanticコードは入れていません。

1. 契約上の基本原則

認証済みでも、member が他人の expense_id を指定した場合は 403 ではなく 404 とします。同様に、admin が別 org_id の申請IDを指定した場合も 404 とします。これにより、権限のない利用者からは「そのIDの申請が存在するか」を判別できません。

一方、利用者がその申請を見る権限は持っているものの、操作だけが許可されていない場合は 403 を返します。たとえば同一組織の admin が他人の申請を PATCH しようとした場合です。

エラー形式は既存APIに合わせて統一します。

JSON
{
  "error": {
    "code": "EXPENSE_NOT_FOUND",
    "message": "Expense not found."
  }
}

内部例外、SQL、JWT claims、社員番号、stack trace、実際の所有者、実際の現在状態などはエラーbodyへ出しません。

2. Expense response schema

全endpointで公開可能なExpense表現を統一します。

JSON
{
  "id": "exp_123",
  "amount": 12800,
  "category": "travel",
  "memo": "東京出張",
  "status": "draft",
  "created_at": "2026-09-16T06:30:00+09:00",
  "owner_name": "山田 太郎"
}

返さないフィールド:

社員番号

org_id

JWT subject

内部DB ID

receipt_url

その他内部監査情報

receipt_url は入力可能ですが、今回提示された「返してよいフィールド」に含まれていないためresponseには含めません。

3. Endpoint契約表
Method / Path	Request	Success	必要role / 所有権	主な失敗
POST /expenses	JSON: amount, category, memo, receipt_url?。Idempotency-Key header必須	201 Created + Expense	member または admin。ownerはJWTから決定	401, 409, 422
GET /expenses	Query: status?, month?。bodyなし	200 OK + {"items":[...]}	member=自分のみ。admin=同一org全部	401, 422
GET /expenses/{expense_id}	bodyなし	200 OK + Expense	owner、または同一org admin	401, 404
PATCH /expenses/{expense_id}	更新するフィールドだけ送信	200 OK + Expense	ownerのみ。draft/returned のみ	401, 403/404, 409, 422
DELETE /expenses/{expense_id}	bodyなし	204 No Content	ownerのみ。暫定で draft/returned のみ	401, 403/404, 409
POST /expenses/{expense_id}/submit	bodyなし	200 OK + Expense	ownerのみ。draft/returned → submitted	401, 403/404, 409
POST /expenses/{expense_id}/approve	bodyなし	200 OK + Expense	同一org adminのみ。submitted → approved	401, 403/404, 409
POST /expenses/{expense_id}/return	bodyなし	200 OK + Expense	同一org adminのみ。submitted → returned	401, 403/404, 409

PATCH status=... のように状態を直接書き換えさせず、submit / approve / return を別操作にしています。これにより「誰がどの遷移を行えるか」をendpoint契約として固定できます。

4. Request schema
POST /expenses
field	型	必須	制約
amount	integer	yes	1 <= amount <= 1,000,000。boolは不可
category	string enum	yes	travel, meal, other
memo	string	yes	0〜200文字
receipt_url	string	no	HTTPS URLのみ

owner、owner_name、org_id、status、created_at はクライアントから受け取りません。

status は常にサーバ側で draft にします。

PATCH /expenses/{id}
JSON
{
  "amount": 15000,
  "memo": "東京出張・交通費"
}

更新可能:

amount, category, memo, receipt_url

id, owner, org_id, status, created_at は変更不可です。

少なくとも1フィールド必要とし、空 {} は 422 とするのを推奨します。

GET /expenses
GET /expenses?status=submitted&month=2026-09

status:

draft | submitted | approved | returned

month:

YYYY-MM

両方指定された場合は AND 条件です。

GETにrequest bodyは持たせません。

5. State transition契約
現状態	edit	delete	submit	approve	return
draft	owner ○	owner ○	owner ○	×	×
submitted	×	×*	×	admin ○	admin ○
returned	owner ○	owner ○	owner ○	×	×
approved	×	×	×	×	×

submitted のDELETEについては、今回「withdraw/cancel」という状態遷移が定義されていないため、暫定で削除不可としています。

したがって、

http
PATCH approved expense

も、

http
DELETE approved expense

も 409 Conflict です。

これは権限不足ではなく「そのresourceの現在状態と操作が競合している」ためです。

6. Idempotency契約

作成の二重登録防止には、resource内容からの推測的な重複判定ではなく Idempotency-Key を使うのが安全です。

http
POST /expenses
Authorization: Bearer ...
Idempotency-Key: 19db1826-d1cf-4a68-aed5-...

同じ認証主体・同じendpointで、

同じkey + 同じrequest内容

なら、最初に作った同じexpenseを返します。新規行を作りません。

http
201 Created
JSON
{
  "id": "exp_123",
  "amount": 12800,
  "category": "travel",
  "memo": "東京出張",
  "status": "draft",
  "created_at": "2026-09-16T06:30:00+09:00",
  "owner_name": "山田 太郎"
}

同じkey + 異なるrequest内容

なら、

http
409 Conflict
JSON
{
  "error": {
    "code": "IDEMPOTENCY_KEY_REUSED",
    "message": "The idempotency key was already used with a different request."
  }
}

keyのscopeは少なくとも

authenticated subject + org_id + endpoint + Idempotency-Key

とします。

Idempotency-Key がない場合は、必須headerのvalidation failureとして暫定的に 422 とします。

7. 正常JSON例
作成
http
POST /expenses
→ 201 Created
JSON
{
  "id": "exp_123",
  "amount": 12800,
  "category": "travel",
  "memo": "東京出張",
  "status": "draft",
  "created_at": "2026-09-16T06:30:00+09:00",
  "owner_name": "山田 太郎"
}
一覧
http
GET /expenses?status=submitted&month=2026-09
→ 200 OK
JSON
{
  "items": [
    {
      "id": "exp_456",
      "amount": 3500,
      "category": "meal",
      "memo": "取引先との昼食",
      "status": "submitted",
      "created_at": "2026-09-12T14:20:00+09:00",
      "owner_name": "山田 太郎"
    }
  ]
}
申請
http
POST /expenses/exp_123/submit
→ 200 OK
JSON
{
  "id": "exp_123",
  "amount": 12800,
  "category": "travel",
  "memo": "東京出張",
  "status": "submitted",
  "created_at": "2026-09-16T06:30:00+09:00",
  "owner_name": "山田 太郎"
}
削除
http
DELETE /expenses/exp_123
→ 204 No Content

204ではbodyを返しません。

8. 失敗JSON例
未認証
http
401 Unauthorized
WWW-Authenticate: Bearer
JSON
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication is required."
  }
}

JWTの失効、署名不正、issuer/audience不一致などについて、外部responseで詳細原因を知らせる必要はありません。

Validation
http
422 Unprocessable Content
JSON
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed."
  }
}

たとえば、

JSON
{
  "amount": 0,
  "category": "hotel",
  "memo": "..."
}

は422です。

FastAPI標準のvalidation error bodyはこの既存契約と違うため、実装時にはexception handlerが必要になります。

他人のID / 存在しないID

両方とも同じです。

http
404 Not Found
JSON
{
  "error": {
    "code": "EXPENSE_NOT_FOUND",
    "message": "Expense not found."
  }
}
Role不足

resource自体を見る資格がある場合に限り403です。

http
403 Forbidden
JSON
{
  "error": {
    "code": "FORBIDDEN",
    "message": "You are not allowed to perform this action."
  }
}
状態競合
http
409 Conflict
JSON
{
  "error": {
    "code": "INVALID_STATE_TRANSITION",
    "message": "The expense state does not allow this operation."
  }
}
9. Error matrix
条件	HTTP	error.code	備考
Authorizationなし	401	UNAUTHORIZED	Bearer要求
JWT不正/期限切れ	401	UNAUTHORIZED	詳細をbodyに出さない
amount範囲外	422	VALIDATION_ERROR	
category不正	422	VALIDATION_ERROR	
memo > 200	422	VALIDATION_ERROR	
receipt_url がHTTP	422	VALIDATION_ERROR	HTTPSのみ
status query不正	422	VALIDATION_ERROR	
month形式不正	422	VALIDATION_ERROR	
createでIdempotency-Key欠落	422	VALIDATION_ERROR	暫定契約
Idempotency-Key再利用+異なるpayload	409	IDEMPOTENCY_KEY_REUSED	
IDが存在しない	404	EXPENSE_NOT_FOUND	
memberが他人のIDを指定	404	EXPENSE_NOT_FOUND	存在を秘匿
adminが別orgのIDを指定	404	EXPENSE_NOT_FOUND	存在を秘匿
同一org adminが他人の申請をGET	200	—	明示的に許可
同一org adminが他人の申請をPATCH	403	FORBIDDEN	見る権限はあるが編集権限なし
memberが自分の申請をapprove	403	FORBIDDEN	role不足
ownerがsubmittedをPATCH	409	INVALID_STATE_TRANSITION	roleではなく状態
ownerがapprovedをDELETE	409	INVALID_STATE_TRANSITION	
adminがdraftをapprove	409	INVALID_STATE_TRANSITION	submittedのみ
adminがapprovedを再approve	409	INVALID_STATE_TRANSITION	

重要なのは、

「存在しない」
「存在するが、その利用者から見えない」

を同じ404にすることです。

10. 認証・所有権・validationテスト
Test	条件	Expected
AUTH-01	Tokenなしで GET /expenses	401 UNAUTHORIZED
AUTH-02	期限切れJWT	401 UNAUTHORIZED
AUTH-03	不正署名JWT	401 UNAUTHORIZED
AUTH-04	JWTのorg_idを書換えたtoken	401
OWNER-01	member A が自分のexpenseをGET	200
OWNER-02	member A がmember Bの有効なexpense_idをGET	404
OWNER-03	member A がmember Bのexpense_idをPATCH	404
OWNER-04	member A がmember Bのexpense_idをDELETE	404
OWNER-05	存在しないexpense_idをmember AがGET	404、OWNER-02と同一body
ORG-01	admin A が同一orgのmember BのexpenseをGET	200
ORG-02	admin A が別orgの実在expense_idをGET	404
ORG-03	同一org adminが他人のexpenseをPATCH	403
ROLE-01	memberが自分のsubmitted expenseをapprove	403
ROLE-02	memberが自分のsubmitted expenseをreturn	403
STATE-01	draftをownerがsubmit	200、submitted
STATE-02	returnedをownerが再submit	200、submitted
STATE-03	submittedをownerがPATCH	409
STATE-04	approvedをownerがPATCH	409
STATE-05	approvedをownerがDELETE	409
STATE-06	draftをadminがapprove	409
STATE-07	submittedをadminがapprove	200、approved
STATE-08	submittedをadminがreturn	200、returned
VAL-01	amount=0	422
VAL-02	amount=1	success
VAL-03	amount=1,000,000	success
VAL-04	amount=1,000,001	422
VAL-05	amount=true	422
VAL-06	category=hotel	422
VAL-07	memo=""	success
VAL-08	memo 200文字	success
VAL-09	memo 201文字	422
VAL-10	http://... receipt_url	422
VAL-11	https://... receipt_url	success
VAL-12	month=2026-9	422
VAL-13	month=2026-09	success
IDEM-01	同じkey・同じcreateを2回	同じid、1件だけ作成
IDEM-02	同じkey・payload変更	409
IDEM-03	別ownerが同じkey	別scopeとして扱う

特に OWNER-02〜04 と ORG-02 は、今回の秘匿要件に対する必須受入テストです。

11. エラー判定の優先順位

競合しやすいケースについて、契約上は次の優先順位が安全です。

1. Authentication
2. Requestの構文・schema validation
3. resource visibility
4. action authorization
5. state transition
6. business operation

したがって、有効なrequestについては、

他人のID → 404
自分のIDだがrole不足 → 403
自分のIDでroleも正しいが状態違反 → 409

となります。

ただしFastAPIの自動Pydantic validationをそのまま使うと、body不正の場合はobject lookupより先に422になることがあります。

これは例えば、

他人のexpense_id + amount=0

でも422になる可能性があるということです。

この挙動自体から「そのIDが存在するか」は判別できないため、今回の秘匿条件とは矛盾しません。存在する他人IDと存在しないIDに対し、同じ入力なら同じ結果になることをテストすべきです。

12. HTTP status / body整合性チェック

今回の契約では矛盾はありません。

HTTP	意味	Body
200	GET/更新/状態遷移成功	success JSON
201	新規expense作成成功	success JSON
204	DELETE成功	bodyなし
401	認証失敗	error JSON
403	objectは見えるが操作権限なし	error JSON
404	nonexistent または invisible object	同一error JSON
409	resource状態/idempotency競合	error JSON
422	request schema不正	error JSON

特に避けるべきなのは 204 + JSON body、200 + {"error":...}、validationでFastAPI標準形式と既存API形式が混在する状態です。

13. 未確定事項

実装前に決める必要があるのは次の点です。

admin自身の申請をadmin自身が承認できるか。 現契約だけなら可能ですが、経費承認では自己承認禁止にするケースがあります。禁止なら admin + owner の approve/return を403にします。

adminはmember能力も持つか。 上表では「adminも自分のexpenseを作成・編集・申請できる」と仮定しています。adminが純粋な承認者なら変更が必要です。

month が何の月か。 現在resourceに expense_date がないため、候補は created_at の月です。ただし「9月に使った8月分経費」を扱うなら業務要件として不十分なので、expense_date が必要です。

month判定のtimezone。 社内業務が日本限定なら Asia/Tokyo 固定が候補ですが、契約として明記が必要です。

submittedの削除/取り下げ。 今回は不可としました。必要なら withdraw の状態遷移を追加する方が、DELETEで申請取消を表現するより明確です。

receipt_url: null をPATCHで送った場合。 「削除する」のか「不正」とするのかを決める必要があります。省略は「変更なし」で問題ありません。

Idempotency-Key保持期間。 24時間などのTTLを決める必要があります。通信再送防止だけなら24時間程度が候補ですが、業務側の再送期間から決めるべきです。

expense_id形式。 UUID、ULID、独自opaque IDのどれか。外部利用者にDB連番を直接公開しない形が適しています。

一覧のpagination。 件数上限、cursor方式、total を返すかが未定です。adminが全組織申請を閲覧するため、実装前に上限を決めた方が安全です。

この契約なら、次にFastAPI実装へ進む際は Pydantic schema → JWT dependency → visible-object lookup → authorization → state transition service → custom exception handler → idempotency storage の境界をかなり明確に分けられます。

この出力になっていれば成功

  • 各endpointにauth/owner・正常/主要失敗がある
  • statusとbody整合
  • 秘密非露出
  • 他人IDテスト

使うときの注意

実行前にGit差分またはバックアップを確認し、テスト環境で検証してください。

権限/所有関係や既存error契約が未定なら保留して質問。403/404等を独断で固定しない。

参考にした一次情報: Python Docs / Stack Overflow

ZEN PROMPT LIBRARY

やりたいことから、
次のPromptを探す。