IoT Smart Payment Platform
AI Agent & Third-party Integration
/api 回覆 HTTP 403、resultCode: "4030"。下列規格與範例供整合準備;不表示已授權或啟用對外存取。GOAL-IOT Open API 提供統一的 JSON 介面,重新開放後可供 AI Agent、第三方系統、自動化工具:
https://iot.goalsoft.com.tw/goaliot
POST https://iot.goalsoft.com.tw/goaliot/api
將 Base URL 與路徑 /api 各接一次;不要使用 /api/api。
| 資源 | 路徑 | 用途 |
|---|---|---|
| API Endpoint | /api | 所有指令的統一入口 |
| 本文件 | /api/docs | 人類閱讀用的 API 文件 |
| OpenAPI Spec | /api/openapi.json | 機器可讀規格(LangChain / GPTs Actions) |
| Well-Known | /.well-known/openapi.json | 標準發現路徑(IETF RFC 8615) |
所有指令都透過同一個 endpoint,以 command 欄位區分功能:
{
"sn": "device_serial_number", // 設備序號(部分指令可省略)
"command": "status", // 指令名稱(必填)
"pin": "1", // 指令參數(依指令而定)
"values": "100" // 指令參數(依指令而定)
}
{
"resultCode": "0000", // "0000" = 成功或付款已受理,其他 = 錯誤碼
"resultMessage": "OK", // 結果訊息
"resultUUID": "", // 穩定的 20 字元付款 order ID(僅 payment 回傳)
"data": { ... } // 指令回傳資料
}
所有 API 請求需附帶 Bearer Token:
Authorization: Bearer YOUR_API_TOKEN
| 指令類型 | 認證方式 | 說明 |
|---|---|---|
| 唯讀設備指令(需 SN) | Token + SN 交叉驗證 | 主擁有者、uid_admin 或 owner 分享者 |
| payment(需 SN) | 主擁有者 Token + SN | 分享者的唯讀授權不能用於付款 |
| 用戶指令(不需 SN) | Token 反查用戶 | 透過 Token 查找用戶,回傳該用戶所有資料 |
下列防護在經授權重新開放後適用。所有回覆使用 Cache-Control: no-store;JSON 必須是單一物件,只能包含已記載欄位。僅接受 POST;OPTIONS 用於 CORS。
| 計數範圍 | 上限 |
|---|---|
| 來源 IP(可信反向代理解析後) | 每 60 秒 120 次 |
| 已驗證 UID,所有指令合計 | 每 60 秒 60 次 |
| payment,同一已驗證 UID | 每 60 秒 10 次 |
每個計數視窗從第一筆請求起固定 60 秒。相同 Idempotency-Key 的付款重試也計入請求額度;被限流的請求不會建立付款記錄或派送設備操作。announce 另受每 UID 小時限額約束(預設 10 次)。
HTTP 429 / 4029:60 秒視窗限額會附 Retry-After 剩餘秒數(至少 1 秒);公告改寫的小時限額目前不附此 header,請勿連續自動重試。重試原付款必須保留原 key 與原內容。HTTP 503 / 5008 表示防護服務不可用,請求在付款副作用前被拒絕;不附 Retry-After。HTTP 503 / 5007 表示無法查詢設備狀態,不能當成設備離線。
任何付款 timeout、連線中斷或 warning 都不能作為換新 key 重送的理由;先保留原 key 查核結果或請人工確認。
最輕量的健康檢查。AI Agent 啟動時先 ping 一下,確認 API 活著、Token 有效。不需要 SN。
{ "command": "ping" }
{
"resultCode": "0000",
"resultMessage": "pong",
"data": {
"timestamp": 1711267200,
"version": "1.0"
}
}
| 欄位 | 說明 |
|---|---|
timestamp | 伺服器 Unix 時間戳 |
version | API 版本號 |
取得 API 支援的所有指令清單,包含每個指令的參數需求。適合 AI Agent 啟動時先呼叫此指令了解能力範圍。
{ "command": "commands" }
{
"resultCode": "0000",
"resultMessage": "OK",
"data": {
"version": "1.0",
"commands": [
{
"command": "ping",
"description": "Health check - verify API connectivity",
"requires_sn": false,
"requires_pin": false,
"requires_values": false
},
{
"command": "commands",
"description": "List all available API commands",
"requires_sn": false,
"requires_pin": false,
"requires_values": false
},
{
"command": "payment",
"description": "Trigger device payment remotely",
"requires_sn": true,
"requires_pin": true,
"requires_values": true
},
{
"command": "status",
"description": "Query device real-time status (running state, RSSI, online)",
"requires_sn": true,
"requires_pin": false,
"requires_values": false
},
{
"command": "revenue",
"description": "Get device last 12 hours hourly revenue data",
"requires_sn": true,
"requires_pin": false,
"requires_values": false
},
{
"command": "devices",
"description": "List all devices owned by the authenticated user",
"requires_sn": false,
"requires_pin": false,
"requires_values": false
},
{
"command": "device_info",
"description": "Get detailed device information",
"requires_sn": true,
"requires_pin": false,
"requires_values": false
},
{
"command": "records",
"description": "Query device event records (paginated)",
"requires_sn": true,
"requires_pin": false,
"requires_values": false
},
{
"command": "announce",
"description": "AI-powered announcement rewriting with emoji",
"requires_sn": false,
"requires_pin": false,
"requires_values": true
}
]
}
}
取得該 Token 擁有者的所有設備清單,包含即時上線狀態。不需要 SN。
{ "command": "devices" }
{
"resultCode": "0000",
"resultMessage": "OK",
"data": {
"count": 1,
"devices": [
{
"sn": "ABC12345",
"name": "1F Washer",
"pin1": "Wash",
"pin2": "Dry",
"online": true,
"enabled": 1
}
]
}
}
取得指定設備的即時運行狀態,包含 RSSI 訊號強度、運行中服務、數值等。
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
sn | string | required | 設備序號 |
command | string | required | "status" |
{ "sn": "89123456", "command": "status" }
{
"resultCode": "0000",
"resultMessage": "OK",
"data": {
"sn": "ABC12345",
"running": {},
"value": {},
"rssi": {
"rssi": "-45",
"time": "1711267200",
"ping": "8"
},
"lastRssi": -45,
"lastRssiUnix": 1711267200,
"online": true
}
}
| 欄位 | 說明 |
|---|---|
running | 各服務的運行狀態 map |
value | 各服務的目前數值(如剩餘分鐘) |
rssi | RSSI 訊號資料 |
lastRssi | 本次讀到的心跳 RSSI;無心跳時為 0 |
lastRssiUnix | 心跳的 Unix 秒數;無時間資料時為 0(未知),不是快取重載時間 |
online | 本次共享 Redis 心跳中是否有 rssi(TTL 300 秒);查詢失敗回 503,不偽裝為離線 |
vending | 販賣機庫存狀態(如適用) |
取得設備的基本資料(名稱、韌體版本、服務名稱等)。enabled 是整數,例如 0/1;uid 只有主擁有者本人可見,分享者回覆會省略此欄位。
{ "sn": "89123456", "command": "device_info" }
{
"resultCode": "0000",
"resultMessage": "OK",
"data": {
"sn": "ABC12345",
"name": "1F Washer",
"pin1": "Wash",
"pin2": "Dry",
"uid": "user123",
"firmware": "9.8.665",
"enabled": 1,
"image": "https://example.com/device.png",
"weather": "Sunny 28C"
}
}
取得指定設備最近 12 小時的逐時營收數據,適合 AI Agent 做營收分析與預測。
{ "sn": "89123456", "command": "revenue" }
{
"resultCode": "0000",
"resultMessage": "OK",
"data": {
"revenues": [
0,
100,
50,
200,
0,
0,
150,
300,
0,
50,
100,
0
],
"start_unix_hour": 475343
}
}
| 欄位 | 說明 |
|---|---|
revenues | 12 個整數陣列,索引 0 = 最早,索引 11 = 當前小時 |
start_unix_hour | 起始 Unix Hour(timestamp / 3600) |
new Date(start_unix_hour * 3600 * 1000)取得設備的事件記錄(WiFi 變更、重啟、韌體更新等),支援分頁。
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
sn | string | required | 設備序號 |
command | string | required | "records" |
pin | string | optional | 頁碼(從 0 開始) |
{ "sn": "89123456", "command": "records", "pin": "0" }
{
"resultCode": "0000",
"resultMessage": "OK",
"data": {
"total": 32,
"page": 1,
"pageSize": 30,
"records": [
"2026-03-24 10:00:00 Payment $50 PIN1",
"2026-03-24 09:30:00 Payment $100 PIN2"
]
}
}
| Header | 必填 | 說明 |
|---|---|---|
Idempotency-Key | required | 8–128 個 visible ASCII 字元。重試同一筆付款必須重用同一 key;新付款必須使用新 key。同一 key 搭配不同 sn / pin / values 會回 409。 |
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
sn | string | required | 設備序號 |
command | string | required | "payment" |
pin | string | required | 服務編號:"1" 或 "2" |
values | string | required | 金額(如 "40") |
{ "sn": "89123456", "command": "payment", "pin": "1", "values": "40" }
{
"resultCode": "0000",
"resultMessage": "12345",
"resultUUID": "0a1b2c3d4e5f6g7h8i9j",
"data": {
"iot_status": "ok"
}
}
| 欄位 | 說明 |
|---|---|
resultMessage | 付款記錄 ID |
resultUUID | 穩定的 20 字元小寫 base32hex 付款 order ID(0–9、a–v),不是 UUID;同一主擁有者與 Idempotency-Key 重試會回相同值 |
data.iot_status | ok 表示 iot_server 回報派送成功,不代表設備已完成實體動作 |
data.warning / data.dispatch_state | 若存在,表示付款記錄已受理但派送仍在處理或結果不明;不可改用新 key 自動重送 |
resultCode: "0000" 表示請求與付款記錄已受理;設備是否已確認派送,仍須檢查 data.iot_status 或 warning。{
"resultCode": "0000",
"resultMessage": "12345",
"resultUUID": "0a1b2c3d4e5f6g7h8i9j",
"data": {
"idempotent_replay": true,
"warning": "payment was already accepted; dispatch outcome is pending or ambiguous; do not retry with a new Idempotency-Key",
"dispatch_state": "api_dispatch_started"
}
}
使用 GPT-4o 將店家公告改寫為更生動、帶有 emoji 的版本。不需要 SN。
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
command | string | required | "announce" |
values | string | required | 原始公告文字 |
{ "command": "announce", "values": "本店週三公休,造成不便敬請見諒" }
{
"resultCode": "0000",
"resultMessage": "OK",
"data": {
"announcement": "Notice: We will be closed on Sunday for the holiday. Please plan your visits accordingly. Thank you for your understanding!"
}
}
請同時判斷 HTTP 狀態與 resultCode;為維持相容性,4004 在 HTTP 400 表示付款 key 缺少或無效,在 HTTP 404 表示設備不存在。
| Code | HTTP | 說明 |
|---|---|---|
| 0000 | 200 | Success |
| 4000 | 400 | Invalid request / JSON parse error |
| 4001 | 400 | Missing required field |
| 4002 | 400 | Invalid pin number |
| 4003 | 400 | Invalid values (price) |
| 4004 | 400 | payment 的 Idempotency-Key 缺少或無效;未建立付款 |
| 4004 | 404 | device_info 查詢時設備不存在 |
| 4010 | 401 | Unauthorized / Invalid token |
| 4030 | 403 | Open API 目前停用 |
| 4029 | 429 | 超過請求額度;依 Retry-After 等待,付款保留原 key |
| 5007 | 503 | 設備心跳查詢失敗;不是離線結果 |
| 5008 | 503 | 防護服務不可用;拒絕付款副作用 |
| 5000 | 500/503 | 資料庫處理失敗或無法驗證權限;不表示已派送 |
| 5001~5006 | 500 | Internal service error (revenue/device/AI) |
| 9999 | 400 | Unknown command |
系統支援 Webhook 回呼機制:當特定事件發生時,主動推送通知到您指定的 URL。
POST{
"sn": "89123456", "orderNo": "cq4g8h1k5l2m",
"name": "1F 洗衣機", "pinName": "洗衣 40 分鐘",
"price": 40, "amount": 40, "currency": "TWD",
"dateTime": "2024-03-20 14:30:00",
"UID": "user_uid", "gateway": "LINE Pay", "memo": ""
}
POST{
"sn": "89123456", "orderNo": "unique_uuid",
"name": "2F 夾娃娃機", "pinName": "A區",
"price": 10, "amount": 10, "currency": "TWD",
"dateTime": "2024-03-20 15:00:00", "UID": "user_uid"
}
| 項目 | 規格 |
|---|---|
| Method | POST |
| Content-Type | text/plain; charset=utf-8 |
| 預期回應 | HTTP 200 + 純文字 OK |
| 白名單 IP | 139.162.116.209 |
https://iot.goalsoft.com.tw/goaliot/api
GoalSoft Open API 可直接映射為 Claude Tool Use 工具定義,讓 AI Agent 自動理解並呼叫。
JSON[
{
"name": "devices",
"description": "列出用戶所有 IoT 洗衣設備,包含名稱、服務、上線狀態",
"input_schema": {
"type": "object", "properties": {}, "required": []
}
},
{
"name": "status",
"description": "查詢指定洗衣設備的即時運行狀態、RSSI 訊號強度、是否在線",
"input_schema": {
"type": "object",
"properties": {
"sn": { "type": "string", "description": "設備序號" }
},
"required": ["sn"]
}
},
{
"name": "revenue",
"description": "查詢設備最近 12 小時逐時營收數據",
"input_schema": {
"type": "object",
"properties": {
"sn": { "type": "string", "description": "設備序號" }
},
"required": ["sn"]
}
},
{
"name": "payment",
"description": "遠端觸發洗衣設備啟動並付款",
"input_schema": {
"type": "object",
"properties": {
"sn": { "type": "string", "description": "設備序號" },
"pin": { "type": "string", "description": "服務編號 1 或 2" },
"values": { "type": "string", "description": "付款金額" }
},
"required": ["sn", "pin", "values"]
}
},
{
"name": "announce",
"description": "用 AI 將店家公告改寫為更生動、帶 emoji 的版本",
"input_schema": {
"type": "object",
"properties": {
"text": { "type": "string", "description": "原始公告文字" }
},
"required": ["text"]
}
}
]
Pythonimport anthropic, requests
API_URL = "https://iot.goalsoft.com.tw/goaliot/api"
API_TOKEN = "your_api_token"
client = anthropic.Anthropic()
def call_api(command, sn="", pin="", values=""):
return requests.post(API_URL, json={
"command": command, "sn": sn, "pin": pin, "values": values,
}, headers={"Authorization": f"Bearer {API_TOKEN}"}).json()
tools = [
{"name": "devices", "description": "列出所有設備",
"input_schema": {"type": "object", "properties": {}, "required": []}},
{"name": "status", "description": "查詢設備狀態",
"input_schema": {"type": "object",
"properties": {"sn": {"type": "string"}}, "required": ["sn"]}},
]
response = client.messages.create(
model="claude-sonnet-5-5",
max_tokens=1024, tools=tools,
messages=[{"role": "user", "content": "幫我看一下所有設備的狀態"}]
)
for block in response.content:
if block.type == "tool_use":
if block.name == "devices":
result = call_api("devices")
elif block.name == "status":
result = call_api("status", sn=block.input["sn"])
print(result)
cURL# 列出所有設備
curl -X POST https://iot.goalsoft.com.tw/goaliot/api \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command": "devices"}'
# 查詢設備狀態
curl -X POST https://iot.goalsoft.com.tw/goaliot/api \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command": "status", "sn": "89123456"}'
# 觸發付款
curl -X POST https://iot.goalsoft.com.tw/goaliot/api \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Idempotency-Key: payment-20260726-0001" \
-H "Content-Type: application/json" \
-d '{"command": "payment", "sn": "89123456", "pin": "1", "values": "40"}'
# AI 生成公告
curl -X POST https://iot.goalsoft.com.tw/goaliot/api \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command": "announce", "values": "本店週三公休"}'
Pythonimport requests
API = "https://iot.goalsoft.com.tw/goaliot/api"
H = {"Authorization": "Bearer YOUR_TOKEN", "Content-Type": "application/json"}
# 列出設備 + 逐一查狀態
devices = requests.post(API, json={"command": "devices"}, headers=H).json()["data"]["devices"]
for d in devices:
s = requests.post(API, json={"command": "status", "sn": d["sn"]}, headers=H).json()["data"]
print(f"{d['name']}: {'Online' if s['online'] else 'Offline'}")
# 查營收
r = requests.post(API, json={"command": "revenue", "sn": "89123456"}, headers=H).json()
print(f"12H 營收: {sum(r['data']['revenues'])} 元")
JavaScriptconst API = 'https://iot.goalsoft.com.tw/goaliot/api';
const TOKEN = 'YOUR_TOKEN';
async function callAPI(command, sn='', pin='', values='') {
const r = await fetch(API, {
method: 'POST',
headers: { 'Authorization': `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ command, sn, pin, values }),
});
return r.json();
}
const { data } = await callAPI('devices');
console.log(`共 ${data.count} 台設備`);
const revenue = await callAPI('revenue', '89123456');
console.log(`12H 營收: ${revenue.data.revenues.reduce((a,b)=>a+b,0)} 元`);
Gopackage main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type Req struct {
SN string `json:"sn,omitempty"`; Command string `json:"command"`
Pin string `json:"pin,omitempty"`; Values string `json:"values,omitempty"`
}
type Resp struct {
ResultCode string `json:"resultCode"`; Data json.RawMessage `json:"data,omitempty"`
}
func call(token string, r Req) *Resp {
b, _ := json.Marshal(r)
req, _ := http.NewRequest("POST", "https://iot.goalsoft.com.tw/goaliot/api", bytes.NewReader(b))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var res Resp
json.NewDecoder(resp.Body).Decode(&res)
return &res
}
func main() {
r := call("TOKEN", Req{Command: "devices"})
fmt.Println(string(r.Data))
}
PHP<?php
$API = 'https://iot.goalsoft.com.tw/goaliot/api';
$TOKEN = 'YOUR_TOKEN';
function callAPI($command, $sn = '', $pin = '', $values = '') {
global $API, $TOKEN;
$ch = curl_init($API);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $TOKEN,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(array_filter([
'command' => $command,
'sn' => $sn,
'pin' => $pin,
'values' => $values,
])),
]);
$resp = curl_exec($ch);
curl_close($ch);
return json_decode($resp, true);
}
// 列出設備
$devices = callAPI('devices')['data']['devices'];
foreach ($devices as $d) {
echo "{$d['name']} ({$d['sn']})\n";
}
// 查營收
$r = callAPI('revenue', '89123456');
echo '12H 營收: ' . array_sum($r['data']['revenues']) . " 元\n";
C#using System.Net.Http.Json;
var API = "https://iot.goalsoft.com.tw/goaliot/api";
var TOKEN = "YOUR_TOKEN";
var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", $"Bearer {TOKEN}");
async Task<JsonElement> CallAPI(string command, string sn = "", string pin = "", string values = "")
{
var resp = await http.PostAsJsonAsync(API, new { command, sn, pin, values });
var doc = await resp.Content.ReadFromJsonAsync<JsonDocument>();
return doc!.RootElement;
}
// 列出設備
var devices = await CallAPI("devices");
foreach (var d in devices.GetProperty("data").GetProperty("devices").EnumerateArray())
Console.WriteLine($"{d.GetProperty("name")} ({d.GetProperty("sn")})");
// 查營收
var r = await CallAPI("revenue", "89123456");
var total = r.GetProperty("data").GetProperty("revenues").EnumerateArray().Sum(x => x.GetInt64());
Console.WriteLine($"12H 營收: {total} 元");
Dart / Flutterimport 'dart:convert';
import 'package:http/http.dart' as http;
const api = 'https://iot.goalsoft.com.tw/goaliot/api';
const token = 'YOUR_TOKEN';
Future<Map<String, dynamic>> callAPI(String command,
{String sn = '', String pin = '', String values = ''}) async {
final resp = await http.post(Uri.parse(api),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: jsonEncode({
'command': command,
if (sn.isNotEmpty) 'sn': sn,
if (pin.isNotEmpty) 'pin': pin,
if (values.isNotEmpty) 'values': values,
}));
return jsonDecode(resp.body);
}
// 列出設備
final devices = (await callAPI('devices'))['data']['devices'] as List;
for (final d in devices) {
print('${d['name']} (${d['sn']})');
}
// 查營收
final r = await callAPI('revenue', sn: '89123456');
final total = (r['data']['revenues'] as List).reduce((a, b) => a + b);
print('12H 營收: $total 元');
此表單僅供經授權重新開放後測試;不會自動重試。
Response will appear here...