{
  "openapi": "3.1.0",
  "info": {
    "title": "Home Visit Trust Diagnosis API",
    "version": "1.0.0",
    "description": "自宅訪問型サービスの契約前確認事項を整理する診断APIです。業者の安全性、防犯効果、身元、反社該当性などを保証するものではありません。本番のPOST /api/diagnoseとPOST /api/agent/diagnoseはAuthorization: Bearer tokenを必須にします。CORSはAPI_ALLOWED_ORIGINS、APIキーはDIAGNOSIS_API_KEYS、軽量レート制限はDIAGNOSIS_API_RATE_LIMIT_PER_MINUTEで設定します。"
  },
  "servers": [
    {
      "url": "/",
      "description": "Same origin"
    }
  ],
  "paths": {
    "/api/diagnose": {
      "post": {
        "summary": "訪問型サービス業者の確認事項を診断する",
        "description": "料金条件、許認可情報、防犯配慮、作業証跡、高齢者対応などをもとに、スコア、未確認項目、契約前に確認すべき質問を返します。入力内容はこのAPI内で永続保存しません。",
        "operationId": "diagnoseVendor",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VendorDiagnosisInput"
              },
              "examples": {
                "minimalUnknown": {
                  "summary": "未確認が多い入力例",
                  "value": {
                    "vendorName": "サンプル回収",
                    "vendorUrl": "",
                    "phoneNumber": "",
                    "category": "不用品回収",
                    "estimateAmount": "",
                    "hasPriceList": "unknown",
                    "hasAdditionalFeePolicy": "unknown",
                    "hasCancellationPolicy": "unknown",
                    "hasLicenseInfo": "unknown",
                    "hasSecondhandDealerLicense": "unknown",
                    "hasWasteDisposalInfo": "unknown",
                    "hasApplianceRecycleInfo": "unknown",
                    "listsUnsupportedItems": "unknown",
                    "explainsDisposalMethod": "unknown",
                    "notifiesWorkerName": "unknown",
                    "showsWorkerPhotoOrIdVerified": "unknown",
                    "sharesVehicleInfo": "unknown",
                    "providesBeforeAfterReport": "unknown",
                    "bansPersonalPhonePhotos": "unknown",
                    "confirmsWorkScope": "unknown",
                    "supportsElderlyAlone": "unknown",
                    "supportsFamilySharing": "unknown",
                    "requiresFamilyApprovalForExtraFees": "unknown",
                    "requiresFamilyApprovalForPurchase": "unknown",
                    "offersVisitPurchase": "unknown",
                    "proposesSameDayAdditionalPurchase": "unknown",
                    "usesSubcontractors": "unknown",
                    "disclosesSubcontractors": "unknown",
                    "supportsWebOrLine": "unknown",
                    "hasFastReply": "unknown",
                    "hasFastEstimate": "unknown",
                    "hasWorkExamples": "unknown",
                    "hasInsuranceInfo": "unknown",
                    "hasValuablesRule": "unknown",
                    "reviewCount": "",
                    "reviewRating": "",
                    "reviewConcernMemo": "",
                    "notes": ""
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "診断結果",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosisApiResponse"
                }
              }
            }
          },
          "400": {
            "description": "JSON不正、必須項目不足、または入力値不正",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosisApiError"
                }
              }
            }
          },
          "401": {
            "description": "DIAGNOSIS_API_KEYS設定時にBearer tokenが不足または不一致",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosisApiError"
                }
              }
            }
          },
          "403": {
            "description": "許可されていないOrigin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosisApiError"
                }
              }
            }
          },
          "429": {
            "description": "軽量レート制限の超過",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "再試行までの秒数"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosisApiError"
                }
              }
            }
          },
          "405": {
            "description": "POST以外のメソッド"
          }
        }
      }
    },
    "/api/agent/diagnose": {
      "post": {
        "summary": "AIエージェント向けに診断結果をメタデータ付きで返す",
        "description": "AIエージェントやチャットボットが、ユーザーから得た業者の公開情報をVendorDiagnosisInputとして整理し、契約前チェックをJSONで返すためのエンドポイントです。住所、身分証、口座情報、非公開の契約本文などの個人情報・非公開情報は送らないでください。Bearer認証とCORS制御の対象です。既存の診断結果をresultに入れ、requestId、billing、quota、usageを付与して返します。billing、quota、usageは固定メタデータであり、実際の課金状態、決済状態、残り利用量を表しません。入力内容はこのAPI内で永続保存しません。現状はBearer認証後に200または400/401/403/429/405を返します。402と支払い証明ヘッダは将来の課金モード候補であり、本番の支払い検証には使っていません。",
        "operationId": "agentDiagnoseVendor",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XPaymentProofCandidate"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VendorDiagnosisInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "requestIdと固定課金メタデータを含む診断結果",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentDiagnosisApiResponse"
                }
              }
            }
          },
          "400": {
            "description": "JSON不正、必須項目不足、または入力値不正",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosisApiError"
                }
              }
            }
          },
          "401": {
            "description": "DIAGNOSIS_API_KEYS設定時にBearer tokenが不足または不一致",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosisApiError"
                }
              }
            }
          },
          "402": {
            "description": "将来の課金モード候補です。現状のPOST /api/agent/diagnoseは支払い証明を検証せず、この応答を本番契約フローとして返しません。402の取り扱い練習はGET /api/billing/payment-required-demoを参照してください。",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentRequiredPlaceholderResponse"
                }
              }
            }
          },
          "403": {
            "description": "許可されていないOrigin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosisApiError"
                }
              }
            }
          },
          "429": {
            "description": "軽量レート制限の超過",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "再試行までの秒数"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosisApiError"
                }
              }
            }
          },
          "405": {
            "description": "POST以外のメソッド"
          }
        }
      }
    },
    "/api/billing/checkout-session": {
      "post": {
        "summary": "Stripe Checkout Sessionを作成する",
        "description": "パートナー登録または支払い意思確認の限定パイロット用に、Stripe Checkout SessionのURLだけを返します。Webhook、APIキー自動発行、クォータ連携は行いません。BILLING_CHECKOUT_TOKENで設定したBearer tokenが必要です。",
        "operationId": "createBillingCheckoutSession",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Stripe Checkout URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingCheckoutSessionResponse"
                }
              }
            }
          },
          "401": {
            "description": "BILLING_CHECKOUT_TOKENとBearer tokenが不一致、または未設定",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosisApiError"
                }
              }
            }
          },
          "502": {
            "description": "StripeからCheckout URLが返らなかった場合",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosisApiError"
                }
              }
            }
          },
          "503": {
            "description": "Stripe Checkout用のサーバ環境変数が未設定",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosisApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/billing/payment-required-demo": {
      "get": {
        "summary": "402 Payment Requiredのデモ応答を返す",
        "description": "x402 / MPPの本番決済は行わず、クライアントが402応答を扱う練習をするためのデモエンドポイントです。常に402とデモ用JSONを返し、ウォレット検証、課金処理、支払い証明検証は行いません。",
        "operationId": "getPaymentRequiredDemo",
        "responses": {
          "402": {
            "description": "支払いが必要なAPI応答を扱う練習用のデモ。実際の決済は行いません。",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentRequiredDemoResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "本番では対象APIごとにサーバ側で設定したBearer tokenを要求します。診断APIはDIAGNOSIS_API_KEYS、Checkout Session APIはBILLING_CHECKOUT_TOKENを使います。キー値は公開ドキュメントやNEXT_PUBLIC_*には置きません。"
      },
      "paymentProofCandidate": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Payment-Proof",
        "description": "将来のMPP/x402等の支払い証明候補です。現状の本番APIでは必須ではなく、検証も行いません。Bearer tokenとは別枠の候補として記載しています。"
      }
    },
    "parameters": {
      "XPaymentProofCandidate": {
        "name": "X-Payment-Proof",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "将来のMPP/x402等の支払い証明候補です。現状のPOST /api/agent/diagnoseでは任意であり、サーバ側で検証しません。"
      }
    },
    "schemas": {
      "YesNoUnknown": {
        "oneOf": [
          {
            "type": "boolean"
          },
          {
            "type": "string",
            "const": "unknown"
          }
        ],
        "description": "true は「はい」、false は「いいえ」、unknown は「わからない」を表します。AIエージェントは、公開情報から確認できなかった項目に unknown を使ってください。unknown は未確認として扱われ、スコアや未確認リストでは保守的な確認対象になります。"
      },
      "VendorCategory": {
        "type": "string",
        "enum": [
          "不用品回収",
          "遺品整理",
          "訪問買取",
          "便利屋/片付け代行"
        ]
      },
      "VendorDiagnosisInput": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "vendorName",
          "vendorUrl",
          "phoneNumber",
          "category",
          "estimateAmount",
          "hasPriceList",
          "hasAdditionalFeePolicy",
          "hasCancellationPolicy",
          "hasLicenseInfo",
          "hasSecondhandDealerLicense",
          "hasWasteDisposalInfo",
          "hasApplianceRecycleInfo",
          "listsUnsupportedItems",
          "explainsDisposalMethod",
          "notifiesWorkerName",
          "showsWorkerPhotoOrIdVerified",
          "sharesVehicleInfo",
          "providesBeforeAfterReport",
          "bansPersonalPhonePhotos",
          "confirmsWorkScope",
          "supportsElderlyAlone",
          "supportsFamilySharing",
          "requiresFamilyApprovalForExtraFees",
          "requiresFamilyApprovalForPurchase",
          "offersVisitPurchase",
          "proposesSameDayAdditionalPurchase",
          "usesSubcontractors",
          "disclosesSubcontractors",
          "supportsWebOrLine",
          "hasFastReply",
          "hasFastEstimate",
          "hasWorkExamples",
          "hasInsuranceInfo",
          "hasValuablesRule",
          "reviewCount",
          "reviewRating",
          "reviewConcernMemo",
          "notes"
        ],
        "properties": {
          "vendorName": {
            "type": "string"
          },
          "vendorUrl": {
            "type": "string"
          },
          "phoneNumber": {
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/VendorCategory"
          },
          "estimateAmount": {
            "type": "string"
          },
          "hasPriceList": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "hasAdditionalFeePolicy": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "hasCancellationPolicy": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "hasLicenseInfo": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "hasSecondhandDealerLicense": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "hasWasteDisposalInfo": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "hasApplianceRecycleInfo": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "listsUnsupportedItems": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "explainsDisposalMethod": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "notifiesWorkerName": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "showsWorkerPhotoOrIdVerified": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "sharesVehicleInfo": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "providesBeforeAfterReport": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "bansPersonalPhonePhotos": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "confirmsWorkScope": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "supportsElderlyAlone": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "supportsFamilySharing": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "requiresFamilyApprovalForExtraFees": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "requiresFamilyApprovalForPurchase": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "offersVisitPurchase": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "proposesSameDayAdditionalPurchase": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "usesSubcontractors": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "disclosesSubcontractors": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "supportsWebOrLine": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "hasFastReply": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "hasFastEstimate": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "hasWorkExamples": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "hasInsuranceInfo": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "hasValuablesRule": {
            "$ref": "#/components/schemas/YesNoUnknown"
          },
          "reviewCount": {
            "type": "string"
          },
          "reviewRating": {
            "type": "string"
          },
          "reviewConcernMemo": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          }
        }
      },
      "ScoreBreakdown": {
        "type": "object",
        "required": [
          "contractSafety",
          "licenseTransparency",
          "crimePreventionPrivacy",
          "workQuality",
          "elderlySupport"
        ],
        "properties": {
          "contractSafety": {
            "type": "integer"
          },
          "licenseTransparency": {
            "type": "integer"
          },
          "crimePreventionPrivacy": {
            "type": "integer"
          },
          "workQuality": {
            "type": "integer"
          },
          "elderlySupport": {
            "type": "integer"
          }
        }
      },
      "RiskLevel": {
        "type": "object",
        "required": [
          "label",
          "description",
          "tone"
        ],
        "properties": {
          "label": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "tone": {
            "type": "string",
            "enum": [
              "highScore",
              "midScore",
              "needsPriorCheck"
            ]
          }
        }
      },
      "DiagnosisApiResponse": {
        "type": "object",
        "required": [
          "apiVersion",
          "disclaimer",
          "totalScore",
          "scores",
          "scoreReasons",
          "riskLevel",
          "flags",
          "unknownItems",
          "questions",
          "checklist",
          "recommendsFamilySharing",
          "familySharingReasons",
          "priorityActions"
        ],
        "properties": {
          "apiVersion": {
            "type": "string",
            "const": "1"
          },
          "disclaimer": {
            "type": "string"
          },
          "totalScore": {
            "type": "integer"
          },
          "scores": {
            "$ref": "#/components/schemas/ScoreBreakdown"
          },
          "scoreReasons": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "riskLevel": {
            "$ref": "#/components/schemas/RiskLevel"
          },
          "flags": {
            "type": "array",
            "description": "リスク上の注意を短文で列挙します。AIエージェントは、断定的な危険表現に変えず、契約前の注意点としてエンドユーザーに提示できます。",
            "items": {
              "type": "string"
            }
          },
          "unknownItems": {
            "type": "array",
            "description": "公開情報や入力から確認できなかった項目です。AIエージェントは、業者へ追加確認する項目としてエンドユーザーに示せます。",
            "items": {
              "type": "string"
            }
          },
          "questions": {
            "type": "array",
            "description": "契約前に業者へ確認するための質問文です。AIエージェントは、ユーザーがそのまま聞ける質問リストとして提示できます。",
            "items": {
              "type": "string"
            }
          },
          "checklist": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "recommendsFamilySharing": {
            "type": "boolean"
          },
          "familySharingReasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "priorityActions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "AgentDiagnosisApiResponse": {
        "type": "object",
        "description": "AIエージェント向けの診断レスポンスです。billing、quota、usageは固定メタデータであり、実際の課金状態、決済状態、残り利用量を表しません。",
        "required": [
          "requestId",
          "result",
          "billing",
          "quota",
          "usage"
        ],
        "properties": {
          "requestId": {
            "type": "string",
            "description": "問い合わせやログ照合に使うサーバ生成ID"
          },
          "result": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DiagnosisApiResponse"
              }
            ],
            "description": "診断結果本体です。AIエージェントは、result.flags、result.unknownItems、result.questions、result.priorityActionsを要約や箇条書きに転用できます。誤解を避けるため、必要に応じてresult.disclaimerの趣旨を返答に含めてください。"
          },
          "billing": {
            "$ref": "#/components/schemas/BillingMetadata"
          },
          "quota": {
            "$ref": "#/components/schemas/QuotaMetadata"
          },
          "usage": {
            "$ref": "#/components/schemas/UsageMetadata"
          }
        }
      },
      "BillingMetadata": {
        "type": "object",
        "description": "固定値です。実際の課金状態を表しません。",
        "required": [
          "status",
          "mode",
          "plan",
          "metered",
          "note"
        ],
        "properties": {
          "status": {
            "type": "string",
            "const": "included"
          },
          "mode": {
            "type": "string",
            "const": "manual_contract"
          },
          "plan": {
            "type": "string",
            "const": "partner_trial"
          },
          "metered": {
            "type": "boolean"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "QuotaMetadata": {
        "type": "object",
        "description": "固定値です。DBやクォータストアには接続していません。",
        "required": [
          "limit",
          "used",
          "remaining",
          "resetAt",
          "note"
        ],
        "properties": {
          "limit": {
            "type": "integer"
          },
          "used": {
            "type": "integer"
          },
          "remaining": {
            "type": "integer"
          },
          "resetAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "UsageMetadata": {
        "type": "object",
        "required": [
          "unit",
          "count"
        ],
        "properties": {
          "unit": {
            "type": "string",
            "const": "diagnosis"
          },
          "count": {
            "type": "integer",
            "const": 1
          }
        }
      },
      "DiagnosisApiError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "details": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "BillingCheckoutSessionResponse": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Stripe Checkoutの遷移先URL"
          }
        }
      },
      "PaymentRequiredDemoResponse": {
        "type": "object",
        "required": [
          "type",
          "message",
          "docs"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "demo"
          },
          "message": {
            "type": "string",
            "description": "402応答の扱いを確認するためのデモ説明文"
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "description": "x402 / MPP検証メモへの公開URL"
          }
        }
      },
      "PaymentRequiredPlaceholderResponse": {
        "type": "object",
        "description": "将来の課金モードで使う可能性がある402応答のプレースホルダです。現状のPOST /api/agent/diagnoseでは本番契約フローとして返しません。",
        "required": [
          "error",
          "message",
          "payment"
        ],
        "properties": {
          "error": {
            "type": "string",
            "const": "Payment required"
          },
          "message": {
            "type": "string",
            "description": "支払いまたは契約確認が必要であることを説明する文言"
          },
          "payment": {
            "type": "object",
            "required": [
              "mode",
              "proofHeader",
              "demoEndpoint"
            ],
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "manual_contract",
                  "stripe",
                  "x402_candidate",
                  "mpp_candidate"
                ],
                "description": "将来候補を含む課金モード名"
              },
              "proofHeader": {
                "type": "string",
                "const": "X-Payment-Proof",
                "description": "将来の支払い証明ヘッダ候補"
              },
              "demoEndpoint": {
                "type": "string",
                "const": "/api/billing/payment-required-demo",
                "description": "現状利用できる402デモエンドポイント"
              }
            }
          }
        }
      }
    }
  }
}
