從你自己的系統開發票
POS、電商後台或自家系統,用一把金鑰就能開立、作廢與查詢發票。基底網址就是你自己的伺服器,沒有共用的雲端端點——每個營業人都是一台自己的機器,資料不經過我們。
1. 先拿一把金鑰
金鑰只能在伺服器本機建立,從外部打管理端點一律回 404。SSH 進你的機器:
curl -s -X POST http://127.0.0.1:3010/admin/api-keys \
-H "x-admin-token: $ADMIN_TOKEN" -H 'content-type: application/json' \
-d '{"name":"門市 POS","scopes":["invoice:write","invoice:read"]}'回應裡的明碼金鑰(fap_live_…)只出現這一次,伺服器只存雜湊。掉了就重建一把。建議每個系統一把、權限給最小集合,必要時用 ipAllowlist 限制來源 IP。
| 權限 | 能做的事 |
|---|---|
invoice:write | 開立、作廢、折讓 |
invoice:read | 查詢單張、列表、統計 |
track:read | 看字軌區段 |
track:write | 新增字軌區段 |
2. 確認手上的金鑰
GET /v1/me 回這把金鑰的權限、速率上限與到期日,也會告訴你伺服器有沒有設定 QRCode 加密金鑰。不需要任何權限,金鑰有效就回。
curl -s https://invoice.your-company.tw/v1/me \
-H "Authorization: Bearer $KEY"3. 開一張發票
POST /v1/invoices,需要 invoice:write。單價含稅,伺服器會反推銷售額與稅額,並自動配一個沒用過的字軌號碼。
curl -s -X POST https://invoice.your-company.tw/v1/invoices \
-H "Authorization: Bearer $KEY" -H 'content-type: application/json' \
-H "Idempotency-Key: pos-20260909-000187" \
-d '{"items":[{"description":"美式咖啡","quantity":2,"unitPrice":80}],"carrier":{"type":"3J0002","id":"/ABC+123"}}'| 欄位 | 必填 | 說明 |
|---|---|---|
buyer.identifier | 否 | 買方統編 8 碼;一般消費者填 0000000000(預設) |
buyer.name | B2B 必填 | 買方名稱,最長 60 字 |
items[].description | 是 | 品名,1 到 500 字 |
items[].quantity | 是 | 數量,大於 0 |
items[].unitPrice | 是 | 含稅單價 |
taxType | 否 | 1 應稅(預設)、2 零稅率、3 免稅 |
carrier | 否 | 手機條碼 3J0002、自然人憑證 CQ0001 |
donate | 否 | 捐贈碼 3 到 7 位數字 |
print | 否 | 是否列印證明聯;沒給就依有沒有載具判斷 |
回應 201,帶完整發票內容,包括證明聯要印的東西:qrLeft 與 qrRight 直接當兩個 QR 的內容,一維條碼是「民國年期別 5 碼+發票號碼 10 碼+隨機碼 4 碼」,用 Code 39。
| status | 意思 |
|---|---|
issued | 已配號存檔,XML 還沒寫進 Turnkey 目錄(會重試) |
queued | XML 已就位,等 Turnkey 下一班上傳 |
uploaded | 大平台已收下 |
failed | 大平台退件,看 resultCode/resultMessage |
voided | 已作廢 |
4. 重送不會重複開票
帶 Idempotency-Key(8 到 128 個英數字)。同一把金鑰用同一個 key 再送一次,回的是原本那張並多一個 replayed: true,不會再燒一個號碼;key 相同但內容不同會回 422。
5. 作廢
POST /v1/invoices/{number}/void,body 帶 reason(1 到 20 字)。還沒被 Turnkey 取走就直接撤回檔案;已上傳的會送 F0501。只能作廢本期發票。
6. 折讓
發生銷貨退回、掉換貨物或折讓時開立折讓證明單(MIG 4.1 的 G0401)。跨期也可以——作廢只能作廢本期,跨期更正就是走這裡。依統一發票使用辦法第 20 條之 1,折讓證明單只能由賣方開立。
curl -s -X POST https://invoice.your-company.tw/v1/allowances \
-H "Authorization: Bearer $KEY" -H 'content-type: application/json' \
-d '{"items":[{"invoiceNumber":"AB12345711","seq":1,"totalIncludingTax":420}]}'一張折讓單可以折多張發票,但買方必須是同一個。金額給 amount(不含稅)或 totalIncludingTax(含稅,伺服器反推)都行;帶原明細的 seq就沿用那一列的品名與單價。折讓單號由伺服器配,16 碼、跨年不重複。
GET /v1/allowances 列表(q 可以用原發票號碼搜尋)、GET /v1/allowances/{number} 查單張、POST /v1/allowances/{number}/void 作廢(送 G0501)。 累計折讓金額不能超過原發票的未稅金額,超過會回 409 exceeds_invoice。
7. 查詢與字軌
GET /v1/invoices/{number} 查單張;GET /v1/invoices 列表可帶 status、period、q、limit(1-200)與 cursor,回應的 nextCursor 帶回去就是下一頁。GET /v1/stats/overview 是本期統計。
GET /v1/tracks 看字軌,POST /v1/tracks 新增區段。字軌用完會開不出發票(409 track_exhausted),記得先配下一段。
8. 期末與申報
POST /v1/periods/{period}/blank-tracks(track:write)產這一期的空白未使用字軌檔(E0402), 帶 ?dryRun=1 只看要報哪些號碼不產檔。**次期 10 日前要上傳**,同一期重傳以後傳的為準。
POST /v1/tracks/import 貼平台配號檔(E0501)的 XML 自動建字軌區段;POST /v1/downloads/sync 掃 Turnkey 下載目錄,把配號、進項發票(E0502)、 進項折讓(E0503)與中獎清冊(E0504)吃進資料庫,處理過的檔案會加上 .done。
GET /v1/incoming/invoices、GET /v1/incoming/allowances、GET /v1/prizes查進項與中獎;PATCH /v1/incoming/invoices/{number} 可以把交際應酬這類標成不可扣抵。
GET /v1/tax/vat/{period} 是 401 試算:銷項來自你開的發票與折讓,進項來自平台下載的檔, 算出本期應納稅額或累積留抵。上期留抵與收據、海關代徵這類非電子發票的進項用 PATCH /v1/tax/vat/{period} 補。
媒體申報檔(401 的 TET_U 與進銷項 TXT)還沒做:112 欄的逐欄定義沒有公開對照表, 驗證之前不會產一個可能填錯格子的申報檔。Fapio 也不代傳、不代報,上傳要自己用工商憑證登入財政部。
9. 錯誤碼
錯誤一律是 { error, message },並帶 x-request-id 供追查。
| HTTP | error | 意思 |
|---|---|---|
| 400 | invalid_invoice | 內容不符 MIG 規格 |
| 401 | unauthorized | 沒帶金鑰、格式不對、金鑰無效或已撤銷 |
| 403 | forbidden | 權限不足或來源 IP 不在允許清單 |
| 409 | track_exhausted | 本期沒有可用字軌 |
| 409 | exceeds_invoice | 累計折讓超過原發票未稅金額 |
| 422 | idempotency_conflict | 同一個 Idempotency-Key 但內容不同 |
| 429 | rate_limited | 超過速率上限,看 retry-after |
| 429 | too_many_failures | 同一 IP 驗證失敗太多次,鎖 15 分鐘 |
429 等 retry-after 秒再試;5xx 用指數退避,帶著同一個 Idempotency-Key重試不會重複開票。
10. 限制
| 項目 | 值 |
|---|---|
| 每把金鑰每分鐘請求數 | 建立時設定,預設 120 |
| 每個來源 IP 每分鐘請求數 | 300 |
| 請求 body 上限 | 256 KB |
| 單張發票品項數 | 999 |
| 單張發票金額上限 | 20 億 |
目前沒有 webhook。上傳結果由伺服器輪詢 Turnkey 後寫回 status,要知道結果就查那張發票,或列表過濾 status=failed。