# 用 Markdown 寫 API 規格教學
> 範例以「案件申貸狀態同步」與「案件推廣來源」兩個端點示範。
[TOC]
---
## 1. 文件說明(Overview)
- **目的**:用 *Markdown* 撰寫一份清楚、可維護的 **API 規格**,包含端點定義、參數、請求/回應格式、錯誤碼、範例與 JSON Schema。
- **風格**:採「純 Markdown」為主,並補充「API Blueprint 速記法」區塊,方便團隊快速擬稿。
- **版本**:v1.0.0
- **主機(HOST)**:`https://[domain]/service/api/{Action}`
- **認證**:`Authorization: Bearer {ACCESS_TOKEN}`
- **內容格式**:`application/json; charset=utf-8`
---
## 2. 全域慣例(Conventions)
- **時間格式**:除非特別註明,皆為 `yyyyMMdd` 或 `yyyy-MM-ddTHH:mm:ssZ`(UTC)。
- **字串編碼**:UTF-8。
- **安全性**:所有寫入類(POST/PUT/PATCH/DELETE)應驗證 Bearer Token。
- **冪等性(Idempotency)**:建議對關鍵寫入操作設計冪等鍵(如 `UPD_KEY`)。
- **錯誤模型**:統一以 `Result.ReturnCode != 0` 作為非成功訊號(見 §6)。
---
## 3. 快速藍本:API Blueprint 速記法(*可選*)
> 若團隊熟悉 API Blueprint,可用下列速記區塊在 HackMD 擬稿,等同於「規格提綱」。
```apib
FORMAT: 1A
HOST: https://[domain]/service/api/{Action}
# 雲端API
## 案件申貸狀態同步 [/SyncLoanCase]
### 取得案件申貸狀態 [GET]
+ Response 200 (application/json)
[
{ /* ... */ }
]
### 異動案件申貸狀態、相關欄位 [POST]
+ Headers
Authorization: Bearer {ACCESS_TOKEN}
+ Request (application/json)
{ /* 請見正式規格 §4.2 請求體 */ }
+ Response 200 (application/json)
{ /* 請見正式規格 §4.3 回應體 */ }
```
> 下方為「正式 Markdown 版規格」,提供完整欄位說明、枚舉、JSON Schema 與範例。
---
## 4. 端點:案件申貸狀態同步 `/SyncLoanCase`
### 4.1 取得案件申貸狀態(GET)
- **Method**:`GET /SyncLoanCase`
- **說明**:依查詢條件取得案件申貸狀態列表(若未提供條件,依系統預設限制返回最近更新)。
- **查詢參數(Query)**:
| 參數 | 型別 | 必填 | 範例 | 說明 |
|---|---|---|---|---|
| `caseId` | string | 否 | `TEST123` | 指定單一案件編號 |
| `updatedSince` | string(yyyyMMdd) | 否 | `20250101` | 只取此日期(含)之後異動 |
| `page` | int | 否 | `1` | 分頁頁碼(>=1) |
| `pageSize` | int | 否 | `50` | 單頁筆數(<=100) |
- **成功回應 200(application/json)**:
```json
[
{
"CASE_ID": "TEST123",
"CASE_STATUS": "1",
"CASE_TYPE": "1",
"UPDATED_AT": "2025-10-01T08:30:00Z"
}
]
```
- **錯誤回應**:見 §6 錯誤模型。
---
### 4.2 異動案件申貸狀態、相關欄位(POST)
- **Method**:`POST /SyncLoanCase`
- **說明**:更新案件申貸狀態與周邊資訊,支援冪等鍵 `UPD_KEY` 以避免重複寫入。
- **必要標頭(Headers)**:
- `Authorization: Bearer {ACCESS_TOKEN}`
- `Content-Type: application/json`
- **請求體(Request Body)**:主要欄位如下;完整驗證規則見 §7 JSON Schema。
| 欄位 | 型別/格式 | 必填 | 範例 | 說明 |
|---|---|---|---|---|
| `MOTO_CASE_MAJ` | string | 是 | `"string"` | 案件唯一值 |
| `CASE_TYPE` | enum string | 否 | `"1"` | `0=None, 1=Mobile, 2=Scooter, 3=Hybrid` |
| `CASE_STATUS` | string | 否 | `"1"` | 申貸狀態代碼(內規定義) |
| `PRD_NUM` | string | 否 | `"4000"` | 商品代碼 |
| `PRD_AMT` | string | 否 | `"4000"` | 商品金額 |
| `LOAN_AMT` | string | 否 | `"4000"` | 申貸金額 |
| `HAND_FEE` | string | 否 | `"4000"` | 手續費 |
| `DSB_AMT` | string | 否 | `"4000"` | 撥款金額 |
| `CR_DOC_FILE_U_ID` | uuid string | 否 | `"ee1d662d-54ca-441e-919a-abe93b15d51d"` | 徵審文件檔 UUID |
| `UPD_KEY` | string | 是 | `"{UPD_KEY}"` | 冪等鍵(同請求多次只生效一次) |
| `SIGN_CONTRACT` | `"0"|"1"` | 否 | `"1"` | 是否簽約 |
| `PAYMENT_UNUSUAL` | `"0"|"1"` | 否 | `"1"` | 付款是否異常 |
| `CONTRACT_BEFORE_FILE_UUID` | uuid | 否 | `"ee1d...d51d"` | 簽約前檔案 |
| `CONTRACT_SIGN_FILE_UUID` | uuid | 否 | `"ee1d...d51d"` | 簽約後檔案 |
| `IS_ABT` | `"0"|"1"` | 否 | `"1"` | 是否棄件/撤件(依定義) |
| `IS_END` | `"0"|"1"` | 否 | `"1"` | 是否已結案 |
| `CONTACT_METHOD` | enum | 否 | `"1"` | 連絡方式代碼 |
| `APP_FIRST_DT` | string(yyyyMMdd) | 否 | `"19990101"` | 初次申請日 |
| `CREDIT_SCORE` | string | 否 | `"80"` | 信評分數 |
| `CUST_ID` | string | 否 | `"X123123123"` | 身分證/統編 |
| `MOBILE_1` | string | 否 | `"0912123123"` | 手機 |
| `MOVABLES_AMT` | string | 否 | `"20000"` | 動產金額 |
| `REAL_DSB_AMT` | string | 否 | `"1500"` | 實際撥款金額 |
| `CC` | string | 否 | `"100"` | 其他代碼 |
| `BIRTHDAY` | string(yyyyMMdd) | 否 | `"19880707"` | 生日 |
| `MOBILE_IMEI_01` | string | 否 | `"357221098977524"` | IMEI |
| `MOBILE_IMEI_02` | string | 否 | `"353990106364578"` | 备用 IMEI |
| `MOBILE_BRAND` | string | 否 | `"Apple"` | 手機品牌 |
| `MOBILE_MODEL` | string | 否 | `"6s"` | 手機型號 |
| `ID_CARD_DATE` | string | 否 | `"20200101"` | 證件發證日(如適用) |
| `CASE_ID` | string | 否 | `"TEST123"` | 案件代碼(系統/外部來源) |
- **請求體範例**:
```json
{
"MOTO_CASE_MAJ": "string",
"CASE_TYPE": "1",
"CASE_STATUS": "1",
"PRD_NUM": "4000",
"PRD_AMT": "4000",
"LOAN_AMT": "4000",
"HAND_FEE": "4000",
"DSB_AMT": "4000",
"CR_DOC_FILE_U_ID": "ee1d662d-54ca-441e-919a-abe93b15d51d",
"UPD_KEY": "{UPD_KEY}",
"SIGN_CONTRACT": "1",
"PAYMENT_UNUSUAL": "1",
"CONTRACT_BEFORE_FILE_UUID": "ee1d662d-54ca-441e-919a-abe93b15d51d",
"CONTRACT_SIGN_FILE_UUID": "ee1d662d-54ca-441e-919a-abe93b15d51d",
"IS_ABT": "1",
"IS_END": "1",
"CONTACT_METHOD": "1",
"APP_FIRST_DT": "19990101",
"CREDIT_SCORE": "80",
"CUST_ID": "X123123123",
"MOBILE_1": "0912123123",
"MOVABLES_AMT": "20000",
"REAL_DSB_AMT": "1500",
"CC": "100",
"BIRTHDAY": "19880707",
"MOBILE_IMEI_01": "357221098977524",
"MOBILE_IMEI_02": "353990106364578",
"MOBILE_BRAND": "Apple",
"MOBILE_MODEL": "6s",
"ID_CARD_DATE": "20200101",
"CASE_ID": "TEST123"
}
```
- **成功回應 200(application/json)**:
```json
{
"Data": {
"IS_SUCCESS": "Y",
"ERR_MSG": ""
},
"Result": {
"ReturnCode": 0,
"ReturnMsg": "更新成功",
"Alert": "更新成功",
"Count": 1
}
}
```
- **常見狀態碼**:
- `200 OK`:更新成功
- `400 Bad Request`:參數格式錯誤、缺少必填欄位
- `401 Unauthorized`:Token 失效或未帶入
- `409 Conflict`:冪等鍵重複提交(已處理)
- `422 Unprocessable Entity`:商業規則未通過
- `500 Internal Server Error`:伺服器錯誤
---
## 5. 端點:案件推廣來源 `/NewsSrc`
### 5.1 取得案件推廣來源(GET)
- **Method**:`GET /NewsSrc`
- **說明**:取得推廣來源清單(僅返回 `IsEnable=1` 且依 `Sort` 排序)。
- **成功回應 200(application/json)**:
```json
[
{ "NewsValue": 1, "NewsText": "Google搜尋" },
{ "NewsValue": 16, "NewsText": "貸鼠先生" },
{ "NewsValue": 2, "NewsText": "網路廣告" },
{ "NewsValue": 3, "NewsText": "簡訊推廣" },
{ "NewsValue": 4, "NewsText": "新聞網站" },
{ "NewsValue": 48, "NewsText": "電話推廣" },
{ "NewsValue": 5, "NewsText": "報章雜誌" },
{ "NewsValue": 6, "NewsText": "電視媒體" },
{ "NewsValue": 7, "NewsText": "親友介紹" }
]
```
---
## 6. 統一錯誤模型與處理建議
- **回應結構**(建議):
```json
{
"Result": {
"ReturnCode": 1234,
"ReturnMsg": "錯誤訊息",
"TraceId": "dd0c836e-...",
"Alert": "可顯示給使用者的友善提示"
},
"Data": null
}
```
- **處理原則**:
- `ReturnCode = 0` 視為成功,其餘為失敗。
- 需記錄 `TraceId` 於後端日誌,方便追查。
- 對外錯誤訊息應避免洩露內部系統訊息。
---
## 7. JSON Schema(請求體驗證:/SyncLoanCase POST)
> 可用於前後端驗證與合約測試。
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "SyncLoanCaseRequest",
"type": "object",
"additionalProperties": false,
"properties": {
"MOTO_CASE_MAJ": { "type": "string", "minLength": 1 },
"CASE_TYPE": { "type": "string", "enum": ["0", "1", "2", "3"] },
"CASE_STATUS": { "type": "string" },
"PRD_NUM": { "type": "string" },
"PRD_AMT": { "type": "string", "pattern": "^[0-9]+$" },
"LOAN_AMT": { "type": "string", "pattern": "^[0-9]+$" },
"HAND_FEE": { "type": "string", "pattern": "^[0-9]+$" },
"DSB_AMT": { "type": "string", "pattern": "^[0-9]+$" },
"CR_DOC_FILE_U_ID": { "type": "string", "format": "uuid" },
"UPD_KEY": { "type": "string", "minLength": 1 },
"SIGN_CONTRACT": { "type": "string", "enum": ["0", "1"] },
"PAYMENT_UNUSUAL": { "type": "string", "enum": ["0", "1"] },
"CONTRACT_BEFORE_FILE_UUID": { "type": "string", "format": "uuid" },
"CONTRACT_SIGN_FILE_UUID": { "type": "string", "format": "uuid" },
"IS_ABT": { "type": "string", "enum": ["0", "1"] },
"IS_END": { "type": "string", "enum": ["0", "1"] },
"CONTACT_METHOD": { "type": "string" },
"APP_FIRST_DT": { "type": "string", "pattern": "^\\d{8}$" },
"CREDIT_SCORE": { "type": "string", "pattern": "^[0-9]+$" },
"CUST_ID": { "type": "string" },
"MOBILE_1": { "type": "string" },
"MOVABLES_AMT": { "type": "string", "pattern": "^[0-9]+$" },
"REAL_DSB_AMT": { "type": "string", "pattern": "^[0-9]+$" },
"CC": { "type": "string" },
"BIRTHDAY": { "type": "string", "pattern": "^\\d{8}$" },
"MOBILE_IMEI_01": { "type": "string", "pattern": "^\\d{14,17}$" },
"MOBILE_IMEI_02": { "type": "string", "pattern": "^\\d{14,17}$" },
"MOBILE_BRAND": { "type": "string" },
"MOBILE_MODEL": { "type": "string" },
"ID_CARD_DATE": { "type": "string" },
"CASE_ID": { "type": "string" }
},
"required": ["MOTO_CASE_MAJ", "UPD_KEY"]
}
```
---
## 8. 範例請求(cURL / C#)
### 8.1 cURL:POST /SyncLoanCase
```bash
curl -X POST "https://[domain]/service/api/SyncLoanCase" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"MOTO_CASE_MAJ": "string",
"UPD_KEY": "abc123",
"CASE_STATUS": "1"
}'
```
### 8.2 C#:POST /SyncLoanCase(RestSharp 範例)
```csharp
var client = new RestClient("https://[domain]/service/api/SyncLoanCase");
var req = new RestRequest(Method.Post);
req.AddHeader("Authorization", "Bearer {ACCESS_TOKEN}");
req.AddHeader("Content-Type", "application/json");
req.AddStringBody(@"{
""MOTO_CASE_MAJ"": ""string"",
""UPD_KEY"": ""abc123"",
""CASE_STATUS"": ""1""
}", DataFormat.Json);
var res = await client.ExecuteAsync(req);
Console.WriteLine(res.Content);
```
---
## 9. Database 參考 SQL(對應 `/NewsSrc`)
```sql
/****** SSMS 中 SelectTopNRows 命令的指令碼 ******/
SELECT [NewsValue], [NewsText]
FROM [dbo].[NEWS_OPTION]
WHERE IsEnable = 1
ORDER BY [Sort];
```
---
## 10. 撰寫檢查清單(Checklist)
- [ ] 主機/環境位址是否明確(Dev/SIT/UAT/Prod)
- [ ] 認證與權限(Bearer、Scopes)是否定義
- [ ] 必填欄位與枚舉值是否完整列出
- [ ] JSON Schema 是否覆蓋格式/型別/模式
- [ ] 回應模型(成功/錯誤)是否一致
- [ ] 範例(cURL/C#)是否可直接執行
- [ ] 變更紀錄(Changelog)是否更新
---
## 11. 變更紀錄(Changelog)
- **v1.0.0 (2025-10-06)**:初版釋出(新增 `/SyncLoanCase`、`/NewsSrc` 規格、JSON Schema、範例程式、SQL 參考)。