{
 "openapi": "3.0.3",
 "info": {
  "title": "ai-sns.io Kitting REST API",
  "version": "1.0.0",
  "description": "キッティングセンター管制SaaS の REST API。全 187 エンドポイント。応答は原則 JSON `{ok:true,...}` / エラーは `{ok:false,error}`。",
  "contact": {
   "name": "Freeborn Inc.",
   "email": "ai.sns.io.official@gmail.com"
  }
 },
 "servers": [
  {
   "url": "https://aisnsio-kitting.hf.space"
  }
 ],
 "tags": [
  {
   "name": "_ping",
   "description": "Health / mount check."
  },
  {
   "name": "metrics",
   "description": "ダッシュボード指標・アラート。"
  },
  {
   "name": "devices",
   "description": "IMEI単位の端末台帳 CRUD・検索・一括操作。"
  },
  {
   "name": "jobs",
   "description": "8段階の案件カード（キッティング工程）。"
  },
  {
   "name": "shipments",
   "description": "入荷・出荷・在庫ロケーション。"
  },
  {
   "name": "tickets",
   "description": "サポートチケットと対応履歴。"
  },
  {
   "name": "customers",
   "description": "顧客アカウント・更新・商談。"
  },
  {
   "name": "quote",
   "description": "見積の作成・改訂・PDF元データ。"
  },
  {
   "name": "invoices",
   "description": "請求書・売上サマリ。"
  },
  {
   "name": "shift",
   "description": "現場スタッフのシフト計画。"
  },
  {
   "name": "dock",
   "description": "バース予約・受入スケジュール。"
  },
  {
   "name": "erasure",
   "description": "ワイプ実績と消去証明書。"
  },
  {
   "name": "replace",
   "description": "バッテリー劣化・更新提案。"
  },
  {
   "name": "network",
   "description": "拠点回線・機器監視。"
  },
  {
   "name": "forecast",
   "description": "16週の需要・要員シミュレーション。"
  },
  {
   "name": "labels",
   "description": "管理番号・IMEI・資産QR印刷。"
  },
  {
   "name": "survey",
   "description": "CSAT / NPS 回収。"
  },
  {
   "name": "onboarding",
   "description": "導入チェックリスト。"
  },
  {
   "name": "team",
   "description": "メンバー・権限・稼働。"
  },
  {
   "name": "settings",
   "description": "テナント設定・機能フラグ。"
  },
  {
   "name": "integrations",
   "description": "Teams / Slack / LINE WORKS 等。"
  },
  {
   "name": "compliance",
   "description": "ISMS/Pマーク/SOC2/APPI・監査ログ・CAPA。"
  },
  {
   "name": "workdesk",
   "description": "社内ナレッジ・依頼。"
  },
  {
   "name": "esg",
   "description": "リユース・CO2・サステナビリティ指標。"
  },
  {
   "name": "portal",
   "description": "顧客共有ビュー。"
  },
  {
   "name": "qbr",
   "description": "四半期ビジネスレビュー。"
  },
  {
   "name": "nurture",
   "description": "リード育成・シーケンス。"
  },
  {
   "name": "server:admin",
   "description": "主管理者認証・テナント管理・サブ管理者CRUD。要adminトークン"
  },
  {
   "name": "server:subadmin",
   "description": "担当クライアント限定の運用（メモ・ファイル・削除）。要subadminトークン"
  },
  {
   "name": "server:backup",
   "description": "日次バックアップ・状態エクスポート。要BACKUP_TOKEN"
  },
  {
   "name": "server:misc",
   "description": "申込/パートナー/予約/署名/CRM・OAuth連携。"
  }
 ],
 "components": {
  "securitySchemes": {
   "bearerAuth": {
    "type": "http",
    "scheme": "bearer",
    "description": "POST /api/admin/login または /api/subadmin/login で取得したトークンを Authorization: Bearer <token> で送信"
   }
  }
 },
 "paths": {
  "/api/ping": {
   "get": {
    "tags": [
     "_ping"
    ],
    "summary": "Health/mount check returning ok flag, current epoch ms as pong, and mounted:true to verify the routes/ auto-mount works.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"pong\":1784675483941,\"mounted\":true}"
       }
      }
     }
    }
   }
  },
  "/api/metrics/overview": {
   "get": {
    "tags": [
     "metrics"
    ],
    "summary": "Headline KPI cards (revenue forecast, MRR, pipeline, kitting count, gross margin, recurring customers) with display strings byte-matched to the inline demo plus counts derived from live shared collections.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"month\":\"令和8年7月度\",\"revenueForecast\":{\"display\":\"¥1.86\",\"unit\":\"M\",\"total\":1860000,\"oneTime\":930000,\"recurring\":930000,\"deltaPct\":12.4},\"mrr\":{\"display\":\"¥930\",\"unit\":\"K\",\"amount\":930000,\"projected\":1400000,\"invoiceRecurring\":550},\"pipeline\":{\"display\":\"¥6.4\",\"unit\":\"M\",\"amount\":6400000,\"deals\":3},\"kittingMonth\":{\"display\":\"412\",\"unit\":\"台\",\"count\":412,\"jobs\":N,\"shipments\":N},\"grossMargin\":{\"display\":\"54.2\",\"unit\":\"%\",\"bar\":54,\"pct\":54.2},\"recurringCustomers\":{\"display\":\"2\",\"unit\":\"社\",\"active\":N,\"proposed\":N,\"rows\":[{\"label\":\"北斗商事（500台・Intune）\",\"amount\":\"¥380K/月\"}]},\"aggregates\":{\"customers\":N,\"jobs\":N,\"shipments\":N,\"invoices\":N}}"
       }
      }
     }
    }
   }
  },
  "/api/revenue/mix": {
   "get": {
    "tags": [
     "metrics"
    ],
    "summary": "12-month revenue-mix series (months labels, recurring, onetime 万円 arrays) for #revChart; optional ?months=N trims to last N points.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"months\":[\"25/8\",\"25/9\",\"25/10\",\"25/11\",\"25/12\",\"26/1\",\"26/2\",\"26/3\",\"26/4\",\"26/5\",\"26/6\",\"26/7\"],\"recurring\":[38,40,42,46,46,48,50,50,93,93,140,140],\"onetime\":[22,65,18,120,34,52,88,93,318,282,60,72]}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "months",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Return only the last N points. Must be 1..120 or a 400 is returned. Default = full 12-point series."
     }
    ]
   }
  },
  "/api/ops/today": {
   "get": {
    "tags": [
     "metrics"
    ],
    "summary": "Today's operation rows (#todayList): id/time/chip/chipLabel/title/detail/status/statusColor, clipped to length limits.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"ops\":[{\"id\":\"op-001\",\"time\":\"09:30\",\"chip\":\"c-cyan\",\"chipLabel\":\"作業\",\"title\":\"北斗商事 交換12台 再キッティング（Intune再登録）\",\"detail\":\"担当：佐々木 ｜ Zone A ｜ チェックリスト 8/12 完了\",\"status\":\"進行中\",\"statusColor\":\"var(--cyan)\"}]}"
       }
      }
     }
    }
   }
  },
  "/api/alerts": {
   "get": {
    "tags": [
     "metrics"
    ],
    "summary": "Alert list (#alertList) with per-alert action (label/danger/toast) plus lateCount = number of unacked late alerts.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"alerts\":[{\"id\":\"alert-001\",\"label\":\"出荷漏れ\",\"late\":true,\"ack\":false,\"chip\":\"c-red\",\"chipLabel\":\"重大\",\"what\":\"北斗商事 交換2台 — 予定 07/10 を超過\",\"detail\":\"...\",\"action\":{\"label\":\"対応\",\"danger\":true,\"toast\":\"本日出荷便に組込みました…（デモ）\"}}],\"lateCount\":1}"
       }
      }
     }
    }
   }
  },
  "/api/alert/{id}/ack": {
   "post": {
    "tags": [
     "metrics"
    ],
    "summary": "Mark one alert acknowledged (sets ack=true, acked=timestamp) and returns the updated alert.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"alert\":{\"id\":\"alert-001\",\"label\":\"出荷漏れ\",\"ack\":true,\"acked\":1710000000000,...}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Alert id from metrics-alerts seed, e.g. 'alert-001'. 404 {ok:false,error:'alert not found'} if unknown."
     }
    ]
   }
  },
  "/api/devices": {
   "get": {
    "tags": [
     "devices"
    ],
    "summary": "List all devices in the ledger collection (seeded with 178 mulberry(42)-generated records keyed by imei).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"devices\":[{\"imei\":\"356247446539346\",\"serial\":\"F2LXD73EB4\",\"asset\":\"HK-26-1001\",\"model\":\"iPhone 15\",\"maker\":\"Apple\",\"cust\":\"北斗商事\",\"mdm\":\"Intune\",\"status\":\"保管中\",\"chip\":\"c-blue\",...}],\"count\":178}"
       }
      }
     }
    }
   }
  },
  "/api/device": {
   "post": {
    "tags": [
     "devices"
    ],
    "summary": "Create one device from partial input; generates a fresh unique imei if absent, fills defaults via makeDevice.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"device\":{\"imei\":\"35...\",\"model\":\"iPhone 16\",\"cust\":\"社内検証機\",\"status\":\"入荷待ち\",\"chip\":\"c-gray\",...}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"imei?\":\"str\",\"model?\":\"str\",\"cust?\":\"str\",\"status?\":\"str\",\"mdm?\":\"str\",\"serial?\":\"str\",\"asset?\":\"str\",\"bat?\":100,\"...other device fields\":\"str\"}"
      }
     }
    }
   }
  },
  "/api/devices/bulk": {
   "post": {
    "tags": [
     "devices"
    ],
    "summary": "Create N devices from {model,cust,mdm,status,qty}; returns the created[] array.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"created\":[{\"imei\":\"35...\",\"model\":\"iPhone 16\",...}],\"count\":5}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"qty\":5,\"model?\":\"iPhone 16\",\"cust?\":\"北斗商事\",\"mdm?\":\"Intune\",\"status?\":\"保管中\"}"
      }
     }
    }
   }
  },
  "/api/device/{imei}": {
   "post": {
    "tags": [
     "devices"
    ],
    "summary": "Update fields of an existing device by imei; imei key is immutable, bat passed raw, other fields clipped, chip recomputed when status changes.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"device\":{\"imei\":\"356247446539346\",\"status\":\"作業中\",\"chip\":\"c-cyan\",...}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "imei",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "IMEI of the device to update (e.g. seed value 356247446539346)."
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"status?\":\"作業中\",\"loc?\":\"B-05\",\"user?\":\"佐藤 健\",\"bat?\":88,\"note?\":\"str\"}"
      }
     }
    }
   }
  },
  "/api/device/{imei}/delete": {
   "post": {
    "tags": [
     "devices"
    ],
    "summary": "Remove one device by imei from the collection.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"imei\":\"356247446539346\"}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "imei",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "IMEI of the device to delete (e.g. seed value 356247446539346)."
     }
    ]
   }
  },
  "/api/jobs": {
   "get": {
    "tags": [
     "jobs"
    ],
    "summary": "List all job (kanban) cards from the in-memory jobs collection.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"jobs\":[{\"id\":\"job-001\",\"c\":\"東成建設\",\"t\":\"PoC 20台（iPhone 16e・Intune新規）\",\"s\":0,\"due\":\"07/22\",\"ch\":\"c-blue\",\"tag\":\"新規\"}, ...9 cards]}"
       }
      }
     }
    }
   }
  },
  "/api/job": {
   "post": {
    "tags": [
     "jobs"
    ],
    "summary": "Create a job card; server assigns id (job-…) and defaults stage to 0.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"job\":{\"id\":\"job-<generated>\",\"c\":\"...\",\"t\":\"...\",\"s\":0,\"due\":\"...\",\"ch\":\"c-blue\",\"tag\":\"...\"}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"c\":\"string\",\"t\":\"string\",\"s\":0,\"due\":\"string\",\"ch\":\"c-blue\",\"tag\":\"string\"}"
      }
     }
    }
   }
  },
  "/api/job/{id}": {
   "post": {
    "tags": [
     "jobs"
    ],
    "summary": "Update stage/fields of an existing job card; only provided (non-null) fields are changed.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"job\":{\"id\":\"job-001\",\"c\":\"...\",\"t\":\"...\",\"s\":3,\"due\":\"...\",\"ch\":\"...\",\"tag\":\"...\"}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Job id e.g. 'job-001'"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"c\":\"string\",\"t\":\"string\",\"s\":3,\"due\":\"string\",\"ch\":\"c-cyan\",\"tag\":\"string\"} (all optional)"
      }
     }
    }
   }
  },
  "/api/job/{id}/delete": {
   "post": {
    "tags": [
     "jobs"
    ],
    "summary": "Delete a job card by id.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"id\":\"job-005\"}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Job id to delete e.g. 'job-005'"
     }
    ]
   }
  },
  "/api/shipments": {
   "get": {
    "tags": [
     "shipments"
    ],
    "summary": "List all in/out shipments (入出荷) on the IN/OUT board.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"shipments\":[{\"id\":\"SHP-0001\",\"late\":true,\"created\":1784673357631,\"updated\":1784673357631,\"date\":\"07/10\",\"kind\":\"out\",\"customer\":\"北斗商事\",\"detail\":\"交換端末 2台 → 仙台支店…\",\"carrier\":\"\",\"status\":\"超過\"}]}"
       }
      }
     }
    }
   }
  },
  "/api/shipment": {
   "post": {
    "tags": [
     "shipments"
    ],
    "summary": "Create a shipment; server assigns SHP-#### id.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"shipment\":{\"id\":\"SHP-0007\",\"date\":\"07/20\",\"kind\":\"out\",\"customer\":\"三峰精機\",\"detail\":\"…\",\"carrier\":\"ヤマトB2\",\"status\":\"予定\",\"late\":false,\"created\":1784...,\"updated\":1784...}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"kind\":\"out\",\"customer\":\"三峰精機\",\"date\":\"07/20\",\"detail\":\"8台 → 大阪支店\",\"carrier\":\"ヤマトB2\",\"status\":\"予定\",\"late\":false}"
      }
     }
    }
   }
  },
  "/api/shipment/{id}": {
   "post": {
    "tags": [
     "shipments"
    ],
    "summary": "Advance status / update fields of an existing shipment.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"shipment\":{\"id\":\"SHP-0001\",\"status\":\"出荷済\",\"late\":false,\"updated\":1784...}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Shipment id e.g. SHP-0001"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"status\":\"出荷済\"}"
      }
     }
    }
   }
  },
  "/api/shipment/{id}/delete": {
   "post": {
    "tags": [
     "shipments"
    ],
    "summary": "Delete a shipment by id.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Shipment id e.g. SHP-0001"
     }
    ]
   }
  },
  "/api/tickets": {
   "get": {
    "tags": [
     "tickets"
    ],
    "summary": "List all support tickets from the tickets collection (seeded with 6 demo tickets TK-0667..TK-0672).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"tickets\":[{\"id\":\"TK-0672\",\"cust\":\"北斗商事\",\"kind\":\"紛失\",\"subject\":\"営業車内で紛失 → 位置特定＆リモートワイプ要請\",\"owner\":\"佐々木\",\"sla\":\"1h 12m\",\"status\":\"対応中\"}]}"
       }
      }
     }
    }
   }
  },
  "/api/ticket": {
   "post": {
    "tags": [
     "tickets"
    ],
    "summary": "Create a new ticket; auto-assigns id TK-0<next num> (starts at 673) and created timestamp, clips input fields.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"ticket\":{\"id\":\"TK-0673\",\"cust\":\"—\",\"kind\":\"問合せ\",\"subject\":\"\",\"owner\":\"—\",\"sla\":\"4h 00m\",\"status\":\"受付\",\"priority\":\"\",\"created\":1700000000000}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"cust\":\"string\",\"kind\":\"string\",\"subject\":\"string\",\"owner\":\"string\",\"sla\":\"string\",\"status\":\"string\",\"priority\":\"string\"}"
      }
     }
    }
   }
  },
  "/api/ticket/{id}": {
   "post": {
    "tags": [
     "tickets"
    ],
    "summary": "Update an existing ticket's status/owner/sla/subject/cust/kind/priority (only provided non-null fields).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"ticket\":{\"id\":\"TK-0672\",\"status\":\"解決\",...}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Ticket id, e.g. TK-0672."
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"status\":\"string\",\"owner\":\"string\",\"sla\":\"string\",\"subject\":\"string\",\"cust\":\"string\",\"kind\":\"string\",\"priority\":\"string\"}"
      }
     }
    }
   }
  },
  "/api/ticket/{id}/delete": {
   "post": {
    "tags": [
     "tickets"
    ],
    "summary": "Delete a ticket and its associated messages.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Ticket id to delete, e.g. TK-0672."
     }
    ]
   }
  },
  "/api/ticket/{id}/messages": {
   "get": {
    "tags": [
     "tickets"
    ],
    "summary": "List all messages for a given ticket (empty array if none recorded).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"messages\":[]}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Ticket id, e.g. TK-0672."
     }
    ]
   }
  },
  "/api/ticket/{id}/message": {
   "post": {
    "tags": [
     "tickets"
    ],
    "summary": "Append a message {from,text} to a ticket's message list; assigns msg id and timestamp.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"message\":{\"id\":\"msg-xxxx\",\"from\":\"田中\",\"text\":\"...\",\"ts\":1700000000000}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Ticket id, e.g. TK-0672."
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"from\":\"string\",\"text\":\"string (required)\"}"
      }
     }
    }
   }
  },
  "/api/customers": {
   "get": {
    "tags": [
     "customers"
    ],
    "summary": "List all customer accounts (id, name, industry, mdm, monthly, devices, since, status, note, renew).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"customers\":[{\"id\":\"cust-mrv8frxlg\",\"key\":\"hokuto\",\"name\":\"北斗商事\",\"industry\":\"大手商社 ｜ 500台（iPhone）\",\"mdm\":\"Intune（Clomo→移行済）\",\"monthly\":\"¥380,000\",\"devices\":\"500 台\",\"since\":\"2025/04\",\"status\":\"運用中\",\"statusChip\":\"c-green\",\"note\":\"豊島倉庫 専用エリア ¥45K/月\",\"renew\":{\"date\":\"2027-03-31\",...}}]}"
       }
      }
     }
    }
   }
  },
  "/api/customer": {
   "post": {
    "tags": [
     "customers"
    ],
    "summary": "Create a new customer account; requires name; returns generated id and full record.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"id\":\"cust-xxxx\",\"customer\":{\"id\":\"cust-xxxx\",\"name\":\"...\",\"industry\":\"\",\"mdm\":\"\",\"monthly\":\"—\",\"devices\":\"—\",\"since\":\"—\",\"status\":\"\",\"statusChip\":\"c-gray\",\"note\":\"\",\"renew\":null}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"...\",\"industry\":\"...\",\"mdm\":\"...\",\"monthly\":\"...\",\"devices\":\"...\",\"since\":\"...\",\"status\":\"...\",\"statusChip\":\"...\",\"note\":\"...\",\"key\":\"...\",\"renew\":{...}|null}"
      }
     }
    }
   }
  },
  "/api/customer/{id}": {
   "post": {
    "tags": [
     "customers"
    ],
    "summary": "Update fields of an existing customer by id; 404 if id not found.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"customer\":{\"id\":\"cust-mrv8frxlg\",\"name\":\"...\",...}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Customer id (e.g. cust-mrv8frxlg)"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"...\",\"industry\":\"...\",\"note\":\"...\",\"renew\":{...}|null, ...any shape fields}"
      }
     }
    }
   }
  },
  "/api/customer/{id}/delete": {
   "post": {
    "tags": [
     "customers"
    ],
    "summary": "Delete a customer by id; 404 if not present.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"id\":\"cust-mrv8frxlg\"}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Customer id to delete"
     }
    ]
   }
  },
  "/api/renewals": {
   "get": {
    "tags": [
     "customers"
    ],
    "summary": "Renewal/deal calendar derived from customers with a renew.date; sorted ascending by date.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"renewals\":[{\"id\":\"ren-cust-mrv8frxli\",\"customerId\":\"cust-mrv8frxli\",\"customer\":\"科研製薬\",\"date\":\"2026-08-31\",\"dateLabel\":\"08/31\",\"year\":\"2026\",\"kind\":\"商談\",\"kindChip\":\"c-orange\",\"what\":\"最終見積回答期限\",\"detail\":\"...\",\"confidence\":\"確度 80%\",\"confColor\":\"orange\"}]}"
       }
      }
     }
    }
   }
  },
  "/api/quote/catalog": {
   "get": {
    "tags": [
     "quote"
    ],
    "summary": "Returns the constant INITIAL/RUNNING line-item catalog master (mirrors app.html).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"catalog\":{\"initial\":[{\"n\":\"プロジェクト管理費\",\"q\":1,\"p\":300000,\"d\":\"...\"}, ...],\"running\":[{\"n\":\"サービス管理・月次報告\",\"q\":1,\"p\":125000,\"d\":\"...\"}, ...]}}"
       }
      }
     }
    }
   }
  },
  "/api/quotes": {
   "get": {
    "tags": [
     "quote"
    ],
    "summary": "Lists all saved quotes, each augmented with a computed totals snapshot.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"quotes\":[{\"id\":\"Q-KAKEN-001\",\"cust\":\"科研製薬 御中\",\"pattern\":\"A\",\"costRate\":45,\"ini\":[...],\"run\":[...],\"signId\":null,\"totals\":{\"ini\":...,\"iniTax\":...,\"iniTotal\":...,\"run\":...,\"arr\":...,\"y1\":...}}]}"
       }
      }
     }
    }
   }
  },
  "/api/quote/{id}": {
   "get": {
    "tags": [
     "quote"
    ],
    "summary": "Returns one saved quote by id with computed totals, or 404 if not found.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"quote\":{\"id\":\"Q-KAKEN-001\",\"cust\":\"科研製薬 御中\",\"pattern\":\"A\",\"costRate\":45,\"ini\":[...],\"run\":[...],\"signId\":null,\"totals\":{...}}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Quote id, e.g. 'Q-KAKEN-001'"
     }
    ]
   },
   "post": {
    "tags": [
     "quote"
    ],
    "summary": "Updates an existing quote in place (patch semantics), or 404 if not found.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"quote\":{\"id\":\"Q-KAKEN-001\",...,\"updatedAt\":...,\"totals\":{...}}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Quote id to update"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"signId\":\"S-123\"} (any subset of: cust,pattern,costRate,ini,run,signId)"
      }
     }
    }
   }
  },
  "/api/quote": {
   "post": {
    "tags": [
     "quote"
    ],
    "summary": "Saves a new quote with a generated id (Q-...), normalizing line items and totals.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"quote\":{\"id\":\"Q-...\",\"cust\":\"...\",...,\"totals\":{...}}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"cust\":\"...\",\"pattern\":\"A\",\"costRate\":45,\"ini\":[{\"n\":\"...\",\"q\":1,\"p\":100,\"d\":\"...\"}],\"run\":[...],\"signId\":null}"
      }
     }
    }
   }
  },
  "/api/invoices": {
   "get": {
    "tags": [
     "invoices"
    ],
    "summary": "List all invoices sorted newest-id first (descending localeCompare on id).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"invoices\":[{\"id\":\"INV-2607-004\",\"cust\":\"三峰精機\",\"month\":\"2607\",\"desc\":\"新入社員向けキッティング 8台＋送料\",\"amount\":19800,\"due\":\"08/15\",\"status\":\"draft\",\"recurring\":false}],\"count\":5}"
       }
      }
     }
    }
   }
  },
  "/api/invoice": {
   "post": {
    "tags": [
     "invoices"
    ],
    "summary": "Create an invoice; auto-generates sequential id (INV-YYMM-NNN) unless a fresh INV- id is supplied.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"invoice\":{\"id\":\"INV-2607-005\",\"cust\":\"北斗商事\",\"month\":\"2607\",\"desc\":\"\",\"amount\":10000,\"due\":\"\",\"status\":\"draft\",\"recurring\":false}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"cust\":\"string(req)\",\"amount\":number(req),\"status\":\"draft|issued|paid|overdue\",\"month\":\"YYMM\",\"desc\":\"string\",\"due\":\"string\",\"id\":\"INV-...\",\"recurring\":bool}"
      }
     }
    }
   }
  },
  "/api/invoice/{id}": {
   "post": {
    "tags": [
     "invoices"
    ],
    "summary": "Update status and/or fields on one existing invoice; validates status enum and amount.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"invoice\":{\"id\":\"INV-2607-004\",\"cust\":\"三峰精機\",\"month\":\"2607\",\"desc\":\"新入社員向けキッティング 8台＋送料\",\"amount\":19800,\"due\":\"08/15\",\"status\":\"issued\",\"recurring\":false}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Invoice id to update, e.g. INV-2607-004; 404 if not found."
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"status\":\"...\",\"cust\":\"string\",\"desc\":\"string\",\"month\":\"YYMM\",\"due\":\"string\",\"amount\":number,\"recurring\":bool} (all optional)"
      }
     }
    }
   }
  },
  "/api/invoices/issue": {
   "post": {
    "tags": [
     "invoices"
    ],
    "summary": "Bulk-issue: flips all draft invoices in a given (or current) month to issued, returns count.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"count\":1}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"month\":\"YYMM\"} (optional; defaults to current month)"
      }
     }
    }
   }
  },
  "/api/billing/summary": {
   "get": {
    "tags": [
     "invoices"
    ],
    "summary": "Compute billing summary: MRR/ARR, billed/paid/unpaid/overdue/draft totals, recurring:oneshot ratio and paid ratio.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"summary\":{\"mrr\":550000,\"arr\":6600000,\"billed\":632500,\"paid\":550000,\"unpaid\":82500,\"overdue\":49500,\"draft\":19800,\"recurring\":550000,\"oneshot\":82500,\"ratio\":\"87:13\",\"recurringPct\":87,\"paidRatio\":87,\"month\":\"2607\",\"count\":5}}"
       }
      }
     }
    }
   }
  },
  "/api/shifts": {
   "get": {
    "tags": [
     "shift"
    ],
    "summary": "Returns the weekly roster grid and today's timeclock state.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"grid\":{\"s1\":[\"day\",\"day\",\"day\",\"day\",\"day\",\"off\",\"off\"],...},\"clock\":{\"s1\":{\"in\":\"09:00\",\"out\":null,\"status\":\"in\",\"units\":76},...}}"
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "shift"
    ],
    "summary": "Saves the roster grid, and optionally the timeclock, to the store.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"grid\":{...},\"clock\":{...}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"grid\":{\"s1\":[\"day\",...]},\"clock\":{\"s1\":{\"in\":\"09:00\",...}}}"
      }
     }
    }
   }
  },
  "/api/staff": {
   "get": {
    "tags": [
     "shift"
    ],
    "summary": "Returns the roster staff list (own + agency staff).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"staff\":[{\"id\":\"s1\",\"name\":\"佐々木\",\"type\":\"own\"},{\"id\":\"a1\",\"name\":\"派遣A\",\"type\":\"agency\"}]}"
       }
      }
     }
    }
   }
  },
  "/api/timeclock/{staffId}/punch": {
   "post": {
    "tags": [
     "shift"
    ],
    "summary": "Punches a staff member in or out, updating and returning their timeclock row.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"staffId\":\"s1\",\"row\":{\"in\":\"09:00\",\"out\":null,\"status\":\"in\",\"units\":76}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "staffId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Staff id (clipped to 40 chars); must exist in staff roster (e.g. 's1','a1')."
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"dir\":\"in\"}"
      }
     }
    }
   }
  },
  "/api/dock/reservations": {
   "get": {
    "tags": [
     "dock"
    ],
    "summary": "List all dock/berth reservations (横づけ枠 weekly schedule data).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"reservations\":[{\"id\":\"RV-0001\",\"created\":1784673357614,\"updated\":1784673357614,\"date\":\"2026-07-13\",\"start\":\"10:00\",\"dur\":90,\"size\":\"2t車\",\"dir\":\"入荷\",\"cust\":\"北斗商事\",\"note\":\"返却45台\",\"berth\":\"A\",\"road\":\"否\",\"roadStatus\":\"\",\"permit\":\"済\"}]}"
       }
      }
     }
    }
   }
  },
  "/api/dock/reservation": {
   "post": {
    "tags": [
     "dock"
    ],
    "summary": "Upsert a reservation by id (create if id missing/unknown, else update existing).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"reservation\":{\"id\":\"RV-0001\",\"date\":\"2026-07-13\",...\"updated\":1784673357614}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"id\":\"RV-0001\",\"date\":\"2026-07-13\",\"start\":\"10:00\",\"dur\":90,\"size\":\"2t車\",\"dir\":\"入荷\",\"cust\":\"北斗商事\",\"note\":\"返却45台\",\"berth\":\"A\",\"road\":\"否\",\"roadStatus\":\"\",\"permit\":\"済\"}"
      }
     }
    }
   }
  },
  "/api/dock/reservation/{id}/delete": {
   "post": {
    "tags": [
     "dock"
    ],
    "summary": "Delete a reservation by id.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Reservation id to delete, e.g. RV-0001."
     }
    ]
   }
  },
  "/api/certs": {
   "get": {
    "tags": [
     "erasure"
    ],
    "summary": "List all erasure certificates, newest-first.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"certs\":[{\"no\":\"KIT-ERZ-2026-1003\",\"date\":\"2026-07-09\",\"cust\":\"科研製薬（検証）\",\"count\":6,\"methodKey\":\"clear\",\"method\":\"論理消去\",\"nist\":\"Clear\",\"devices\":[...]}]}"
       }
      }
     }
    }
   }
  },
  "/api/erasure/certs": {
   "get": {
    "tags": [
     "erasure"
    ],
    "summary": "Alias of /api/certs — list all erasure certificates newest-first.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"certs\":[{\"no\":\"KIT-ERZ-2026-1003\",...}]}"
       }
      }
     }
    }
   }
  },
  "/api/cert/{no}": {
   "get": {
    "tags": [
     "erasure"
    ],
    "summary": "Fetch a single certificate by its cert number for reprint.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"cert\":{\"no\":\"KIT-ERZ-2026-1001\",\"date\":\"2026-07-03\",\"cust\":\"北斗商事\",\"count\":45,\"methodKey\":\"purge\",\"method\":\"暗号化消去\",\"nist\":\"Purge\",\"devices\":[...]}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "no",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Certificate number, e.g. KIT-ERZ-2026-1001"
     }
    ]
   }
  },
  "/api/cert": {
   "post": {
    "tags": [
     "erasure"
    ],
    "summary": "Issue a new certificate; server auto-numbers via persisted counter.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"cert\":{\"no\":\"KIT-ERZ-2026-1004\",\"date\":\"2026-07-22\",\"cust\":\"...\",\"count\":1,\"methodKey\":\"purge\",\"method\":\"暗号化消去\",\"nist\":\"Purge\",\"devices\":[...]}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"cust\":\"string\",\"methodKey\":\"purge|clear|destroy\",\"date\":\"YYYY-MM-DD\",\"count\":1,\"devices\":[{\"model\":\"iPhone 16\",\"id\":\"...\",\"storage\":\"256GB\"}]}"
      }
     }
    }
   }
  },
  "/api/replace/proposals": {
   "get": {
    "tags": [
     "replace"
    ],
    "summary": "Returns the per-customer 'proposed' (提案済み) map for device-refresh bundles as { customer: ISO-timestamp }.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"proposals\":{}}"
       }
      }
     }
    }
   }
  },
  "/api/replace/proposal": {
   "post": {
    "tags": [
     "replace"
    ],
    "summary": "Marks a customer's device-refresh bundle as proposed (upserts { cust, at:now }) and returns the updated proposals map.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"cust\":\"ACME株式会社\",\"at\":\"2026-07-22T00:00:00.000Z\",\"proposals\":{\"ACME株式会社\":\"2026-07-22T00:00:00.000Z\"}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{ \"cust\": \"<customer>\" }"
      }
     }
    }
   }
  },
  "/api/network/deals": {
   "get": {
    "tags": [
     "network"
    ],
    "summary": "Return the capacity-network deal board (provide/accept/request deals), newest-first.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"deals\":[{\"id\":\"dmrv8fry22c\",\"dir\":\"provide\",\"partner\":\"ネットワーク公開\",\"kind\":\"キッティング枠\",\"units\":40,\"period\":\"7/15–7/22\",\"rate\":1200,\"status\":\"募集中\"}]}"
       }
      }
     }
    }
   }
  },
  "/api/network/deal": {
   "post": {
    "tags": [
     "network"
    ],
    "summary": "Add a deal to the board (list surplus capacity or request capacity); prepends to deals array.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"deal\":{\"id\":\"d...\",\"dir\":\"provide\",\"partner\":\"...\",\"kind\":\"キッティング枠\",\"units\":40,\"period\":\"—\",\"rate\":0,\"status\":\"募集中\"},\"deals\":[...]}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"dir\":\"provide\",\"partner\":\"ネットワーク公開\",\"kind\":\"キッティング枠\",\"units\":40,\"period\":\"7/15–7/22\",\"rate\":1200,\"status\":\"募集中\"}"
      }
     }
    }
   }
  },
  "/api/network/centers": {
   "get": {
    "tags": [
     "network"
    ],
    "summary": "Return the certified kitting-center directory / marketplace (5 seeded centers).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"centers\":[{\"id\":\"c-tsm\",\"name\":\"豊島 近接センター\",\"region\":\"東京\",\"mdm\":[\"Intune\",\"Workspace ONE\"],\"slots\":80,\"shelf\":40,\"rate\":1100,\"certs\":[\"ISMS\",\"Pマーク\"]}]}"
       }
      }
     }
    }
   }
  },
  "/api/network/oem": {
   "get": {
    "tags": [
     "network"
    ],
    "summary": "Return the white-label / OEM tenant config (brand, logo, domain, plan).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"oem\":{\"brand\":\"\",\"logo\":\"\",\"domain\":\"\",\"plan\":\"seat\"}}"
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "network"
    ],
    "summary": "Save the OEM tenant config; overwrites the single 'oem' doc.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"oem\":{\"brand\":\"Acme\",\"logo\":\"A\",\"domain\":\"acme.example\",\"plan\":\"seat\"}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"brand\":\"Acme\",\"logo\":\"A\",\"domain\":\"acme.example\",\"plan\":\"seat\"}"
      }
     }
    }
   }
  },
  "/api/forecast": {
   "get": {
    "tags": [
     "forecast"
    ],
    "summary": "Returns 16-week demand series, transparent demand/staffing model constants, and persisted user prefs (horizon, sim).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"weekly\":[620,600,540,470,560,690,810,940,980,900,700,600,560,540,600,640],\"constants\":{\"PER_PERSON\":80,\"BASE_STAFF\":2,\"WORKDAYS\":5,\"CEIL_DAY\":192,\"COST_PER_STAFF\":310000,\"COST_MONTHS\":2,\"DEAL_REV\":3850000,\"DEAL_GROSS\":1900000},\"prefs\":{\"horizon\":12,\"sim\":2}}"
       }
      }
     }
    }
   }
  },
  "/api/forecast/prefs": {
   "post": {
    "tags": [
     "forecast"
    ],
    "summary": "Persists user prefs; validates horizon in {8,12,16} and sim in {0,1,2}, returns saved prefs.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"prefs\":{\"horizon\":12,\"sim\":2}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"horizon\":12,\"sim\":2}"
      }
     }
    }
   }
  },
  "/api/labels/templates": {
   "get": {
    "tags": [
     "labels"
    ],
    "summary": "List all saved label templates.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"templates\":[]}"
       }
      }
     }
    }
   }
  },
  "/api/labels/template": {
   "post": {
    "tags": [
     "labels"
    ],
    "summary": "Create a new label template with a server-issued id.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"template\":{\"id\":\"tpl-xxxx\",\"name\":\"...\",\"fields\":{...},\"size\":\"m\",\"ts\":1700000000000}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"string\",\"size\":\"s|m|l\",\"fields\":{\"mgmt\":bool,\"imei\":bool,\"cust\":bool,\"model\":bool,\"qr\":bool,\"barcode\":bool},\"ts\":number}"
      }
     }
    }
   }
  },
  "/api/labels/template/{id}/delete": {
   "post": {
    "tags": [
     "labels"
    ],
    "summary": "Delete a template by id.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"id\":\"tpl-xxxx\"}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Template id (clipped to 40 chars). Must exist in tplCol."
     }
    ]
   }
  },
  "/api/labels/log": {
   "get": {
    "tags": [
     "labels"
    ],
    "summary": "List all print-batch log entries.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"log\":[]}"
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "labels"
    ],
    "summary": "Append a print-batch record to the log.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"entry\":{\"id\":\"lg-xxxx\",\"ts\":1700000000000,\"date\":\"2026-07-22\",\"count\":0,\"size\":\"m\"}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"size\":\"s|m|l\",\"count\":number,\"date\":\"YYYY-MM-DD\",\"ts\":number}"
      }
     }
    }
   }
  },
  "/api/survey": {
   "get": {
    "tags": [
     "survey"
    ],
    "summary": "Return persisted survey state: arrived responses, churn-save followups keyed by response id, and the running sent counter.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"responses\":[],\"followups\":{},\"sent\":152}"
       }
      }
     }
    }
   }
  },
  "/api/survey/send": {
   "post": {
    "tags": [
     "survey"
    ],
    "summary": "Increment the survey-sent counter by {batch} (defaults to 1 if missing/invalid) and return the new total.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"sent\":162}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"batch\": 10}"
      }
     }
    }
   }
  },
  "/api/survey/response": {
   "post": {
    "tags": [
     "survey"
    ],
    "summary": "Append/upsert an arrived survey response (id,date,cust,src,k,nps,csat,comment) into the responses collection.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"response\":{\"id\":\"SR-1001\",\"date\":\"2026-07-22\",\"cust\":\"Acme\",\"src\":\"ticket\",\"k\":\"CSAT\",\"nps\":9,\"csat\":5,\"comment\":\"Great\"}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"id\":\"SR-1001\",\"date\":\"2026-07-22\",\"cust\":\"Acme\",\"src\":\"ticket\",\"k\":\"CSAT\",\"nps\":9,\"csat\":5,\"comment\":\"Great\"}"
      }
     }
    }
   }
  },
  "/api/survey/followup/{id}": {
   "post": {
    "tags": [
     "survey"
    ],
    "summary": "Record or replace a churn-save follow-up {action,note,date} for the response identified by :id.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"followup\":{\"action\":\"Called customer\",\"note\":\"Resolved billing issue\",\"date\":\"2026-07-22\"}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Response id the follow-up is attached to (clip 60)."
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"action\":\"Called customer\",\"note\":\"Resolved billing issue\",\"date\":\"2026-07-22\"}"
      }
     }
    }
   }
  },
  "/api/onboarding/projects": {
   "get": {
    "tags": [
     "onboarding"
    ],
    "summary": "Returns the current onboarding projects state {sel,tab,projects} (7-step MDM/ABM flow per customer), self-seeding demo data if missing.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"state\":{\"sel\":\"北斗商事\",\"tab\":\"cust\",\"projects\":{\"北斗商事\":{\"mdm\":\"Intune\",\"steps\":{\"kickoff\":\"done\",\"abm\":\"done\",\"apns\":\"done\",\"design\":\"done\",\"pilot\":\"done\",\"rollout\":\"done\",\"handover\":\"wip\"},\"start\":\"2026-06-27\"},\"三峰精機\":{...},\"科研製薬\":{...}}}}"
       }
      }
     }
    }
   },
   "put": {
    "tags": [
     "onboarding"
    ],
    "summary": "Replaces the projects state; requires a projects object, sanitizes sel (clip 120) and tab (emp|cust).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"state\":{\"sel\":\"...\",\"tab\":\"cust\",\"projects\":{...}}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"sel\":\"北斗商事\",\"tab\":\"cust\",\"projects\":{\"北斗商事\":{\"mdm\":\"Intune\",\"steps\":{...},\"start\":\"2026-06-27\"}}}"
      }
     }
    }
   }
  },
  "/api/onboarding/requests": {
   "get": {
    "tags": [
     "onboarding"
    ],
    "summary": "Lists all employee device requests, sorted ascending by ts. Seed is empty (mirrors client).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"requests\":[]}"
       }
      }
     }
    }
   }
  },
  "/api/onboarding/request": {
   "post": {
    "tags": [
     "onboarding"
    ],
    "summary": "Creates a pending device request; honors a client-provided id so ids stay in sync. Requires name and dept.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"request\":{\"id\":\"REQ-...\",\"name\":\"山田太郎\",\"dept\":\"営業部\",\"model\":\"iPhone 15\",\"mdm\":\"Intune\",\"use\":\"field\",\"ticket\":\"T-100\",\"status\":\"pending\",\"ts\":1700000000000}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"山田太郎\",\"dept\":\"営業部\",\"model\":\"iPhone 15\",\"mdm\":\"Intune\",\"use\":\"field\",\"ticket\":\"T-100\"}"
      }
     }
    }
   }
  },
  "/api/onboarding/request/{id}": {
   "post": {
    "tags": [
     "onboarding"
    ],
    "summary": "Approve/reject/reset an existing request by id via {status}; on 'approved' sets a lead-time (optional lead days else random 0.8-2.4).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"request\":{\"id\":\"REQ-...\",\"status\":\"approved\",\"lead\":1.5,...}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "request id; unknown -> 404 {error:'request not found'}"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"status\":\"approved\",\"lead\":1.5}"
      }
     }
    }
   }
  },
  "/api/team/members": {
   "get": {
    "tags": [
     "team"
    ],
    "summary": "Return the full list of team members from the team-members collection.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"members\":[{\"id\":\"t1\",\"name\":\"佐藤 美咲\",\"ini\":\"MS\",\"role\":\"owner\",\"dept\":\"経営\",\"email\":\"misaki.sato@ai-sns.io\"}, ...]}"
       }
      }
     }
    }
   }
  },
  "/api/team/member": {
   "post": {
    "tags": [
     "team"
    ],
    "summary": "Create/invite a new member; server issues the id, requires a name.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"member\":{\"id\":\"t<generated>\",\"name\":\"...\",\"ini\":\"NA\",\"role\":\"member\",\"dept\":\"—\",\"email\":\"\"}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"string\",\"ini\":\"string?\",\"role\":\"owner|admin|exec|member|viewer?\",\"dept\":\"string?\",\"email\":\"string?\",\"scopes\":{...}?}"
      }
     }
    }
   }
  },
  "/api/team/member/{id}": {
   "post": {
    "tags": [
     "team"
    ],
    "summary": "Update an existing member's role/scopes/fields; blocks demoting the last owner.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"member\":{\"id\":\"t5\",\"name\":\"中村 由美\",\"role\":\"member\", ...}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Member id to update (e.g. 't5'); clipped to 40 chars."
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"role\":\"...?\",\"name\":\"...?\",\"ini\":\"...?\",\"dept\":\"...?\",\"email\":\"...?\",\"scopes\":{...}?}"
      }
     }
    }
   }
  },
  "/api/team/member/{id}/delete": {
   "post": {
    "tags": [
     "team"
    ],
    "summary": "Delete a member by id; refuses to remove the last remaining owner.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"id\":\"t6\"}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Member id to delete (e.g. 't6'); clipped to 40 chars."
     }
    ]
   }
  },
  "/api/team/plan": {
   "get": {
    "tags": [
     "team"
    ],
    "summary": "Return the current billing plan key (starter|pro|ent), defaulting to 'pro'.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"planKey\":\"pro\"}"
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "team"
    ],
    "summary": "Set the current billing plan; validates against starter|pro|ent.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"planKey\":\"pro\"}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"planKey\":\"starter|pro|ent\"}"
      }
     }
    }
   }
  },
  "/api/settings/company": {
   "get": {
    "tags": [
     "settings"
    ],
    "summary": "Return the issuer/company profile stamped onto quote/invoice/certificate PDFs.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"company\":{\"name\":\"株式会社 ai-sns.io\",\"brand\":\"ai-sns.io Kitting\",\"addr\":\"東京都板橋区・板橋キッティングセンター\",\"tel\":\"\",\"email\":\"ai.sns.io.official@gmail.com\",\"regno\":\"\",\"rep\":\"\",\"web\":\"\",\"bank\":\"\",\"logo\":\"\"}}"
       }
      }
     }
    }
   },
   "put": {
    "tags": [
     "settings"
    ],
    "summary": "Merge-update the issuer/company profile (text fields + base64 logo dataURL).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"company\":{...merged record...}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"...\",\"brand\":\"...\",\"addr\":\"...\",\"tel\":\"...\",\"email\":\"...\",\"regno\":\"...\",\"rep\":\"...\",\"web\":\"...\",\"bank\":\"...\",\"logo\":\"data:image/png;base64,...\"}"
      }
     }
    }
   }
  },
  "/api/settings/prefs": {
   "get": {
    "tags": [
     "settings"
    ],
    "summary": "Return notification preferences (emailNotify / slaAlert toggles).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"prefs\":{\"emailNotify\":true,\"slaAlert\":true}}"
       }
      }
     }
    }
   },
   "put": {
    "tags": [
     "settings"
    ],
    "summary": "Merge-update notification toggles (emailNotify / slaAlert).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"prefs\":{\"emailNotify\":true,\"slaAlert\":false}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"emailNotify\":true,\"slaAlert\":false}"
      }
     }
    }
   }
  },
  "/api/integrations/rules": {
   "get": {
    "tags": [
     "integrations"
    ],
    "summary": "Returns the automation rule toggles (trigger->action) plus any user-added extra rules.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"rules\":{\"r-ship\":true,\"r-inv\":true,\"r-lost\":true,\"r-ade\":true,\"r-sla\":true,\"r-pay\":false},\"extraRules\":[]}"
       }
      }
     }
    }
   },
   "put": {
    "tags": [
     "integrations"
    ],
    "summary": "Persists the whole rules state; replaces rules map (id->bool) and/or the extraRules array.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"rules\":{...},\"extraRules\":[...]}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"rules\":{\"r-ship\":true,\"r-pay\":true},\"extraRules\":[{\"id\":\"r-x1\",\"trig\":\"...\",\"act\":\"...\",\"ic\":\"⚙️\",\"on\":true}]}"
      }
     }
    }
   }
  },
  "/api/integrations/log": {
   "get": {
    "tags": [
     "integrations"
    ],
    "summary": "Returns the recent sync log entries, newest first (sorted by time desc).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"log\":[{\"id\":\"LOG-0001\",\"service\":\"intune\",\"kind\":\"インベントリ取得\",\"result\":\"ok\",\"time\":\"2026-07-21T22:06:57.616Z\"}]}"
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "integrations"
    ],
    "summary": "Appends a new sync-log entry and returns it plus the full (bounded to 60) log.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"entry\":{\"id\":\"LOG-0008\",\"service\":\"yamato\",\"kind\":\"送り状 発行\",\"result\":\"ok\",\"time\":\"...\"},\"log\":[...]}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"service\":\"yamato\",\"kind\":\"送り状 発行\",\"result\":\"ok\",\"time\":\"2026-07-22T00:00:00.000Z\"}"
      }
     }
    }
   }
  },
  "/api/compliance": {
   "get": {
    "tags": [
     "compliance"
    ],
    "summary": "Returns control checklists per standard (isms/pmark/soc2/appi) plus the full CAPA corrective-action list.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"controls\":{\"isms\":[true,true,true,true,false,true],\"pmark\":[...],\"soc2\":[...],\"appi\":[...]},\"capa\":[{\"id\":\"CAPA-2026-014\",\"src\":\"SOC 2\",\"finding\":\"変更管理の承認記録に一部欠落\",\"due\":\"2026-07-25\",\"owner\":\"佐々木 玲\",\"state\":\"対応中\"}]}"
       }
      }
     }
    }
   }
  },
  "/api/compliance/capa/{id}": {
   "post": {
    "tags": [
     "compliance"
    ],
    "summary": "Updates the state of a single corrective action (CAPA) identified by id.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"capa\":{\"id\":\"CAPA-2026-014\",\"src\":\"SOC 2\",\"finding\":\"...\",\"due\":\"2026-07-25\",\"owner\":\"佐々木 玲\",\"state\":\"是正待ち\"}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "CAPA id, e.g. CAPA-2026-014"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"state\":\"是正待ち\"}"
      }
     }
    }
   }
  },
  "/api/compliance/controls/{key}": {
   "put": {
    "tags": [
     "compliance"
    ],
    "summary": "Replaces a standard's control checklist with a new boolean array.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"key\":\"isms\",\"controls\":[true,true,false,true,false,true]}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "key",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Control set key: isms | pmark | soc2 | appi"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"controls\":[true,true,false,true,false,true]}"
      }
     }
    }
   }
  },
  "/api/audit-log": {
   "get": {
    "tags": [
     "compliance"
    ],
    "summary": "Returns the tamper-evident audit trail as newest-first rows, each [ts,user,action,target,result,cat].",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"logs\":[[\"2026-07-11 09:02\",\"佐々木 玲\",\"ログイン（多要素認証）\",\"管制コンソール\",\"成功\",\"login\"],[\"2026-07-11 09:14\",\"田中 亮\",\"端末台帳 更新（ステータス変更）\",\"IMEI 356821…4471\",\"成功\",\"ledger\"]]}"
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "compliance"
    ],
    "summary": "Appends a new audit entry; auto-fills ts/user/target/result/cat defaults when omitted.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"entry\":[\"2026-07-22 10:15\",\"山本 佳奈\",\"消去実行\",\"北斗商事 45台\",\"成功\",\"erase\"]}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"action\":\"消去実行\",\"user\":\"山本 佳奈\",\"target\":\"北斗商事 45台\",\"result\":\"成功\",\"cat\":\"erase\"}"
      }
     }
    }
   }
  },
  "/api/workdesk/docs": {
   "get": {
    "tags": [
     "workdesk"
    ],
    "summary": "List source-library documents, newest user-added first then seed docs in original order.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"docs\":[{\"id\":\"sop\",\"name\":\"キッティング手順書\",\"type\":\"SOP\",\"size\":184320,\"status\":\"索引済\",\"date\":\"2026/07/21\",\"created\":0,\"seedIdx\":0}]}"
       }
      }
     }
    }
   }
  },
  "/api/workdesk/doc": {
   "post": {
    "tags": [
     "workdesk"
    ],
    "summary": "Create/upsert a document; all body fields optional, defaults applied.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"doc\":{\"id\":\"doc-...\",\"name\":\"...\",\"type\":\"FILE\",\"size\":0,\"status\":\"索引済\",\"date\":\"2026/07/22\",\"created\":1700000000000}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"...\",\"type\":\"...\",\"size\":123,\"status\":\"...\",\"date\":\"...\",\"id\":\"...\"}"
      }
     }
    }
   }
  },
  "/api/workdesk/doc/{id}/delete": {
   "post": {
    "tags": [
     "workdesk"
    ],
    "summary": "Delete a document by id; 404 if not found.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Doc id to delete (e.g. 'sop')"
     }
    ]
   }
  },
  "/api/workdesk/threads": {
   "get": {
    "tags": [
     "workdesk"
    ],
    "summary": "List AI chat threads, newest first (created desc); seed is empty.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"threads\":[]}"
       }
      }
     }
    }
   }
  },
  "/api/workdesk/thread": {
   "post": {
    "tags": [
     "workdesk"
    ],
    "summary": "Create/upsert a full chat thread; body optional, sanitizes msgs.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"thread\":{\"id\":\"thr-...\",\"title\":\"新しいチャット\",\"ts\":\"\",\"msgs\":[],\"created\":1700000000000}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"id\":\"...\",\"title\":\"...\",\"ts\":\"...\",\"msgs\":[{\"role\":\"user\",\"text\":\"...\"}]}"
      }
     }
    }
   }
  },
  "/api/workdesk/thread/{id}": {
   "post": {
    "tags": [
     "workdesk"
    ],
    "summary": "Append/replace msgs or rename a thread; creates thread on-append if missing.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"thread\":{\"id\":\"...\",\"title\":\"...\",\"ts\":\"...\",\"msgs\":[...],\"created\":1700000000000}}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Thread id"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"title\":\"...\",\"ts\":\"...\",\"msgs\":[...],\"append\":[{\"role\":\"ai\",\"text\":\"...\"}]}"
      }
     }
    }
   }
  },
  "/api/workdesk/conn": {
   "post": {
    "tags": [
     "workdesk"
    ],
    "summary": "Persist connector on/off map {id:bool,...}, coerced to booleans.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"conn\":{\"slack\":true,\"teams\":false}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"slack\":true,\"teams\":false}  OR  {\"map\":{\"slack\":true}}"
      }
     }
    }
   },
   "get": {
    "tags": [
     "workdesk"
    ],
    "summary": "Read persisted connector on/off map (hydration convenience).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"conn\":{}}"
       }
      }
     }
    }
   }
  },
  "/api/esg/settings": {
   "get": {
    "tags": [
     "esg"
    ],
    "summary": "Return the auditable ESG coefficients (co2e kg-CO2e/unit, ewaste kg/unit).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"settings\":{\"co2e\":55,\"ewaste\":0.42}}"
       }
      }
     }
    }
   },
   "put": {
    "tags": [
     "esg"
    ],
    "summary": "Merge-update the ESG coefficients; accepts positive finite co2e and/or ewaste, else keeps prior value.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"settings\":{\"co2e\":60,\"ewaste\":0.5}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"co2e\": 55, \"ewaste\": 0.42}"
      }
     }
    }
   }
  },
  "/api/esg/metrics": {
   "get": {
    "tags": [
     "esg"
    ],
    "summary": "Return FY, monthly reuse volumes, per-customer reuse/return figures, and aggregate totals (reuse, ret, circularity %).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"fy\":\"2026年度\",\"months\":[\"4\",\"5\",...],\"monthly\":[78,85,...],\"customers\":[{\"name\":\"北斗商事\",\"reuse\":420,\"ret\":470},...],\"totals\":{\"reuse\":1240,\"ret\":1420,\"circularity\":87.3}}"
       }
      }
     }
    }
   }
  },
  "/api/portals": {
   "get": {
    "tags": [
     "portal"
    ],
    "summary": "Returns the full customer portal state: per-customer {published,suffix,last,name,slug} plus global counters _tickets (MTD) and _dls (avg monthly downloads).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"state\":{\"hokuto\":{\"published\":true,\"suffix\":\"blk3\",\"last\":\"3分前\",\"name\":\"北斗商事\",\"slug\":\"hokuto\"},\"tosei\":{\"published\":false,\"suffix\":\"1jip\",\"last\":\"—\",...},\"_tickets\":4,\"_dls\":63}}"
       }
      }
     }
    }
   }
  },
  "/api/portal/{custId}": {
   "post": {
    "tags": [
     "portal"
    ],
    "summary": "Mutates one customer portal. body.action: 'toggle' (default; flip or set published, sets last='たった今' when publishing) | 'ticket' (bump global tickets, set last) | 'dl' (bump global downloads) | 'touch' (set last from body.last). Returns updated portal + counters.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"portal\":{\"id\":\"tosei\",\"name\":\"東成建設\",\"slug\":\"tosei\",\"published\":true,\"suffix\":\"1jip\",\"last\":\"たった今\"},\"tickets\":4,\"dls\":63}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "custId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Customer id: one of hokuto|mitsumine|kaken|tosei|mizuki. 404 if unknown."
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"action\":\"toggle\",\"published\":true,\"last\":\"たった今\"}"
      }
     }
    }
   }
  },
  "/api/portal/issue": {
   "post": {
    "tags": [
     "portal"
    ],
    "summary": "Issues (publishes) a portal for a customer identified by body.custId/id, sets published=true and last='たった今', and returns the public portal URL '/portal.html?c=<name>'.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"url\":\"/portal.html?c=%E6%9D%B1%E6%88%90%E5%BB%BA%E8%A8%AD\",\"portal\":{\"id\":\"tosei\",\"name\":\"東成建設\",\"slug\":\"tosei\",\"published\":true,\"suffix\":\"1jip\",\"last\":\"たった今\"}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"custId\":\"tosei\"}"
      }
     }
    }
   }
  },
  "/api/qbr/history": {
   "get": {
    "tags": [
     "qbr"
    ],
    "summary": "List generated-QBR history entries (newest-first, capped at 20).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"history\":[]}"
       }
      }
     }
    }
   }
  },
  "/api/qbr": {
   "post": {
    "tags": [
     "qbr"
    ],
    "summary": "Record a generated QBR into history; returns the new record plus updated capped history.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"record\":{\"custId\":\"c1\",\"cust\":\"Acme\",\"qId\":\"2026Q2\",\"q\":\"2026 Q2\",\"date\":\"2026-07-22T00:00:00.000Z\"},\"history\":[{...}]}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"custId\":\"c1\",\"cust\":\"Acme\",\"qId\":\"2026Q2\",\"q\":\"2026 Q2\",\"date\":\"2026-07-22T00:00:00.000Z\"}"
      }
     }
    }
   }
  },
  "/api/nurture": {
   "get": {
    "tags": [
     "nurture"
    ],
    "summary": "Return the single nurture config+history doc (per-step edits, custom drip sequences, send-history flags, auto-remind toggle).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"doc\":{\"edits\":{},\"custom\":[],\"sent\":{},\"autoRemind\":true}}"
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "nurture"
    ],
    "summary": "Persist (replace) the nurture config+history doc after shaping/clipping to bounded sizes.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"doc\":{\"edits\":{},\"custom\":[],\"sent\":{},\"autoRemind\":true}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{ \"edits\": { \"seqId:0\": {\"subject\":\"...\",\"body\":\"...\"} }, \"custom\": [ {\"id\":\"...\",\"name\":\"...\",\"target\":\"...\",\"tone\":\"...\",\"desc\":\"...\",\"steps\":[{\"day\":0,\"subject\":\"...\",\"body\":\"...\"}]} ], \"sent\": {\"leadId:seqId:0\":true}, \"autoRemind\": true }"
      }
     }
    }
   }
  },
  "/api/admin/login": {
   "post": {
    "tags": [
     "server:admin"
    ],
    "summary": "Master-admin login; returns an 8h HMAC-signed bearer token when the password matches ADMIN_PASSWORD.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"token\":\"<body>.<sig>\"} | 401 {\"ok\":false,\"error\":\"パスワードが違います\"}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"password\":\"kitting-admin\"}"
      }
     }
    }
   }
  },
  "/api/admin/tenants": {
   "get": {
    "tags": [
     "server:admin"
    ],
    "summary": "List all tenants plus aggregate stats (totals, active/trial/suspended counts, MRR, devices, seats, sites, clients).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"tenants\":[{\"id\":\"t-itabashi\",\"name\":\"板橋キッティングセンター\",\"plan\":\"Business\",\"seats\":180,\"used\":120,\"status\":\"active\",\"mrr\":582000,\"devices\":1204,\"clients\":[...],\"features\":{...}}],\"stats\":{\"total\":5,\"active\":3,\"trial\":1,\"suspended\":1,\"mrr\":2487000,...},\"origin\":\"https://...\"}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Admin token (alternative to Authorization: Bearer header)"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/admin/tenant/{id}": {
   "get": {
    "tags": [
     "server:admin"
    ],
    "summary": "Get one tenant with derived client dashboard URLs and published-site URLs.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"tenant\":{...},\"dashboards\":[{\"client\":\"北斗商事\",\"url\":\".../portal.html?c=...\"}],\"published\":[{\"slug\":\"itabashi-kitting\",\"url\":\".../site/itabashi-kitting\",\"live\":true}],\"origin\":\"https://...\"}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id, e.g. t-itabashi"
     },
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Admin token (alt to Bearer header)"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   },
   "post": {
    "tags": [
     "server:admin"
    ],
    "summary": "Update an existing tenant's editable fields (name, plan, seats, used, status, email, devices, mrr, features, clients).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"tenant\":{...updated...}}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id to update"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"plan\":\"Enterprise\",\"seats\":300,\"status\":\"active\",\"features\":{\"daas\":true}}"
      }
     }
    }
   }
  },
  "/api/admin/tenant": {
   "post": {
    "tags": [
     "server:admin"
    ],
    "summary": "Create a new tenant (auto-generated id 't-<slug>-<hex>'), defaults to Starter/trial with base feature flags.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"tenant\":{\"id\":\"t-...-a1b2\",\"status\":\"trial\",\"used\":0,\"mrr\":0,\"features\":{...}}}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"新規センター\",\"plan\":\"Business\",\"seats\":50,\"email\":\"x@example.jp\"}"
      }
     }
    }
   }
  },
  "/api/admin/tenant/{id}/delete": {
   "post": {
    "tags": [
     "server:admin"
    ],
    "summary": "Delete a tenant from the store and persist.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id to delete"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/admin/tenant/{id}/client": {
   "post": {
    "tags": [
     "server:admin"
    ],
    "summary": "Add a client name to a tenant and return that client's portal dashboard URL.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"url\":\".../portal.html?c=%E6%96%B0%E8%A6%8F...\"}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"client\":\"新規クライアント\"}"
      }
     }
    }
   }
  },
  "/api/admin/impersonate/{id}": {
   "post": {
    "tags": [
     "server:admin"
    ],
    "summary": "Return an assist-mode ('admin_as') app URL for the given tenant (app shows a disclaimer banner).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"url\":\".../app?admin_as=%E6%9D%BF%E6%A9%8B...\"}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id to impersonate"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/admin/subadmins": {
   "get": {
    "tags": [
     "server:admin"
    ],
    "summary": "List all sub-admins (public-safe fields, no passwords) plus a compact {id,name} tenant list for assignment.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"subadmins\":[{\"id\":\"sa-demo\",\"name\":\"サポート担当 山田\",\"email\":\"yamada@ai-sns.io\",\"tenants\":[\"t-itabashi\",\"t-nagoya\"],\"perms\":{\"canDelete\":true},\"active\":true,\"created\":\"2026-06-01\"}],\"tenants\":[{\"id\":\"t-itabashi\",\"name\":\"板橋キッティングセンター\"}]}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Admin token (alt to Bearer header)"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/admin/subadmin": {
   "post": {
    "tags": [
     "server:admin"
    ],
    "summary": "Create a sub-admin; validates required name/email and unique email, auto-generates a password if none supplied.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"subadmin\":{...},\"password\":\"<pass>\"}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"新担当\",\"email\":\"new@ai-sns.io\",\"tenants\":[\"t-osaka\"],\"canDelete\":false}"
      }
     }
    }
   }
  },
  "/api/admin/subadmin/{id}": {
   "post": {
    "tags": [
     "server:admin"
    ],
    "summary": "Update a sub-admin's name, email, password, assigned tenants, canDelete perm, or active flag.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"subadmin\":{...updated...}}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Sub-admin id"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"active\":false,\"canDelete\":true}"
      }
     }
    }
   }
  },
  "/api/admin/subadmin/{id}/delete": {
   "post": {
    "tags": [
     "server:admin"
    ],
    "summary": "Delete a sub-admin and persist.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Sub-admin id to delete"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/admin/backups": {
   "get": {
    "tags": [
     "server:backup"
    ],
    "summary": "Master-admin global backup log (last 120 snapshot rows across all tenants) plus daily auto-backup status.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"log\":[{\"tenantId\":\"t-itabashi\",\"tenantName\":\"板橋キッティングセンター\",\"id\":\"bk-...\",\"ts\":1784600000000,\"by\":\"system\",\"reason\":\"日次自動バックアップ\",\"sites\":1}],\"daily\":{\"last\":1784600000000,\"tenants\":5}}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Admin JWT; alternatively sent as Authorization: Bearer <token>. Required."
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/admin/activity": {
   "get": {
    "tags": [
     "server:admin"
    ],
    "summary": "Return the audit/activity log (newest first, up to 200 entries).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"activity\":[{\"ts\":1699999999999,\"by\":\"サポート担当 山田\",\"role\":\"subadmin\",\"action\":\"パスワードを変更\",\"tenantId\":\"\",\"tenantName\":\"\",\"detail\":\"\"}]}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Admin token (alt to Bearer header)"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/subadmin/login": {
   "post": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Sub-admin login; matches email+password against the subadmins map and issues an 8h HMAC token.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"token\":\"<b64>.<sig>\",\"sub\":{\"id\":\"sa-demo\",\"name\":\"サポート担当 山田\",\"email\":\"yamada@ai-sns.io\",\"tenants\":[\"t-itabashi\",\"t-nagoya\"],\"perms\":{\"canDelete\":true},\"active\":true,\"created\":\"2026-06-01\"}}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"email\":\"yamada@ai-sns.io\",\"password\":\"kitting-sub\"}"
      }
     }
    }
   }
  },
  "/api/subadmin/me": {
   "get": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Returns the authenticated sub-admin profile, their in-scope tenants, and aggregate stats.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"sub\":{...},\"tenants\":[{\"id\":\"t-itabashi\",...}],\"stats\":{\"tenants\":2,\"devices\":1414,\"clients\":5,\"sites\":1},\"origin\":\"https://aisnsio-kitting.hf.space\"}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Alternative to Authorization: Bearer header"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/subadmin/tenant/{id}": {
   "get": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Scoped read of one tenant plus derived dashboards, published sites, backups, and the sub-admin's perms.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"tenant\":{\"id\":\"t-itabashi\",\"clients\":[\"北斗商事\",...],\"sites\":[\"itabashi-kitting\"]},\"dashboards\":[{\"client\":\"北斗商事\",\"url\":\".../portal.html?c=...\"}],\"published\":[{\"slug\":\"itabashi-kitting\",\"url\":\".../site/itabashi-kitting\",\"live\":true}],\"backups\":[],\"perms\":{\"canDelete\":true},\"origin\":\"...\"}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id, must be in sub-admin scope (seed: t-itabashi, t-nagoya)"
     },
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Alternative to Bearer header"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   },
   "post": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Scoped non-core tenant edit; only the contact email can be changed, main settings are blocked.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"tenant\":{\"id\":\"t-itabashi\",\"email\":\"new@example.co.jp\",...}}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id, must be in scope"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"email\":\"new@example.co.jp\"}"
      }
     }
    }
   }
  },
  "/api/subadmin/tenant/{id}/client": {
   "post": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Adds a client name to a scoped tenant and returns its portal URL.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"url\":\"https://aisnsio-kitting.hf.space/portal.html?c=%E6%96%B0%E8%A6%8F...\"}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id, must be in scope"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"client\":\"新規クライアント\"}"
      }
     }
    }
   }
  },
  "/api/subadmin/impersonate/{id}": {
   "post": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Generates an assisted-login URL into the client app for a scoped tenant (support impersonation).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"url\":\"https://aisnsio-kitting.hf.space/app?admin_as=%E6%9D%BF%E6%A9%8B...&by=subadmin\"}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id, must be in scope"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/subadmin/delete-request": {
   "post": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Requests deletion of a client/site/tenant; generates a 6-digit access code (10-min expiry) gated by canDelete perm.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"requestId\":\"dr-<hex>\",\"sentTo\":[\"itabashi@example.co.jp\",\"マスター管理者\"],\"expiresInMin\":10,\"demoCode\":\"123456\"}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"tenantId\":\"t-itabashi\",\"targetType\":\"client\",\"targetId\":\"北斗商事\"}"
      }
     }
    }
   }
  },
  "/api/subadmin/delete-confirm": {
   "post": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Confirms a delete request with the emailed access code and performs the scoped delete after auto-backup.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"targetType\":\"client\"}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"requestId\":\"dr-<hex>\",\"code\":\"123456\"}"
      }
     }
    }
   }
  },
  "/api/subadmin/activity": {
   "get": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Returns up to 150 recent activity-log entries filtered to the sub-admin's own actions or in-scope tenants.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"activity\":[{\"ts\":1700000000000,\"by\":\"サポート担当 山田\",\"role\":\"subadmin\",\"action\":\"サポートメモを追加\",\"tenantId\":\"t-itabashi\",\"tenantName\":\"板橋キッティングセンター\",\"detail\":\"...\"}]}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Alternative to Bearer header"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/subadmin/backups-all": {
   "get": {
    "tags": [
     "server:backup"
    ],
    "summary": "Scope-wide backup log for the logged-in sub-admin: last 120 snapshot rows across only their assigned tenants.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"log\":[{\"tenantId\":\"t-nagoya\",\"tenantName\":\"名古屋モバイル管理\",\"id\":\"bk-...\",\"ts\":1784600000000,\"by\":\"system\",\"reason\":\"日次自動バックアップ\",\"sites\":0}]}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Sub-admin JWT (role==='subadmin'); also accepted as Authorization: Bearer. Required."
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/subadmin/password": {
   "post": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Self-service password change for the authenticated sub-admin.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"current\":\"kitting-sub\",\"next\":\"newpass123\"}"
      }
     }
    }
   }
  },
  "/api/notes/{tenantId}": {
   "get": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Lists support notes for a tenant (admin any tenant, sub-admin scoped).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"notes\":[{\"id\":\"nt-...\",\"ts\":1700000000000,\"by\":\"サポート担当 山田\",\"text\":\"...\",\"done\":false}]}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "tenantId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id; sub-admin must have it in scope (seed: t-itabashi)"
     },
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Alternative to Bearer header"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   },
   "post": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Adds a support note (max 600 chars) to a tenant's note list.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"note\":{\"id\":\"nt-...\",\"ts\":1700000000000,\"by\":\"サポート担当 山田\",\"text\":\"...\",\"done\":false}}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "tenantId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id, scoped for sub-admin"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"text\":\"クライアントへ連絡済み\"}"
      }
     }
    }
   }
  },
  "/api/notes/{tenantId}/{noteId}": {
   "post": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Updates (text/done) or deletes a specific support note.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"note\":{\"id\":\"nt-...\",\"done\":true,\"text\":\"...\"}}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "tenantId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id, scoped"
     },
     {
      "name": "noteId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Note id (nt-...)"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"done\":true}"
      }
     }
    }
   }
  },
  "/api/files/{tenantId}": {
   "get": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Lists uploaded file metadata (no data blob) for a tenant.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"files\":[{\"id\":\"fl-...\",\"ts\":1700000000000,\"by\":\"サポート担当 山田\",\"name\":\"手順書.pdf\",\"type\":\"application/pdf\",\"size\":10240}]}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "tenantId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id, scoped (seed: t-itabashi)"
     },
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Alternative to Bearer header"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   },
   "post": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Uploads a base64 file/document (up to ~2MB) attached to a tenant.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"file\":{\"id\":\"fl-...\",\"ts\":1700000000000,\"by\":\"...\",\"name\":\"doc.pdf\",\"type\":\"application/pdf\",\"size\":12345}}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "tenantId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id, scoped"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"doc.pdf\",\"type\":\"application/pdf\",\"data\":\"<base64>\"}"
      }
     }
    }
   }
  },
  "/api/files/{tenantId}/{fileId}/download": {
   "get": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Returns a single file's full base64 data for download.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"name\":\"doc.pdf\",\"type\":\"application/pdf\",\"data\":\"<base64>\"}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "tenantId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id, scoped"
     },
     {
      "name": "fileId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "File id (fl-...); 404 if not found"
     },
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Alternative to Bearer header"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/files/{tenantId}/{fileId}/delete": {
   "post": {
    "tags": [
     "server:subadmin"
    ],
    "summary": "Deletes an uploaded file from a tenant's file list.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "tenantId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id, scoped"
     },
     {
      "name": "fileId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "File id to remove"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/backups/{tenantId}": {
   "get": {
    "tags": [
     "server:backup"
    ],
    "summary": "List backup snapshot metadata for one tenant (newest first), for admin or the scoped sub-admin.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"backups\":[{\"id\":\"bk-lz...\",\"ts\":1784600000000,\"by\":\"system\",\"reason\":\"日次自動バックアップ\",\"sites\":1}]}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "tenantId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id, e.g. t-itabashi (seed tenants: t-itabashi, t-osaka, t-nagoya, t-fukuoka, t-sapporo)."
     },
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Admin or sub-admin JWT; also accepted as Authorization: Bearer. Required."
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/export/state": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Full runtime-data dump (all in-memory collections + counts) for automated backups; requires the backup token.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":false,\"error\":\"unauthorized\"} (no token) | {\"ok\":true,\"exportedAt\":..,\"tenants\":{..},\"leads\":{..},\"counts\":{..}}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "token",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Backup token; alternatively X-Backup-Token header. Must equal BACKUP_TOKEN"
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/backup/{tenantId}": {
   "post": {
    "tags": [
     "server:backup"
    ],
    "summary": "Create an on-demand snapshot of one tenant (deep-copies tenant record + its sites) with an optional reason label.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"backup\":{\"id\":\"bk-lz9x...\",\"ts\":1784675000000,\"by\":\"マスター管理者\",\"reason\":\"手動バックアップ\",\"sites\":1}}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "tenantId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id to snapshot, e.g. t-itabashi."
     },
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Admin or scoped sub-admin JWT (or Authorization: Bearer). Required."
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"reason\":\"設定変更前の手動バックアップ\"}"
      }
     }
    }
   }
  },
  "/api/restore/{tenantId}/{backupId}": {
   "post": {
    "tags": [
     "server:backup"
    ],
    "summary": "Rollback: restore a tenant (and its sites) from a stored snapshot; auto-snapshots current state first for recoverability.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"tenant\":{\"id\":\"t-itabashi\",\"name\":\"板橋キッティングセンター\",\"plan\":\"Business\",\"sites\":[\"itabashi-kitting\"],...}}"
       }
      }
     },
     "401": {
      "description": "unauthorized"
     }
    },
    "parameters": [
     {
      "name": "tenantId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Tenant id to roll back, e.g. t-itabashi."
     },
     {
      "name": "backupId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Snapshot id to restore, e.g. bk-lz9x2a (obtained from GET /api/backups/:tenantId)."
     },
     {
      "name": "token",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Admin or scoped sub-admin JWT (or Authorization: Bearer). Required."
     }
    ],
    "security": [
     {
      "bearerAuth": []
     }
    ]
   }
  },
  "/api/synthia": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Synthia AI chat proxy: forwards message+history to Anthropic Claude if ANTHROPIC_API_KEY is set, else returns a fallback flag so the client uses its built-in mock.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":false,\"fallback\":true,\"reason\":\"no_key\"} | {\"ok\":true,\"text\":\"...\",\"model\":\"claude-opus-4-8\"}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"message\":\"string\",\"history\":[{\"role\":\"user\",\"content\":\"...\"}]}"
      }
     }
    }
   }
  },
  "/api/marketprice": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Live used-device market-price lookup for trade-in/buyback via SerpAPI / Yahoo Shopping / Google CSE; returns reference note if no key configured.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"live\":false,\"model\":\"iPhone 13\",\"note\":\"ライブ相場APIキーが未設定です…\"}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "model",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Device model keyword (<=80 chars); 400 if missing"
     },
     {
      "name": "cond",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Condition keyword (<=24 chars)"
     }
    ]
   }
  },
  "/api/connect/status": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Lists all 13 integration providers (MDM/Apple/shipping/accounting/chat) with connected state, configured flag, redirectUri and go-live guide.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"origin\":\"https://aisnsio-kitting.hf.space\",\"providers\":[{\"key\":\"intune\",\"name\":\"Microsoft Intune\",\"group\":\"MDM\",\"authType\":\"oauth\",\"connected\":false,\"configured\":false,\"guide\":{...}}]}"
       }
      }
     }
    }
   }
  },
  "/api/connect/{provider}": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Starts OAuth: redirects (302) to the provider authorize URL for oauth providers, or returns an HTML popup for credential providers / unset creds.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "HTTP 200 text/html popup, OR 302 redirect to authorize URL, OR 404 text 'unknown provider'"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "provider",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Provider key e.g. intune, freee, moneyforward, lineworks"
     }
    ]
   }
  },
  "/api/connect/{provider}/callback": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "OAuth redirect callback: validates state, exchanges the code for tokens against the provider, stores them, and returns an HTML popup that postMessages the opener.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "HTTP 200 text/html popup ('接続に失敗しました' on state mismatch / '接続が完了しました' on success)"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "provider",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Provider key"
     },
     {
      "name": "code",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "OAuth authorization code"
     },
     {
      "name": "state",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "CSRF state from the authorize step"
     },
     {
      "name": "error",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Provider error code"
     }
    ]
   }
  },
  "/api/connect/{provider}/credentials": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Saves API-key/token/webhook credentials for a credential-type provider; for webhook providers sends a real test POST to validate.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"tested\":true} | {\"ok\":false,\"error\":\"...が未入力です\"}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "provider",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Credential provider key e.g. jamf, teams, yamato"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"fields\":{\"webhook\":\"https://...\"}}"
      }
     }
    }
   }
  },
  "/api/connect/{provider}/disconnect": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Removes stored tokens/credentials for a provider.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "provider",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Provider key"
     }
    ]
   }
  },
  "/api/site/publish": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Publishes a white-label tenant marketing site (slug+HTML) served at /site/<slug>.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"slug\":\"acme\",\"url\":\"https://.../site/acme\"}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"slug\":\"acme\",\"html\":\"<html>..\",\"name\":\"Acme\"}"
      }
     }
    }
   }
  },
  "/api/site/list": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Lists all published white-label sites with slug, name, updated timestamp and public URL.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"sites\":[{\"slug\":\"acme\",\"name\":\"Acme\",\"updated\":1780000000000,\"url\":\"https://.../site/acme\"}]}"
       }
      }
     }
    }
   }
  },
  "/api/site/{slug}/unpublish": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Deletes a published site by slug.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Site slug to remove"
     }
    ]
   }
  },
  "/site/{slug}": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Serves the published tenant site's raw HTML (not an /api path); 404 HTML page if slug unknown.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "<!doctype html>...stored site HTML... (or 404 HTML)"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Published site slug"
     }
    ]
   }
  },
  "/api/lead": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Public lead capture: stores a CRM lead from site widgets/forms.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"id\":\"L-xxxx\"}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"..\",\"email\":\"..\",\"company\":\"..\",\"message\":\"..\"}"
      }
     }
    }
   }
  },
  "/api/leads": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Returns all CRM leads (newest first).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"leads\":[{\"id\":\"L-..\",\"name\":\"QA Test\",\"email\":\"qa@example.com\",\"source\":\"signup\",\"status\":\"new\",\"created\":1784675609773}]}"
       }
      }
     }
    }
   }
  },
  "/api/lead/{id}": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Updates a lead's status/notes/contact fields.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"lead\":{...}} | 404 {\"ok\":false}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Lead id"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"status\":\"contacted\",\"notes\":\"..\"}"
      }
     }
    }
   }
  },
  "/api/lead/{id}/delete": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Deletes a lead by id.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Lead id"
     }
    ]
   }
  },
  "/api/sign/create": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Creates a signable e-signature document (quote/contract) and returns its signing URL.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"id\":\"SG-xxxx\",\"url\":\"https://.../sign.html?id=SG-xxxx\"}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"title\":\"..\",\"client\":\"..\",\"amount\":\"..\",\"bodyHtml\":\"..\"}"
      }
     }
    }
   }
  },
  "/api/sign/{id}": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Fetches a signable document (public signing page reads this).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"doc\":{\"id\":\"SG-..\",\"title\":\"..\",\"status\":\"pending\",...}} | 404 {\"ok\":false,\"error\":\"not found\"}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Document id SG-xxxx"
     }
    ]
   }
  },
  "/api/sign/{id}/submit": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Signs a document: records signer name/signature/timestamp/IP and marks status 'signed'.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"status\":\"signed\",\"signedAt\":1780000000000}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Document id"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"..\",\"agreed\":true,\"signature\":\"data:..\"}"
      }
     }
    }
   }
  },
  "/api/sign/{id}/status": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Returns just the signing status of a document.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"status\":\"pending\",\"signedName\":\"\",\"signedAt\":0} | 404 {\"ok\":false}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Document id"
     }
    ]
   }
  },
  "/api/signs": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Lists all e-signature documents (metadata, newest first).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"signs\":[{\"id\":\"SG-..\",\"title\":\"..\",\"client\":\"..\",\"status\":\"pending\",\"created\":..}]}"
       }
      }
     }
    }
   }
  },
  "/api/booking": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Public consultation booking; also creates a linked CRM lead with status 'meeting'.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"id\":\"B-xxxx\"}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"name\":\"..\",\"email\":\"..\",\"when\":\"..\",\"topic\":\"..\"}"
      }
     }
    }
   }
  },
  "/api/bookings": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Lists all consultation bookings (newest first).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"bookings\":[{\"id\":\"B-..\",\"name\":\"..\",\"email\":\"..\",\"status\":\"requested\",\"created\":..}]}"
       }
      }
     }
    }
   }
  },
  "/api/booking/{id}": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Updates a booking's status.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"booking\":{...}} | 404 {\"ok\":false}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Booking id"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"status\":\"confirmed\"}"
      }
     }
    }
   }
  },
  "/api/referral": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Creates or updates a referral code for a referrer and returns the shareable ?ref= URL.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"code\":\"abc12\",\"url\":\"https://.../?ref=abc12\"}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"referrer\":\"..\",\"code\":\"..\"}"
      }
     }
    }
   }
  },
  "/api/referrals": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Lists all referral codes with click/lead counts.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"referrals\":[{\"code\":\"abc12\",\"referrer\":\"..\",\"clicks\":0,\"leads\":0,\"created\":..}]}"
       }
      }
     }
    }
   }
  },
  "/api/referral/{code}/track": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Increments the click counter for a referral code.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "code",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Referral code (case-insensitive)"
     }
    ]
   }
  },
  "/api/signup": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Self-service trial signup: creates a trial (Starter) tenant and a linked CRM lead, returns the prospect app URL.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"tenant\":{\"id\":\"t-..\",\"name\":\"..\"},\"url\":\"https://.../app?prospect=..\"}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"email\":\"..\",\"company\":\"..\",\"name\":\"..\"}"
      }
     }
    }
   }
  },
  "/api/partner": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Partner/agency program application; creates a partner record + referral code + linked lead.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"id\":\"P-xxxx\",\"code\":\"acme12\",\"referralUrl\":\"https://.../?ref=acme12\"}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"company\":\"..\",\"email\":\"..\",\"contact\":\"..\",\"region\":\"..\"}"
      }
     }
    }
   }
  },
  "/api/partners": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Lists all partner applications (newest first).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"partners\":[{\"id\":\"P-..\",\"company\":\"..\",\"email\":\"..\",\"tier\":\"Silver\",\"status\":\"applied\",\"code\":\"..\"}]}"
       }
      }
     }
    }
   }
  },
  "/api/partner/{id}": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Updates a partner's status/tier/deals/revenue/note.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"partner\":{...}} | 404 {\"ok\":false}"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Partner id"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"status\":\"active\",\"tier\":\"Gold\",\"deals\":3,\"revenue\":500000}"
      }
     }
    }
   }
  },
  "/api/review": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Public review/NPS submission (stored unapproved pending moderation).",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"rating\":5,\"comment\":\"..\",\"name\":\"..\",\"nps\":9}"
      }
     }
    }
   }
  },
  "/api/reviews": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Lists approved reviews with computed average rating, count and NPS score.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"reviews\":[{\"id\":\"RV-965da4\",\"name\":\"情シス部長\",\"company\":\"北斗商事\",\"rating\":5,\"nps\":9,...}],\"avg\":4.7,\"count\":3,\"nps\":..}"
       }
      }
     }
    }
   }
  },
  "/api/events": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Returns the static list of 3 upcoming webinars/events.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"events\":[{\"id\":\"ev-mdm\",\"title\":\"30分でわかる：Intune × キッティング自動化\",\"date\":\"2026-08-05 15:00\",\"seats\":100}]}"
       }
      }
     }
    }
   }
  },
  "/api/event/{id}/register": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Registers for a webinar; creates a linked CRM lead (source 'webinar').",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true} | 404 {\"ok\":false} for unknown event"
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Event id (ev-mdm|ev-lcm|ev-ai)"
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"email\":\"..\",\"name\":\"..\",\"company\":\"..\"}"
      }
     }
    }
   }
  },
  "/api/ab": {
   "post": {
    "tags": [
     "server:misc"
    ],
    "summary": "Records an A/B landing test event (view or convert) for variant a or b.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true}"
       }
      }
     }
    },
    "requestBody": {
     "content": {
      "application/json": {
       "example": "{\"variant\":\"b\",\"event\":\"convert\"}"
      }
     }
    }
   },
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Returns current A/B test counters for variants a and b.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"ok\":true,\"stats\":{\"a\":{\"view\":0,\"convert\":0},\"b\":{\"view\":1,\"convert\":0}}}"
       }
      }
     }
    }
   }
  },
  "/healthz": {
   "get": {
    "tags": [
     "server:misc"
    ],
    "summary": "Health check.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "example": "{\"status\":\"ok\"}"
       }
      }
     }
    }
   }
  }
 }
}