<!-- 此檔為 /docs 頁（docs.html）的 Markdown 對應版，供開發者下載並餵給 AI 工具。
     內容須與 docs.html 同步更新——兩者是同一份開發者指南的雙載體。 -->

# Build with AI-Pay — 開發者指南

> 本文件來源：`https://ai-pay.coswic.ai/docs.md?lang=zh-Hant`（網頁版：`https://ai-pay.coswic.ai/docs`）。重新下載即取得最新版；SDK／CLI 的現行版本以 npm 上的 `@coswic/aipay-sdk`／`@coswic/aipay` 為準。

AI-Pay 讓你的 App 用**使用者自己的 AI credits** 呼叫模型：使用者透過「Sign in with AI-Pay」給你的 App 一份**「AI 額度授權」**，在自己設定的花費上限內，每一筆 AI 用量直接從使用者的 AI-Pay 錢包結算。

- **你不碰卡、不墊成本**——付款、額度、風控都在 AI-Pay；你的 App 沒有模型帳單。
- **使用者隨時可控**——暫停、撤銷、調整上限會立即阻止新的 AI 用量（已派發的請求仍可能依原規則結算）；你的 App 要能處理對應的 403。
- **帳跟著回應走**——每個成功回應附帶收據（實際扣款與分帳）；每筆都可對帳。

> 正式環境現況為**免費封測**：僅受邀使用者可以建立帳號及使用服務，不開放一般公開註冊。**你的使用者第一次透過你的 App 的「Sign in with AI-Pay」登入前，也必須先獲得邀請**；沒有 AI-Pay 帳號又未受邀的人會停在 AI-Pay 登入頁、無法完成授權。測試額度依邀請而定；正式環境目前不受理儲值，額度用完後使用者無法自行增加。邀請條件、測試額度與各功能狀態見 [`/about`](https://ai-pay.coswic.ai/about#status)。其他環境的功能與付款方式依該環境的畫面為準；Stripe／PayPal 測試模式不會實際扣款，測試額度不代表真實付款或開發者收入。
> 以下範例的 API base 是 `https://ai-pay.coswic.ai`、授權端點是 `https://auth.ai-pay.coswic.ai`——**這兩個值由你取得本文件的那個環境代入**，直接複製就能用。

金額一律以 USD 顯示，傳輸為整數 µUSD 字串（$1 = 1,000,000 µUSD）。

---

## 快速開始

開始前，請先到 [Dashboard](https://ai-pay.coswic.ai/dashboard?lang=zh-Hant) 用自己的 Google 帳戶登入，依畫面完成所在地區資料與現行服務條款確認，再進入 Console。第一次登入建立帳號需要有效邀請；安裝 SDK 或 CLI 不會略過這些前置。若 API 回 `region_required`，請回 Dashboard 完成設定，或使用下方的 `aipay region` 指令。

1. 在 Console（App 控制台）右上角「建立 App」：只填名稱。本機 callback `http://127.0.0.1:{Port}/auth/aipay/callback` 會自動登記（`127.0.0.1` 不比對埠，任何本機埠都能用；正式 HTTPS 網址之後用下方 CLI callback 指令加入），並取得 `client_id`。AI-Pay 用 public client＋PKCE S256，**沒有 client secret**。建立後 Console 會給你一段貼給 coding agent 的提示，或自己跑 CLI——見下節。
2. 取得 SDK（TypeScript，伺服器與瀏覽器同一份）：`npm i @coswic/aipay-sdk`（已發佈於公開 npm）。瀏覽器使用守則見「無伺服器 App」節。
3. 把使用者導向授權連結（見下節）；使用者在 AI-Pay 登入、設定上限、同意後回到你的 callback。
4. 用 code 換 token，呼叫 `POST /v1/chat/completions` 送出第一筆請求。
5. 回 Console 的「請求」分頁對帳（request_id 逐筆可查）。App 服務費與提領目前未開放（見「App 定價（服務費）」與「用量對帳與收款」節）；現階段接入的終點是成功回應與可核對的收據。

---

## 用 CLI 或 coding agent 接上（推薦）

建立 App 後，Console 會給你一段可以直接貼給 Claude Code、Cursor、Codex 等工具的提示；AI 會讀這份文件、照你的 App 設定完成串接。提示裡不含任何 token 或金鑰。你也可以自己跑同一個 CLI（`@coswic/aipay`，公開 npm）：

```
npx @coswic/aipay@latest login --api https://ai-pay.coswic.ai
npx @coswic/aipay@latest init --app <client_id> --auth https://auth.ai-pay.coswic.ai --api https://ai-pay.coswic.ai
npx @coswic/aipay@latest test
```

CLI 開發者登入用來管理自己的 App，與使用者授權 App 使用 AI 額度是兩份獨立授權。遠端機器使用 `login --device`，請開發者本人在瀏覽器批准。部署前執行 `aipay apps callbacks add --app <client_id> --callback https://yourapp.example/auth/aipay/callback`，會保留既有本機 callback；用 `apps callbacks list` 檢視、`apps callbacks remove --callback <url>` 移除單個網址。在已連結專案中可省略 `--app`。`init --callback` 只設定本地接線，不會登記伺服器。遇到同時修改或結果未知，需讀回現況，CLI 不會盲目重送寫入；修改 callback 需要服務支援 ETag。

`init` 在你的專案裡做這些事（不覆寫既有檔案、重跑冪等）：偵測套件管理器與框架（Next.js／Express／Fastify／Hono／Node，或 Vite 瀏覽器 SPA；`--framework` 可覆寫；TypeScript 或 JavaScript），寫 `.aipay/project.json`（可提交、無機密）、`.env.local`（四個公開值 `AIPAY_CLIENT_ID`／`AIPAY_AUTH_ORIGIN`／`AIPAY_API_BASE`／`AIPAY_CALLBACK_URL`，並補進 `.gitignore`）、一組 start／callback 路由範本、`aipay-agent.md`（專案事實＋整份文件，給 agent 離線讀），最後安裝 `@coswic/aipay-sdk`。

`test` 是唯一能從頭到尾驗證的方法：CLI 在 `127.0.0.1` 的任一空埠收回呼（AI-Pay 對 loopback 不比對埠，你的 dev server 可以繼續跑），開瀏覽器讓你用自己的帳號登入並授權，換 token、以你的身份送一筆 chat completion、印出收據（`request_id`、金額）。Console「驗證」步的綠燈來自伺服器端事實，不是 CLI 自報。

| 指令 | 用途 |
|---|---|
| `aipay init --app <client_id> [--auth] [--api] [--callback] [--dry-run] [--no-install]` | 連結專案並產生串接程式碼；登入後 `--auth`／`--api` 可省 |
| `aipay test [--model] [--port] [--no-open]` | 真登入＋第一筆請求＋收據 |
| `aipay doctor [--offline]` | 檢查 Node、連結、`.env.local`、SDK、授權伺服器與 API 可達性 |
| `aipay status`／`aipay unlink` | 看連結到哪個 App／解除連結（保留你的程式碼） |
| `aipay login --api https://ai-pay.coswic.ai`／`whoami`／`logout` | 開發者本人登入 CLI（loopback PKCE、audience `aipay-developer-api`；遠端／無瀏覽器的機器加 `--device`：印出網址與代碼，在任何裝置的瀏覽器輸入即可）；token 存 OS keychain（macOS Keychain／Linux keyring／Windows DPAPI 加密檔），都沒有才退到權限 0600 的檔案 |
| `aipay apps list`／`aipay apps create --name <名稱>`／`aipay init --create <名稱>` | 登入後用 CLI 建 App（帶 `Idempotency-Key`，重跑不會建第二個）並直接連結 |
| `aipay region`／`aipay region set --country <國家碼> [--state US-XX --line1 <街道門牌> --city <城市> --zip <ZIP>] --accept-terms` | 看或設定帳戶的所在地區並同意現行服務條款；美國帳戶另需完整地址（後台據此定位銷售稅轄區：州／郡／市／特別區，`aipay region` 會顯示定位結果）；沒填之前開發者 API 一律回 403 `region_required`（`aipay region` 會印出要同意的版本與條款網址） |

直接呼叫 `POST /v1/developer/apps` 建立 App 時，建議帶 `Idempotency-Key`；相同 key 的名稱與 Redirect URI 清單必須相同。成功回 201，同 key 查回已完成的 App 回 200。409 `provisioning_in_progress` 或 502 `oauth_provider_error` 且 `details.outcome: 'unknown'`，請稍後沿用同 key 重試；502 且 `outcome: 'cancelled'` 表示這次建立已取消，可以沿用同 key 重新建立。若已主動刪除成功建立的 App，原 key 回 409 `idempotency_key_consumed`，新 App 需用新 key。未帶 key 的建立若中斷且 15 分鐘仍未完成，系統會安排自動清理。

每個指令都有 `--json`（stdout 一份 JSON，授權網址走 stderr）與 `--dir`；退出碼 0 成功、1 本機可修、2 用法錯、3 網路或授權失敗。連線問題請見[連線與錯誤排查](#network-access)。

登入狀態下，`init` 與 `test` 會把結果回報給 Console（框架、產生的檔案、收據編號），App 頁「交給 AI」卡會從「等待工具連線」變成「CLI 已完成 init」。撤銷這個連線：Console →「已連接 App」→「AI-Pay CLI」→ 撤銷授權；之後 CLI 的下一次呼叫會要求重新登入。

---

## Sign in with AI-Pay（OAuth 2.0＋PKCE）

AI-Pay 可以直接當你的登入服務（OIDC）：使用者用 Google 或 email 登入 AI-Pay，你的
App 拿到穩定的使用者識別碼（`sub`）與 email——不必再接其他登入服務、不必碰密碼。
只做登入就只要求身份 scope（`openid email`）——建立的是**「登入連線」**；要用 AI 就加花費 scope，同一次授權建立**「AI 額度授權」**。

`sub` 是**你的 App 專屬的假名**：同一位使用者在你的 App 永遠拿到同一個 `sub`（當帳號
主鍵安全），但在別的 App 是不同值——App 之間無法拿識別碼關聯同一使用者（隱私設計，
比照 Sign in with Apple）。若你另外要求 email，email 本身可跨服務關聯，是使用者明示
授權給你的資料。

SDK 一行造出授權連結（自動產 state 與 PKCE code_verifier）：

```ts
import { createConnectUrl, getUserInfo, handleCallback, refreshAccessToken } from '@coswic/aipay-sdk'

const cfg = {
  hydraPublicUrl: 'https://auth.ai-pay.coswic.ai',
  clientId: '<Console 註冊回的 client_id>',
  redirectUri: 'http://127.0.0.1:4567/auth/aipay/callback',
  // 純登入 App：scopes: ['openid', 'email']（授權畫面就不會出現花費上限）
  // 預設不含 offline_access——伺服器 App 需要使用者離線時續期才顯式加進 scopes
}

// 1. 造授權連結——把 { state, codeVerifier } 存進你的 session（瀏覽器＝sessionStorage）
const { url, state, codeVerifier } = await createConnectUrl(cfg)
// 2. 使用者在 AI-Pay 登入、（有花費 scope 才）設定額度、同意 → 回你的 callback（?code=…&state=…）
// 3. callback：handleCallback 一步完成「驗 error → 驗 state → 換 token」——
//    state 驗證不必自己寫（自己寫容易漏，漏了＝CSRF/code 注入面）；
//    使用者按「拒絕」或 session 遺失都拋出訊息明確的 ConnectError
let tokens = await handleCallback(cfg, callbackUrl, { state, codeVerifier })
// tokens.accessToken（15 分鐘）；tokens.refreshToken（未授 offline_access 時為 null）
// tokens.idToken：OIDC id_token（有授 openid 才有）
// 只在伺服器 App 顯式要求並取得 offline_access 時續期；瀏覽器 App 不要求它
if (tokens.refreshToken) {
  tokens = await refreshAccessToken(cfg, tokens.refreshToken)
  // 將更新後的 tokens 存回伺服器 session；旋轉後的舊 refresh token 作廢
}

// 4. 取使用者身份（伺服器端驗證，不必自己驗 id_token 簽章）
const user = await getUserInfo(cfg, tokens.accessToken)
// user.sub＝你的 App 專屬的穩定假名——資料庫用它當帳號主鍵（email 可變，不要當鍵）
// user.email／user.emailVerified／user.name 依授了哪些 scope 而定
```

不用 SDK 也可以——標準 OAuth 2.0 授權碼流程，授權端點 `GET /oauth2/auth` 的參數：

| 參數 | 值 |
|---|---|
| `client_id` | Console 註冊回的 client_id |
| `response_type` | `code` |
| `scope` | 登入：`openid email`；用 AI 再加 `ai.chat.create wallet.charge`；長期存取加 `offline_access` 換 refresh token |
| `audience` | `aipay-api`（必帶——token 的受眾） |
| `redirect_uri` | 與註冊值精確比對 |
| `state` | 你的 CSRF 隨機值，callback 必驗 |
| `code_challenge` / `code_challenge_method` | PKCE，method 固定 `S256` |

換 token：`POST /oauth2/token`（`grant_type=authorization_code`＋`code_verifier`；續期用 `grant_type=refresh_token`）。access token 效期 15 分鐘——401 時 refresh 一次再重跑是正常事件，不是錯誤。

可要求的 scope（使用者在授權畫面看到的就是這些的人話版）：

| scope | 使用者看到的意思 |
|---|---|
| `openid` | 以 AI-Pay 身份登入（id_token／userinfo 的 `sub`——你的 App 專屬假名） |
| `email` | 提供 email 地址（含 `email_verified`——曾經 Google 驗證才是 true） |
| `profile` | 提供顯示名稱 |
| `ai.chat.create` | 建立 AI 對話請求（文字） |
| `wallet.charge` | 依實際用量扣除使用者的 credits |
| `offline_access` | 使用者不在線時續期授權（拿 refresh token 的長期存取） |

> 授權畫面由 AI-Pay 提供：含花費 scope（AI 額度授權）時，使用者在那裡設定**每月／每日／單次上限**並看到你的 App 收費方式；只要求身份 scope（登入連線）時是輕量的兩步登入授權，完全不出現花費介面。granted scope＝你要求的全部——想降低使用者的心理門檻，就少要 scope。
>
> **回訪與重新同意**：再次連接時，使用者須先完成帳戶地區資料及現行服務條款確認。只有帳號、App 與既有授權仍有效，scope 完全相同，授權文件指紋有效，App 費用未超過同意上限，才可略過重複授權同意。含花費權限的授權還須已有預算，並涵蓋當前平台計價政策及費率上限。scope 變更、服務條款或授權文件更新、未同意的計價政策、費率超出上限或必要紀錄缺漏，都可能要求重新確認；不會略過帳號確認。
>
> 使用者隨時可在 AI-Pay 撤銷授權。撤銷會阻止該授權發起新的 AI 用量並啟動憑證撤銷；已派發的請求仍可能結算。請把 `getUserInfo` 的 401／`ConnectError` 當作「請使用者重新連接」處理。撤銷不會自動終止你的 App 自有登入狀態或刪除已取得的資料；你的 App 須自行處理其登入狀態及資料義務。

---

## Redirect URI（授權結果回傳地址）

授權完成後，AI-Pay 把一次性授權碼（`code`）用瀏覽器轉址送到哪裡——就是你註冊 App 時填的 redirect URI。它是**白名單**：只有登記過的網址收得到授權結果，比對一字不差（協定、主機、port、路徑、query；唯一例外：`http://127.0.0.1` 的 port 可變，見下表）。可登記多個（每行一個），開發與正式環境並存。

各種 App 型態都用同一機制，沒有特例：

| 你的 App | redirect URI | 說明 |
|---|---|---|
| 有伺服器的網站 | `https://yourapp.com/auth/callback` | callback 由你的伺服器處理 |
| 純前端（SPA／靜態站） | `https://yourapp.com/callback` | callback 是一頁 JS，直接在瀏覽器換 token（PKCE 免 secret） |
| 本機開發 | `http://127.0.0.1:<port>/callback` | 允許 http 的只有本機主機名 `127.0.0.1` 與 `localhost`（RFC 8252）。**只有 `127.0.0.1` 的 port 不參與比對**、換 port 不用改註冊；`http://localhost:<port>/…` 也能登記，但整串（含 port）精確比對；IPv6 `[::1]` 目前不能登記 |
| iOS App | `https://yourapp.com/auth/aipay`（Universal Link） | 你網域的一條 https 網址設為 Universal Link，iOS 驗證網域所有權後把它開回你的 App——對 AI-Pay 就是普通 https，零特例 |
| Android App | 同上（App Links） | 機制相同：https 網址由 OS 開回 App |
| CLI／桌面工具 | `http://127.0.0.1:<port>/callback` | 程式臨時起本機 port 收 code（用 `127.0.0.1` 才享 port 可變） |

自訂 scheme（`myapp://callback`）**不接受**：任何 App 都能宣稱同一個 scheme，授權碼可能被同裝置的其他 App 攔截；Universal Links／App Links 由 OS 驗證網域所有權，沒有這個弱點。這是 OAuth 2.1／RFC 8252 的業界標準做法（Apple、Google、GitHub 同）。

**有人登記惡意地址怎麼辦？**不影響你和你的使用者。redirect URI 只決定「那個 App 自己的授權結果」送到哪；授權碼綁定發起流程的 client 與 PKCE verifier，別的 App 偷不走你的 code。惡意開發者能做到的極限是讓使用者授權給「他自己的 App」——使用者在授權畫面看到的正是那個 App 的名稱與收費方式，且花費以使用者自己設定的上限為界、隨時可撤銷。

---

## 無伺服器（純前端）App

有沒有自己的伺服器，流程**完全相同**——差別只在「誰保管 token、誰發請求」。純前端（靜態站／SPA）可以把整個流程放在瀏覽器：PKCE 本來就不需要 client secret，token 端點與 AI 端點都開放 CORS。可用 SDK 的 `getUserInfo` 取得使用者的 `sub`／`email`。

SDK 在瀏覽器直接可用（只用 WebCrypto 與 fetch）——與伺服器版是**同一組函式**：

```js
import { createConnectUrl, handleCallback, AIPayClient } from '@coswic/aipay-sdk'

// 開始授權（例如「連接 AI-Pay」按鈕）：{ state, codeVerifier } 存 sessionStorage
const { url, state, codeVerifier } = await createConnectUrl(cfg)
sessionStorage.setItem('aipay_flow', JSON.stringify({ state, codeVerifier }))
location.href = url

// callback 頁：handleCallback 一步完成「驗 error → 驗 state → 換 token」→ 串流（全在瀏覽器）
const saved = JSON.parse(sessionStorage.getItem('aipay_flow'))
sessionStorage.removeItem('aipay_flow') // 一次性：用畢即清
const tokens = await handleCallback(cfg, location.href, saved)
const client = new AIPayClient({ baseUrl: AIPAY_API_URL, accessToken: tokens.accessToken })
const stream = await client.chat.completions.create({ model, messages, stream: true })
for await (const ev of stream) { /* delta 幀拼內容；收據幀最後才到（欄位見「呼叫模型」節的收據欄位表） */ }
```

不用 SDK 也可以——同一組端點直接 fetch（授權參數見上方參數表）。SSE 幀格式：每幀 `data: <JSON>`；正常序列＝delta 幀 → 收據幀（帶 `usage`＋`aipay`）→ `data: [DONE]`。錯誤會以**幀**的形式出現在 200 串流裡（`{ "error": { code, message, request_id, details } }`），之後串流可能直接結束、也可能仍補一個 `[DONE]`——**財務終態只有收據幀**，`[DONE]` 只是傳輸記號。幀序與終態判準見「呼叫模型」節。

瀏覽器整合指引：

- **不要申請 `offline_access`**：refresh token 放瀏覽器＝長效憑證暴露在 XSS 面。access token 只有 15 分鐘，過期讓使用者重走一次授權——符合前述回訪檢查時才可略過重複同意。
- **token 放記憶體（變數）就好**，避免 localStorage 落地。
- 最壞情況有界：token 只能在使用者設定的上限內花費、使用者隨時可撤銷——這是平台的設計保證。
- CORS 開放的只有 AI 端點（`/v1/chat/completions`、`/v1/models`）與 token 端點；Dashboard API 是 cookie session 面，不開放跨源。

---

## 呼叫模型

可用模型與即時價格：`GET /v1/models`（免 Bearer token、OpenAI 相容、開放 CORS）——下例的 `model` 換成清單中 availability.status 為 available 的模型 id。

```ts
import { AIPayClient } from '@coswic/aipay-sdk'

const client = new AIPayClient({
  baseUrl: 'https://ai-pay.coswic.ai',
  accessToken: tokens.accessToken,
})

const res = await client.chat.completions.create({
  model: 'openai/gpt-oss-20b',   // 範例；實際請查 GET /v1/models
  messages: [{ role: 'user', content: '…' }],
  max_tokens: 200,
})
res.choices[0].message.content
res.aipay.cost_uusd   // 本次實際扣款（整數 µUSD 字串；$1 = 1,000,000 µUSD）
res.aipay.breakdown   // provider／平台／你的服務費 分帳

// 串流（SSE）
const stream = await client.chat.completions.create({ model, messages, stream: true })
for await (const ev of stream) {
  if (ev.type === 'delta') process.stdout.write(ev.content)
  if (ev.type === 'receipt') console.log('入帳', ev.aipay.cost_uusd, 'µUSD')
}
```

裸 HTTP：`POST /v1/chat/completions`，header 帶 `Authorization: Bearer <access token>` 與 `Idempotency-Key`（**必帶**，1–200 字）；body 與 OpenAI 相容，但**只認四個欄位**。可用模型與費率查 `GET /v1/models`（免 Bearer token）——價格在請求當下凍結成快照，之後改價不影響已送出的請求。

### 請求 body 的嚴格規則

- body 只接受 `model` / `messages` / `max_tokens` / `stream`。**任何其他欄位**（`temperature`、`tools`、`response_format`、`user`…）一律 400 `invalid_request`（message 點名欄位）——不會被靜默忽略，因為未生效的參數不該讓你以為生效了。
- `messages`：1–100 則；每則只認 `role` 與 `content`（多任何欄位也是 400）。`role` 限 `system` / `user` / `assistant`；`content` 必須是**非空字串**——不接受 OpenAI 的 content-part 陣列（影像等多模態輸入目前不賣，見「模型目錄與計價」）。全部 `content` 合計上限 100,000 字元。
- `max_tokens`：正整數。超過該模型的 `aipay.hard_max_output_tokens` 時**靜默 clamp 到硬上限**（不報錯；各模型值見 `GET /v1/models`）；不帶＝該模型的 `aipay.default_max_output_tokens`。
- `stream`：布林；對不支援串流的模型帶 `true` 會在動錢之前就回 400 `invalid_request`。
- 選帶 header `Replay-Key`：24–200 字且相異字元 ≥12，格式不合回 400（見「重試紀律與兩把 key」）。

收據幀在**結算完成後**才送出：串流看到 `receipt`＝錢已入帳。串流中斷不會多扣款——斷線後照實結算，帶同一對 key 重送可取回結局（見下節）。

回應的 `choices[0].finish_reason` 為 `stop`、`length` 或 `content_filter`；上游未提供或回傳其他值時使用 `stop`。非串流、串流收據與重放膠囊一致。推理模型可能把輸出額度用於思考，得到空 `content` 與 `length`；只要上游提供可信的完整用量，仍按用量結算。

推理文字不會回傳或存入膠囊。有效推理片段可維持串流的首幀／幀間存活，但不延長總時限；只有 heartbeat 或空幀不會續期。尚無可見文字時若推理後中斷，仍屬結果未知，應帶同一對 key 查結局。未回傳進度或長時間沉默的上游仍可能逾時。執行時限會扣除開押金與派發等待已耗的時間，保留結算餘裕；請依回應的 retryable／outcome 判定重試方式。

部分模型的 `/v1/models` 回應會在 `aipay.input_token_cap` 揭露輸入估算上限。估算方式為所有訊息內容的 UTF-8 byte 數加每則 8；估算值必須**小於**上限，達到上限會在預留額度前回 `400 invalid_request`。這不改變前述 100,000 字元限制。模型也可能有平台設定的推理強度；目前不接受客戶端傳入 `reasoning` 或時限參數。

經核准的模型可使用較長的首幀或非串流等待偏好，硬上限分別為 120 秒與 300 秒。這是最大等待偏好，不是保證執行時間：營運時限、串流總時限與額度預留的結算餘裕可能使有效時限更短。沒有模型設定時沿用既有預設；長時間沒有進度仍可能取消。上游若明確回報非預設服務層級，平台會記錄路由異常；使用者價格仍以本次凍結的模型價格為準。

### SSE 串流的幀序與終態

- 正常序列：delta 幀（`object: 'chat.completion.chunk'`，文字在 `choices[0].delta.content`）→ **收據幀**（同為 chunk，`finish_reason` 為上述完成原因，帶 `usage`＋`aipay`）→ `data: [DONE]`。沒有可見文字時可直接收到收據幀。
- **錯誤幀**：HTTP 已回 200、串流已開之後才發生的錯誤，以一幀 `data: {"error":{"code","message","request_id","details"}}` 表達，語意同錯誤碼表：`provider_unavailable`（`charged: false`；`retryable: true`＝上游未執行、同 key 可重試，`retryable: false`＝上游已執行但失敗、要再試用**新** key）、`provider_timeout`（執行前預算耗盡且釋放成功時 `retryable: true`；同 key 可重試）或 `provider_timeout` / `usage_unknown`（`outcome: 'unknown'`——帶同一對 key 重送查結局）、`internal_error`。SDK 收到錯誤幀會丟出對應例外。
- **`[DONE]` 不是財務終態**：上游途中失敗或逾時後串流會直接結束、**沒有** `[DONE]`；而「內容已送出但無法計價」的 `usage_unknown` 錯誤幀之後**仍會**補一個 `[DONE]`。若已解析到錯誤幀，先依它的 `details.retryable`／`details.outcome` 判斷（例如執行前釋放成功可用同 key 重試）。否則 **收到收據幀＝已結算；沒收到收據幀就結束（不論有沒有 `[DONE]`）＝結果未知**，帶同一對 key 重送查結局（SDK 對應 `StreamTruncatedError`）。
- 串流請求在開 SSE 之前就失敗（401／403／422／429／503…）走一般 JSON 錯誤信封；已結算的串流請求帶同 key 重送也回一次性 JSON（收據，或帶 Replay-Key 時的原文），不重播 SSE。

### 收據欄位（`aipay`；非串流回應與串流收據幀同一份）

| 欄位 | 意思 |
| --- | --- |
| `usage_event_id` / `request_id` | 這筆用量事件的 id（Console「請求」分頁可查；回應 `id` 的 `chatcmpl_` 後綴同值）／本次 HTTP 請求的 request_id。 |
| `hold_id` | 這次請求的額度保留參照——逐筆 hold 的 id；走額度租借快路徑時為租約 ticket id（同形 uuid，語意相同）。 |
| `amount_max_uusd` | 請求當下凍結的押金上限（估價天花板）。實際扣款永遠 ≤ 它。 |
| `total_charged_uusd` / `cost_uusd` | 實際扣款總額（兩者同值；`cost_uusd` 是相容別名）。 |
| `reference_base_uusd` / `aipay_markup_uusd` / `model_charge_uusd` / `app_service_fee_uusd` / `usage_tax_uusd` | 逐 component 的**實收**金額：參考基價（凍結的上游參考價 × 用量）／該模型適用的平台加價／模型費（＝前兩者之和，即目錄計價）／你的服務費／用量稅額（目前恆為 0）。`model_charge_uusd = reference_base_uusd + aipay_markup_uusd`；`total_charged_uusd = model_charge_uusd + app_service_fee_uusd + usage_tax_uusd`，不要重複加計模型費與其組成。 |
| `breakdown.provider_cost_uusd` / `platform_fee_uusd` / `app_margin_uusd` | 相容三欄，分別等於 `reference_base_uusd` / `aipay_markup_uusd` / `app_service_fee_uusd`。注意 **`provider_cost_uusd` 是計價用的參考基價，不是上游當筆的實際成本**（實際成本可能不同）。 |
| `usage` | 計價用量 `{ in_tok, out_tok }`（與頂層 `usage.prompt_tokens` / `completion_tokens` 同源）。 |
| `settlement_state` / `clamped` | 通常是 `'settled'` / `false`。若依實際用量算出的金額超過押金上限 `amount_max_uusd`，只收到上限：`clamped: true`、`settlement_state: 'settled_capped_loss'`，並另附 `computed`（契約上本應收的各 component 與 `total_uusd`）與 `component_shortfall`（各 component 未收到的差額，`total_uusd` 為合計；使用者不會被追扣。參考基價與平台加價的未收差額計入平台吸收損失；未收的 App 服務費由開發者承擔，不產生該部分應付款，不能將全部差額視為平台承擔）。 |
| `replayed` / `note` | `replayed: true`＝這是同 key 重送取回的收據／原文，不是新的執行。`note` 只在特殊情況出現（收據-only 重放、hold 逾時後 0 扣款等）。 |
| `pricing_policy` / `pricing_semantics_version` / `markup_bps` / `catalog_pricing_version` / `price_snapshot_hash` / `reference_fetched_at` / `reference_valid_until` / `reference_prices_per_million_uusd` / `retail_prices_per_million_uusd` | 凍結價目快照的追溯欄位——舊目錄下架後仍可只憑收據重算這筆帳。 |

---

<a id="network-access"></a>

## 連線與錯誤排查

使用本文件提供的 API 與授權端點，照常登入、註冊 App 與完成 OAuth 授權。瀏覽器、CLI 與 App 伺服器各自需要能連到相關 HTTPS 端點。

- **瀏覽器與純前端 App**：使用 PKCE、token 交換及 `getUserInfo`。核對 API 與授權端點，以及 Console 登記的 callback 網址。
- **CLI 與 App 伺服器**：核對設定的端點是否正確、HTTPS 連線是否正常，再使用 OAuth token 呼叫 API。遠端 `aipay login --device` 的 CLI 與批准登入的瀏覽器都需要能連到服務。
- **模型目錄與價目**：`GET /v1/models` 與 `https://ai-pay.coswic.ai/models` 提供即時清單與價目。模型目錄免 Bearer token；AI 請求仍須使用者授權與額度。
- **空白 404 或無法連線**：先核對完整網址、HTTP 方法及所選環境，再檢查 DNS 與 TLS 連線。單憑空白 404 不能確定原因；若回應有錯誤碼，依「錯誤碼」一節排查，記下 HTTP 狀態碼與可取得的 request_id，方便追查。

帳號、OAuth 授權、撤銷、額度與扣款檢查照常生效。

---

## 模型目錄與計價

**目錄現況**：數十個純文字模型（Qwen、DeepSeek、Llama、Mistral、Cohere、GPT-OSS、Kimi、
MiniMax、Hermes、Phi、Gemma、GPT-3.5/4 等系列）。即時清單與價目：人看價目頁
`https://ai-pay.coswic.ai/models`，程式讀 `GET /v1/models`（免 Bearer token）——這兩個是唯一權威來源。不要把名單或價格寫死
在程式裡（也別抄本頁的舉例價——目錄會隨上游上下架與調價變動，寫死的數字一定過期）。

模型可用狀態與價格以 `/v1/models` 為準，目錄以外的模型仍不接受。**目前不提供**影像／音訊輸入，以及需要尚未涵蓋計價項目的模型；新增支援會經審核後更新目錄。

### 一筆請求怎麼計價

以下公式適用於 `openrouter_reference_markup_v1`。目錄也可能包含 legacy 政策；請讀取該模型的政策與零售價，勿一律套用 12%。授權畫面會揭露平台費率上限，新增未同意的政策或超過已同意的平台費率上限時，須重新取得使用者同意。

```text
目錄計價 ＝ ceil( Σ(tokens × 零售單價) ÷ 1,000,000 )      ← 整數 µUSD，一次進位
零售單價 ＝ ceil( 參考單價 × (10000 + markup_bps) ÷ 10000 ) ← v3 定價
使用者實付 ＝ 目錄計價 ＋ 你的每次服務費 ＋ 你的用量加成
```

- **參考單價**（`reference_prices_per_million_uusd`）是審核時凍結的上游路由參考價格，並非每筆請求的實際供應商成本。實際成本可能不同；使用者費用依當次凍結的零售價計算。
- **平台加價依各模型的 `markup_bps`** 計算，例如 `1200` 表示 12%。該加價已含在 v3 零售價內；legacy 政策的 fee／margin 也包含於其零售價，不能再次疊加。
- 進位只發生一次（對所有 token 維度的總和），不是逐維度各進一次。

示例（假設加價 12%，非即時報價；`openai/gpt-oss-20b`，參考價 in $0.03／M、out $0.14／M → 零售 in $0.0336／M、
out $0.1568／M）：一筆 1,000 輸入＋500 輸出 token

```text
參考成本 = (1000 × 30000 + 500 × 140000) ÷ 1e6 = 100 µUSD
目錄計價 = (1000 × 33600 + 500 × 156800) ÷ 1e6 = 112 µUSD   ← 使用者付這個
                                    其中 AI-Pay 加價 = 12 µUSD
```

收據（`res.aipay.breakdown`）會逐 component 拆給你看，對得回這條算式。

### 價格凍結與新鮮度

- 價格在**建立 hold 的當下**凍結成快照；之後改價**不影響**已送出的請求。
- 每個模型的價目有新鮮度期限（`valid_until`）。AI-Pay 會自動向上游續期；**續不到就阻擋該模型的新請求**
  ——新請求回 `503 pricing_stale`，其他模型不受影響。這是刻意的：寧可暫時賣不了，也不用過期
  價目收你的使用者的錢。
- 上游調價**不會**自動生效。價格變動要走人工核准後發新版本，舊版本永久保留（歷史請求對得回
  當時的真實價目）。

### `GET /v1/models` 的計價欄位

| 欄位 | 意思 |
| --- | --- |
| `aipay.pricing_policy` | `openrouter_reference_markup_v1`＝下面這組欄位有效 |
| `aipay.reference_prices_per_million_uusd` | 審核時凍結的上游路由參考單價；實際請求成本可能不同 |
| `aipay.markup_bps` | 加價基點；`1200` ＝ +12% |
| `aipay.retail_prices_per_million_uusd` | 零售單價＝使用者實際被計價的單價 |
| `aipay.availability` | `{status, reason, checked_at}`：本地目錄是否允許新請求，不代表上游服務已實測正常。`available` 的 reason 必為 `null`；`unavailable` 的原因是 `pricing_stale`、`model_price_unavailable` 或 `pricing_unverified`。 |
| `aipay.valid_until` | 此價目的新鮮度期限（過期即阻擋新請求） |
| `aipay.price_locked_at_request` | `true`＝請求當下凍結，改價不追溯 |
| `aipay.default_max_output_tokens` / `hard_max_output_tokens` | 不帶 `max_tokens` 時的預設值／硬上限 |

> `fee_bps`／`margin_bps` 是 legacy 欄位，v3 模型一律為 `0`；**不要**用它們推導價格。

暫時不可用的模型仍保留在清單。只選取 `availability.status === "available"` 的模型；出現在列表不等於可以接單。`checked_at` 是本次依目錄判定的時間，與 chat 共用目錄快取（正常為 30 秒），不是上游探測時間。回應要求重新驗證（`Cache-Control: no-cache`），即使目錄仍在快取中也會重算到期狀態。定價觀測缺失或無效時為 `pricing_unverified`，可能省略無法確認的 v3 定價欄位；新請求在扣款前回 HTTP 503。

---

## 重試紀律與兩把 key

金錢語意的核心：**回應遺失絕不能變成第二次扣款**。SDK 內建以下慣例（裸 HTTP 請照做）：

- **Idempotency-Key**（必帶，1–200 字；SDK 自動產 UUID）：重試一律沿用同一把——伺服器保證同 key 同 payload 不會執行第二次。同 key 換 payload → 409 `idempotency_conflict`。唯一性以**你的 App**為範圍（同一個 client_id 下所有使用者共用同一個命名空間）——用 UUID，別用可能撞號的流水號。確定未執行的失敗（`retryable: true`）會釋放 key；已結算、進行中、結果未知的 key 會一直佔住。
- **Replay-Key**（選帶；**SDK 預設不產**——`new AIPayClient({ autoReplayKey: true })` 開啟，或單筆傳 `replayKey`）：帶著它，回應原文會加密與結算原子入庫——之後在有效期內（15 分鐘）帶同一對 key 重送同一 payload 就能取回**原文**（有效期內可重複取回，不是讀一次即失效）；不帶就只有收據（扣了多少、用量多少；`choices` 為空）。格式 24–200 字、相異字元 ≥12（SDK 的 `generateReplayKey()` 合規）。預設關閉是因為伺服器端每把 key 都要做一次刻意昂貴的金鑰推導——自己會保存回應的 App 不需要它。
- **重試看旗標，不看 HTTP 狀態**：SDK 只自動重試三種情況——`provider_unavailable` / `provider_timeout` / `catalog_unavailable` 且 `details.retryable: true`（＝伺服器擔保確定未執行、0 扣款；估價階段逾時的 504 也是）、429、網路層錯誤——同一對 key 指數退避（預設 2 次）。`pricing_stale` / `model_price_unavailable` / `pricing_unverified` / `service_overloaded` 在線上也標 `retryable: true`，但 SDK **不會**自動重試它們（丟一般 `AIPayApiError`）——要不要等、等多久、要不要換模型由你決定。**`details.outcome: 'unknown'`（如 `usage_unknown`、逾時的 504）絕不自動重試**——結果未知＝錢可能已結算，換新 key 重打就是第二次 AI 行為；帶同一對 key 重送查結局才是正解。
- **422 `budget_exceeded`**：details 附 `max_affordable_uusd`——SDK 預設按它縮 `max_tokens` 用**新** key 重試一次（422 從未執行，payload 變了沿用舊 key 會 409）。

結果未知時的正確處理（SDK）——非串流是 `ProviderError.outcomeUnknown`；串流在收據幀前中斷則丟 `StreamTruncatedError`，兩者都掛著同一對 key：

```ts
import { ProviderError, StreamTruncatedError, requestKeysOf } from '@coswic/aipay-sdk'

try {
  await client.chat.completions.create(params)
} catch (e) {
  const unknown =
    (e instanceof ProviderError && e.outcomeUnknown) || e instanceof StreamTruncatedError
  if (unknown) {
    // { idempotencyKey, replayKey }——SDK create() 丟出的錯誤一定掛著這對 key；
    // replayKey 只在開了 autoReplayKey（或自帶）時才非 null
    const keys = requestKeysOf(e)
    if (!keys) throw e
    // 存下這對 key；稍後帶著它重送同一 payload＝查結局：
    // 已結算 → 取回收據（有 Replay-Key 且在 TTL 內才連原文一起回，否則 choices 為空）；未執行 → 這次才真正執行
    await client.chat.completions.create({
      ...params,
      idempotencyKey: keys.idempotencyKey,
      ...(keys.replayKey ? { replayKey: keys.replayKey } : {}),
    })
  }
}
```

---

## 錯誤碼

錯誤一律是統一信封 `{ "error": { "code", "message", "request_id", "details" } }`——請依 `code` 分支，不要解析 message 文字。

| HTTP | code | 意義／該怎麼辦 |
|---|---|---|
| 400 | `invalid_request` | 請求不合法——修參數，不重試：未知欄位（body 只認 `model` / `messages` / `max_tokens` / `stream`）、`content` 非字串或空、`role` 不合法、`max_tokens` 非正整數、缺 `Idempotency-Key`（`details.reason: 'missing_idempotency_key'`）、`Replay-Key` 格式錯（`'invalid_replay_key'`）、或對不支援串流的模型帶 `stream: true`。message 會點名欄位。 |
| 400 | `model_not_allowed` | 模型不在允許清單（id 打錯或已下架）——查 `GET /v1/models` 換 id，不重試。 |
| 401 | `unauthorized` | token 缺／過期（15 分鐘）——refresh 後重跑一次。 |
| 402 | `insufficient_balance` | 使用者的可用額度低於這次請求要保留的金額（details 附 `shortfall_uusd`）——請求沒有送出、沒有扣款，也不是你的錯誤；告訴使用者 AI 額度不足。正式環境目前不受理儲值，別引導使用者去儲值。 |
| 403 | `authorization_revoked` / `authorization_paused` | 使用者撤銷／暫停了授權——尊重它；撤銷需重新走授權流程。 |
| 403 | `app_pricing_changed` | App 收費超過使用者已同意的費率上限——引導使用者重新授權。 |
| 403 | `consent_terms_changed` | AI-Pay 的授權同意文件已更新、使用者尚未重新同意——引導使用者重新授權（下次登入會自動走同意畫面）。 |
| 403 | `region_required` | 帳戶尚未申報所在地區或尚未同意現行服務條款（開發者 API 與自助面 API 都會回）——CLI 跑 `aipay region set --country <國家碼> --accept-terms`，儀表板會自動開補填門。 |
| 403 | `authorization_circuit_broken` / `forbidden` | 授權熔斷（待平台恢復）／權限不足：App、使用者或錢包非 active，或缺 scope（`details.reason: 'scope_missing'`、`details.scope` 指出缺哪個——呼叫模型必須同時有 `ai.chat.create` 與 `wallet.charge`）。 |
| 409 | `idempotency_conflict` | 同一把 Idempotency-Key 的衝突，依 `details.reason` 分支：`payload_mismatch`＝同 key 換了 payload → 新意圖用新 key；`in_flight`＝同 key 的前一筆還在執行 → 等它結束再帶同 key 重送；`unknown_outcome`＝前一筆結果未知（hold 逾時被回收、待對帳裁定）→ 勿換新 key 重打；`reclaimed_before_dispatch`＝未取得派發權（可能是授權、目錄或請求狀態已變），不代表已確認 hold 過期／釋放 → 帶同 key 查狀態，不保證可重新執行。 |
| 422 | `budget_exceeded` | 超過使用者上限（details 附 `which` / `limit_uusd` / `max_affordable_uusd`）——縮額重試（新 key）或引導使用者調上限。 |
| 429 | `rate_limit_exceeded` | 限流——退避後重試（同 key）。 |
| 500 / 503 | `internal_error` | 平台內部錯誤（如該模型的 provider 未設定、結算失敗）；503 且 `details.reason: invalid_timing_policy` 表示設定組合無效，尚未派發或建 hold，須由營運修正。不要換新 key 重打——帶同 key 重送永遠安全（冪等擋住），也能查到結局。 |
| 500 / 502 / 503 | `usage_unknown`（`details.outcome: 'unknown'`） | **結果未知——勿盲目重試。** 500＝上游已成功回應但結算無法落地（錢可能已結算、或押在 hold 到 TTL）；502＝上游未執行但釋放無法落地（資金押在 hold 到 TTL、0 扣款）。503＝尚未派發，但押金釋放未獲確認（可能已到期）；不能當作已釋放重新執行。帶同一對 key 稍後重送查結局。 |
| 502 | `provider_unavailable`（`details.retryable: true`） | 上游在執行前就失敗（連線層／上游拒收）——確定未執行、0 扣款；可退避重試（同 key）。 |
| 502 | `provider_unavailable`（`details.charged: false, retryable: false`） | 上游**已執行但回報失敗**（含串流途中失敗）——0 扣款，但這把 key 已終結（同 key 不會再執行上游）；要再試必須用**新** key。 |
| 502 / 504 | `provider_unavailable` / `provider_timeout`（`details.outcome: 'unknown'`） | **結果未知——勿盲目重試。** 上游逾時、或回傳無法計價——可能已執行、已產生成本；資金押在 hold 到 TTL，同 key 維持佔用。帶同一對 key 重送查結局（期間會得到 409 `in_flight` / `unknown_outcome`）。 |
| 503 | `catalog_unavailable` | 模型目錄讀不到——`retryable: true`、0 扣款；退避重試（同 key）。 |
| 503 | `ledger_unavailable`（`details.reason: hold_budget_unavailable`） | 派發前無法確認保留額度的有效時限；沒有呼叫上游，且零費釋放已確認。`retryable: true`，稍後用同 key 重試。釋放未確認則回 `usage_unknown`，請帶同一對 key 查結果。串流已開始時以相同 error frame 回覆。 |
| 503 | `pricing_stale` / `model_price_unavailable` / `pricing_unverified` | 該模型的價目過期／被計價熔斷器暫停／定價觀測未確認（`details.model`）——`retryable: true`、0 扣款，其他模型不受影響。SDK **不會**自動重試這些錯誤：自行退避或改用其他模型。 |
| 503 | `service_overloaded` | 伺服器滿載、這筆尚未開始處理（帶 `Retry-After: 2`）——`retryable: true`、0 扣款；同 key 退避重試（SDK 不自動重試）。 |
| 503 | `service_unavailable`（`details.reason: server_draining`） | 服務正在重新啟動，請求尚未送往上游；若已保留額度，零費釋放已確認。`retryable: true`，稍後用同 key 重試。釋放未確認則回 `usage_unknown`，不能當作已釋放。 |
| 503 | `maintenance_mode` | 系統維護中（帶 `Retry-After: 120`）——稍後重試；餘額與授權不受影響。 |
| 503 / 504 | `provider_timeout`（`details.retryable: true`） | 504＝估價階段逾時，尚未建 hold；503 且 `details.reason: hold_execution_budget_exhausted`＝派發前剩餘預算已耗盡，且零費釋放已確認。兩者都未呼叫上游，可用同 key 重試。 |

> 通則：**重試看 `details.retryable` 與 `details.outcome`，不看 HTTP 狀態**——同一個 502／503／504 會對應「確定未執行」與「結果未知」兩種完全不同的語意。帶同一把 Idempotency-Key 重送永遠安全：伺服器保證同 key 不會執行第二次。

---

## App 定價（服務費）

> ⚠️ **透過 AI-Pay 收取 App 服務費目前未開放**：因為提領尚未開放，App 服務費一併停用——設定端點回
> `503 app_fees_disabled`（設回 0 仍可）、含花費權限的授權畫面會標示本次授權不加收平台內的 App 服務費、
> 實際請求也**不會**向使用者收取任何 App 費用。既有設定值可能保留，但開放須另行公告、完成適用的開發者付款約定及使用者費用同意。
> 以下說明支援的費用結構供整合準備；正式開放前 App 服務費仍停用。

你可以在 Console 的「App 收費」分頁為 App 定價，兩者可並用，歸屬以實際收取的 App 服務費為準，並受適用契約的退款、回沖與提領條件約束：

- **每次請求服務費**（固定 USD，最高 $20——平台上限）；
- **AI 用量費加成**（目錄計價的百分比）。

使用者實付＝目錄計價＋已開放且經同意的每次服務費與加成。目錄計價依該模型的政策與凍結價格（見「模型目錄與計價」）；此處所說的開發者服務費是 AI-Pay 平台內的收費，與你的 App 在平台外另行訂閱或收費不同。授權畫面會**醒目揭露**App 收費，同意當下的收費上限會被記錄：

- 費率**超過使用者已同意的上限**時，該授權的請求回傳 403 `app_pricing_changed`，直到重新授權——請在 App 內處理此碼並引導重新同意；
- **調低**即時生效（收現價）。

另可設定**建議每月上限**——授權畫面的預設值（每日／單次按 10:1:100 推導），最高 **$200**（平台對建議值的天花板）；金額仍由使用者裁決、可自行調高。定價與建議值的每次變更都留有稽核紀錄。

---

## 用量對帳與收款

Console 的「請求」分頁逐筆列出你的 App 的請求：狀態（已結算／失敗／**待確認**——待確認勿重試）、tokens、收費與服務費歸屬；每列可展開 `request_id` / `Idempotency-Key` 等識別碼，與你的 log 對單。此分頁不直接顯示使用者 email 或完整 prompt／回應本文；請求、授權及假名識別碼可與你的 App 自有紀錄連結，不能視為匿名資料。

App 收費與提領開放後，服務費依實際結算與歸屬紀錄核對，並依正式契約處理保留期、退款／拒付回沖及付款。目前不提供提領或自動回沖，也未承諾開放日期。

> ⚠️ **提領目前未開放**：收款帳戶設定與提領申請目前不受理（`503 payouts_disabled`）。**App 收費關閉期間，新請求不產生 App 服務費**。既有紀錄會保留；其性質與未來能否提領須依實際交易、正式契約及適用法律確認，不能將測試紀錄視為真實應付款。

---

## 參考資料

- **產品介紹與開放狀態**：[`/about`](https://ai-pay.coswic.ai/about)——AI-Pay 是什麼、誰付費、目前開放哪些功能。
- **API 契約**：[`/openapi.yaml`](https://ai-pay.coswic.ai/openapi.yaml)（OpenAPI 3.1，對外開發者契約——生成自程式碼，含你會呼叫的端點與全部錯誤碼）。這份可直接餵給 coding agent、匯入 Postman／Bruno，或拿去產生 client。
- **SDK 與 CLI**：[`@coswic/aipay-sdk`](https://www.npmjs.com/package/@coswic/aipay-sdk)（TypeScript SDK，README 含完整用法與內建慣例）、[`@coswic/aipay`](https://www.npmjs.com/package/@coswic/aipay)（CLI，指令一覽見「用 CLI 或 coding agent 接上」節）——兩者皆為公開 npm 套件。
- **法律文件**：[服務條款](https://ai-pay.coswic.ai/terms)、[隱私政策](https://ai-pay.coswic.ai/privacy)——你的 App 引導使用者授權前，請自行確認你的使用方式與兩者相容。
- **完整範例**：QuickGist 小摘（「Sign in with AI-Pay」→ 串流呼叫 → 錯誤逐類處理 → 結果未知查詢的全流程參考實作）目前隨 AI-Pay 原始庫提供（`showcase/quickgist/`，共用骨架 `showcase/kit/`），尚未獨立公開下載；要在自己的專案取得同款接線範本，請用 CLI 的 `aipay init`（依框架產生 start／callback 路由與 `aipay-agent.md`）。
