Build with AI-Pay

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。其他環境的功能與付款方式依該環境的畫面為準;Stripe/PayPal 測試模式不會實際扣款,測試額度不代表真實付款或開發者收入。以下範例的 API base 是 https://ai-pay.coswic.ai、授權端點是 https://auth.ai-pay.coswic.ai——這兩個值由你取得本文件的那個環境代入,直接複製就能用。

快速開始

開始前,請先到 Dashboard 用自己的 Google 帳戶登入,依畫面完成所在地區資料與現行服務條款確認,再進入 Console。第一次登入建立帳號需要有效邀請;安裝 SDK 或 CLI 不會略過這些前置。若 API 回 region_required,請回 Dashboard 完成設定,或使用下方的 aipay region 指令。

  1. 在 Console(Apps)右上角「建立 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 網路或授權失敗。連線問題請見「連線與錯誤排查」。

登入狀態下,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):

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_idConsole 註冊回的 client_id
response_typecode
scope登入:openid email;用 AI 再加 ai.chat.create wallet.charge;長期存取加 offline_access 換 refresh token
audienceaipay-api(必帶——token 的受眾)
redirect_uri與註冊值精確比對
state你的 CSRF 隨機值,callback 必驗
code_challenge/code_challenge_methodPKCE,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依實際用量扣除使用者的 AI 額度
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 型態都用同一機制,沒有特例:

你的 Appredirect URI說明
有伺服器的網站https://yourapp.com/auth/callbackcallback 由你的伺服器處理
純前端(SPA/靜態站)https://yourapp.com/callbackcallback 是一頁 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 Apphttps://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)——與伺服器版是同一組函式:

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。

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 重送可取回結局(見下一節)。

SSE 串流的幀序與終態

  • 正常序列:delta 幀(object: 'chat.completion.chunk',文字在 choices[0].delta.content)→ 收據幀(同為 chunk,finish_reason: 'stop',帶 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/usage_unknown(outcome: 'unknown'——帶同一對 key 重送查結局)、internal_error。SDK 收到錯誤幀會丟出對應例外。
  • [DONE] 不是財務終態:上游途中失敗或逾時後串流會直接結束、沒有 [DONE];而「內容已送出但無法計價」的 usage_unknown 錯誤幀之後仍會補一個 [DONE]。判準只有一個:收到收據幀=已結算;沒收到收據幀就結束(不論有沒有 [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/notereplayed: 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凍結價目快照的追溯欄位——舊目錄下架後仍可只憑收據重算這筆帳。

連線與錯誤排查

使用本文件提供的 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 與 價目頁 提供即時清單與價目。模型目錄免 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 等系列)。即時清單與價目:人看價目頁,程式讀 GET /v1/models(免 Bearer token)——這兩個是唯一權威來源。不要把名單或價格寫死在程式裡(也別抄本頁的舉例價——目錄會隨上游上下架與調價變動,寫死的數字一定過期)。

目前不提供多模態模型(影像/音訊輸入)與有 prompt caching、reasoning token、web search 等額外計價維度的模型。原因是這些維度 AI-Pay 還沒有建模,無法保證每一筆都能完整計價——與其用估的收你錢,不如先不賣。建模完成後會擴充目錄。

一筆請求怎麼計價

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

目錄計價 = 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

參考成本 = (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_policyopenrouter_reference_markup_v1=下面這組欄位有效
aipay.reference_prices_per_million_uusd審核時凍結的上游路由參考單價;實際請求成本可能不同
aipay.markup_bps加價基點;1200 = +12%
aipay.retail_prices_per_million_uusd零售單價=使用者實際被計價的單價
aipay.availability本地目錄是否允許新請求,不代表上游已實測正常。available 的 reason 必為 null;unavailable 的原因是 pricing_stale、model_price_unavailable 或 pricing_unverified。
aipay.valid_until此價目的新鮮度期限(過期即阻擋新請求)
aipay.price_locked_at_requesttrue=請求當下凍結,改價不追溯
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:

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 文字。

HTTPcode意義/該怎麼辦
400invalid_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 會點名欄位。
400model_not_allowed模型不在允許清單(id 打錯或已下架)——查 GET /v1/models 換 id,不重試。
401unauthorizedtoken 缺/過期(15 分鐘)——refresh 後重跑一次。
402insufficient_balance使用者的可用額度低於這次請求要保留的金額(details 附 shortfall_uusd)——請求沒有送出、沒有扣款,也不是你的錯誤;告訴使用者 AI 額度不足。正式環境目前不受理儲值,別引導使用者去儲值。
403authorization_revoked/authorization_paused使用者撤銷/暫停了授權——尊重它;撤銷需重新走授權流程。
403app_pricing_changedApp 收費超過使用者已同意的費率上限——引導使用者重新授權。
403consent_terms_changedAI-Pay 的授權同意文件已更新、使用者尚未重新同意——引導使用者重新授權(下次登入會自動走同意畫面)。
403region_required帳戶尚未申報所在地區或尚未同意現行服務條款(開發者 API 與自助面 API 都會回)——CLI 跑 aipay region set --country <國家碼> --accept-terms,儀表板會自動開補填門。
403authorization_circuit_broken/forbidden授權熔斷(待平台恢復)/權限不足:App、使用者或錢包非 active,或缺 scope(details.reason: 'scope_missing'、details.scope 指出缺哪個——呼叫模型必須同時有 ai.chat.create 與 wallet.charge)。
409idempotency_conflict同一把 Idempotency-Key 的衝突,依 details.reason 分支:payload_mismatch=同 key 換了 payload → 新意圖用新 key;in_flight=同 key 的前一筆還在執行 → 等它結束再帶同 key 重送;unknown_outcome=前一筆結果未知(hold 逾時被回收、待對帳裁定)→ 勿換新 key 重打;reclaimed_before_dispatch=派發前 hold 已過期被回收、尚未呼叫上游 → 帶同 key 重送即可。
422budget_exceeded超過使用者上限(details 附 which/limit_uusd/max_affordable_uusd)——縮額重試(新 key)或引導使用者調上限。
429rate_limit_exceeded限流——退避後重試(同 key)。
500internal_error平台內部錯誤(如該模型的 provider 未設定、結算失敗)。不要換新 key 重打——帶同 key 重送永遠安全(冪等擋住),也能查到結局。
500/502usage_unknown(details.outcome: 'unknown')結果未知——勿盲目重試。500=上游已成功回應但結算無法落地(錢可能已結算、或押在 hold 到 TTL);502=上游未執行但釋放無法落地(資金押在 hold 到 TTL、0 扣款)。帶同一對 key 稍後重送查結局。
502provider_unavailable(details.retryable: true)上游在執行前就失敗(連線層/上游拒收)——確定未執行、0 扣款;可退避重試(同 key)。
502provider_unavailable(details.charged: false, retryable: false)上游已執行但回報失敗(含串流途中失敗)——0 扣款,但這把 key 已終結(同 key 不會再執行上游);要再試必須用新 key。
502/504provider_unavailable/provider_timeout(details.outcome: 'unknown')結果未知——勿盲目重試。上游逾時、或回傳無法計價——可能已執行、已產生成本;資金押在 hold 到 TTL,同 key 維持佔用。帶同一對 key 重送查結局(期間會得到 409 in_flight/unknown_outcome)。
503catalog_unavailable模型目錄讀不到——retryable: true、0 扣款;退避重試(同 key)。
503pricing_stale/model_price_unavailable/pricing_unverified該模型的價目過期/被計價熔斷器暫停/定價觀測未確認(details.model)——retryable: true、0 扣款,其他模型不受影響。SDK 不會自動重試這些錯誤:自行退避或改用其他模型。
503service_overloaded伺服器滿載、這筆尚未開始處理(帶 Retry-After: 2)——retryable: true、0 扣款;同 key 退避重試(SDK 不自動重試)。
503maintenance_mode系統維護中(帶 Retry-After: 120)——稍後重試;餘額與授權不受影響。
504provider_timeout(details.retryable: true)估價階段逾時——尚未建 hold、未呼叫上游;可重試(同 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——AI-Pay 是什麼、誰付費、目前開放哪些功能。
  • API 契約:/openapi.yaml(OpenAPI 3.1,對外開發者契約——生成自程式碼,含你會呼叫的端點與全部錯誤碼)。這份可直接餵給 coding agent、匯入 Postman/Bruno,或拿去產生 client。
  • SDK 與 CLI:@coswic/aipay-sdk(TypeScript SDK,README 含完整用法與內建慣例)、@coswic/aipay(CLI,指令一覽見「用 CLI 或 coding agent 接上」節)——兩者皆為公開 npm 套件。
  • 法律文件:服務條款、隱私政策——你的 App 引導使用者授權前,請自行確認你的使用方式與兩者相容。
  • 完整範例:QuickGist 小摘(「Sign in with AI-Pay」→ 串流呼叫 → 錯誤逐類處理 → 結果未知查詢的全流程參考實作)目前隨 AI-Pay 原始庫提供(showcase/quickgist/,共用骨架 showcase/kit/),尚未獨立公開下載;要在自己的專案取得同款接線範本,請用 CLI 的 aipay init(依框架產生 start/callback 路由與 aipay-agent.md,見「用 CLI 或 coding agent 接上」節)。
金額一律以 USD 顯示(整數 µUSD 字串,$1 = 1,000,000 µUSD)。

單次儲值

增加 AI 額度

選擇金額

完整卡號與安全碼在 Stripe 頁面填寫;AI-Pay 會接收交易及部分付款資料。稅費與最終總額以 Stripe 付款頁為準。

確認操作