GOAL-IOT Open API

IoT Smart Payment Platform
AI Agent & Third-party Integration

REST + JSON AI Agent Ready Claude Tool Use

Overview

目前未開放:Open API 已停用,/api 回覆 HTTP 403、resultCode: "4030"。下列規格與範例供整合準備;不表示已授權或啟用對外存取。

GOAL-IOT Open API 提供統一的 JSON 介面,重新開放後可供 AI Agent、第三方系統、自動化工具:

Base URL

https://iot.goalsoft.com.tw/goaliot

API Endpoint

POST https://iot.goalsoft.com.tw/goaliot/api

將 Base URL 與路徑 /api 各接一次;不要使用 /api/api。

Quick Links

資源路徑用途
API Endpoint/api所有指令的統一入口
本文件/api/docs人類閱讀用的 API 文件
OpenAPI Spec/api/openapi.json機器可讀規格(LangChain / GPTs Actions)
Well-Known/.well-known/openapi.json標準發現路徑(IETF RFC 8615)

Request Format

所有指令都透過同一個 endpoint,以 command 欄位區分功能:

{
  "sn":      "device_serial_number",   // 設備序號(部分指令可省略)
  "command": "status",                  // 指令名稱(必填)
  "pin":     "1",                       // 指令參數(依指令而定)
  "values":  "100"                      // 指令參數(依指令而定)
}

Response Format

{
  "resultCode":    "0000",              // "0000" = 成功或付款已受理,其他 = 錯誤碼
  "resultMessage": "OK",               // 結果訊息
  "resultUUID":    "",                  // 穩定的 20 字元付款 order ID(僅 payment 回傳)
  "data": { ... }                       // 指令回傳資料
}

Authentication

所有 API 請求需附帶 Bearer Token:

Authorization: Bearer YOUR_API_TOKEN
每位用戶註冊時系統自動產生唯一的 API Token(32 字元十六進位字串),儲存於用戶帳號中。
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 查核結果或請人工確認。

▶ POST ping API 健康檢查

最輕量的健康檢查。AI Agent 啟動時先 ping 一下,確認 API 活著、Token 有效。不需要 SN。

Request

{ "command": "ping" }

Response

{
  "resultCode": "0000",
  "resultMessage": "pong",
  "data": {
    "timestamp": 1711267200,
    "version": "1.0"
  }
}
欄位說明
timestamp伺服器 Unix 時間戳
versionAPI 版本號
▶ POST commands 列出所有可用 API 指令

取得 API 支援的所有指令清單,包含每個指令的參數需求。適合 AI Agent 啟動時先呼叫此指令了解能力範圍。

Request

{ "command": "commands" }

Response

{
  "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
      }
    ]
  }
}
▶ POST devices 列出用戶所有設備

取得該 Token 擁有者的所有設備清單,包含即時上線狀態。不需要 SN。

Request

{ "command": "devices" }

Response

{
  "resultCode": "0000",
  "resultMessage": "OK",
  "data": {
    "count": 1,
    "devices": [
      {
        "sn": "ABC12345",
        "name": "1F Washer",
        "pin1": "Wash",
        "pin2": "Dry",
        "online": true,
        "enabled": 1
      }
    ]
  }
}
▶ POST status 查詢設備即時狀態

取得指定設備的即時運行狀態,包含 RSSI 訊號強度、運行中服務、數值等。

Request

欄位類型必填說明
snstringrequired設備序號
commandstringrequired"status"
{ "sn": "89123456", "command": "status" }

Response

{
  "resultCode": "0000",
  "resultMessage": "OK",
  "data": {
    "sn": "ABC12345",
    "running": {},
    "value": {},
    "rssi": {
      "rssi": "-45",
      "time": "1711267200",
      "ping": "8"
    },
    "lastRssi": -45,
    "lastRssiUnix": 1711267200,
    "online": true
  }
}

Data Fields

欄位說明
running各服務的運行狀態 map
value各服務的目前數值(如剩餘分鐘)
rssiRSSI 訊號資料
lastRssi本次讀到的心跳 RSSI;無心跳時為 0
lastRssiUnix心跳的 Unix 秒數;無時間資料時為 0(未知),不是快取重載時間
online本次共享 Redis 心跳中是否有 rssi(TTL 300 秒);查詢失敗回 503,不偽裝為離線
vending販賣機庫存狀態(如適用)
▶ POST device_info 查詢設備詳細資訊

取得設備的基本資料(名稱、韌體版本、服務名稱等)。enabled 是整數,例如 0/1;uid 只有主擁有者本人可見,分享者回覆會省略此欄位。

Request

{ "sn": "89123456", "command": "device_info" }

Response

{
  "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"
  }
}
▶ POST revenue 查詢設備近 12 小時營收

取得指定設備最近 12 小時的逐時營收數據,適合 AI Agent 做營收分析與預測。

Request

{ "sn": "89123456", "command": "revenue" }

Response

{
  "resultCode": "0000",
  "resultMessage": "OK",
  "data": {
    "revenues": [
      0,
      100,
      50,
      200,
      0,
      0,
      150,
      300,
      0,
      50,
      100,
      0
    ],
    "start_unix_hour": 475343
  }
}
欄位說明
revenues12 個整數陣列,索引 0 = 最早,索引 11 = 當前小時
start_unix_hour起始 Unix Hour(timestamp / 3600)
計算時間: new Date(start_unix_hour * 3600 * 1000)
每個元素代表該小時累計營收(新台幣)。
▶ POST records 查詢設備事件記錄

取得設備的事件記錄(WiFi 變更、重啟、韌體更新等),支援分頁。

欄位類型必填說明
snstringrequired設備序號
commandstringrequired"records"
pinstringoptional頁碼(從 0 開始)
{ "sn": "89123456", "command": "records", "pin": "0" }

Response

{
  "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"
    ]
  }
}
▶ POST payment 遠端觸發設備付款
注意:此指令會實際觸發設備啟動並建立付款記錄,請確認後再呼叫。

Required Header

Header必填說明
Idempotency-Keyrequired8–128 個 visible ASCII 字元。重試同一筆付款必須重用同一 key;新付款必須使用新 key。同一 key 搭配不同 sn / pin / values 會回 409。
若回覆表示派送結果不明,請保留原 key 查詢/人工確認;不可換新 key 自動重送,以免設備重複啟動。
欄位類型必填說明
snstringrequired設備序號
commandstringrequired"payment"
pinstringrequired服務編號:"1" 或 "2"
valuesstringrequired金額(如 "40")
{ "sn": "89123456", "command": "payment", "pin": "1", "values": "40" }

Response

{
  "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_statusok 表示 iot_server 回報派送成功,不代表設備已完成實體動作
data.warning / data.dispatch_state若存在,表示付款記錄已受理但派送仍在處理或結果不明;不可改用新 key 自動重送
payment 的 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"
  }
}
▶ POST announce AI 智能改寫公告

使用 GPT-4o 將店家公告改寫為更生動、帶有 emoji 的版本。不需要 SN。

欄位類型必填說明
commandstringrequired"announce"
valuesstringrequired原始公告文字
{ "command": "announce", "values": "本店週三公休,造成不便敬請見諒" }

Response

{
  "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!"
  }
}
額外小時限額:公告改寫預設每位用戶每小時最多 10 次,實際值依伺服器設定;此小時限額的 429 目前不附 Retry-After。

Error Codes

請同時判斷 HTTP 狀態與 resultCode;為維持相容性,4004 在 HTTP 400 表示付款 key 缺少或無效,在 HTTP 404 表示設備不存在。

CodeHTTP說明
0000200Success
4000400Invalid request / JSON parse error
4001400Missing required field
4002400Invalid pin number
4003400Invalid values (price)
4004400payment 的 Idempotency-Key 缺少或無效;未建立付款
4004404device_info 查詢時設備不存在
4010401Unauthorized / Invalid token
4030403Open API 目前停用
4029429超過請求額度;依 Retry-After 等待,付款保留原 key
5007503設備心跳查詢失敗;不是離線結果
5008503防護服務不可用;拒絕付款副作用
5000500/503資料庫處理失敗或無法驗證權限;不表示已派送
5001~5006500Internal service error (revenue/device/AI)
9999400Unknown command

Webhook Callback

系統支援 Webhook 回呼機制:當特定事件發生時,主動推送通知到您指定的 URL。

在 APP 中進入「設定 Callback」頁面即可設定回呼網址。

行動支付回呼

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"
}
項目規格
MethodPOST
Content-Typetext/plain; charset=utf-8
預期回應HTTP 200 + 純文字 OK
白名單 IP139.162.116.209

How to Get Your API Key

  1. 開啟 GOAL 物聯網 APP
  2. 進入任一設備的管理頁面
  3. 點選「設定 Callback」
  4. 在頁面底部找到「API 協議金鑰」區塊
  5. 點擊複製圖示即可複製 Token

API Endpoint

https://iot.goalsoft.com.tw/goaliot/api
每位用戶有唯一的 API Token,綁定您帳號下所有設備的存取權限。請妥善保管。

Claude Tool Use Integration

GoalSoft Open API 可直接映射為 Claude Tool Use 工具定義,讓 AI Agent 自動理解並呼叫。

Tool Definitions

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"]
    }
  }
]

Python Agent Example

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)

Code Examples

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 元');

Try It

目前 Open API 停用,預設入口將回 HTTP 403。不要以連續重送測試服務是否重新開放;付款如有 warning/timeout,不可換新 key 自動重送。

此表單僅供經授權重新開放後測試;不會自動重試。

Response will appear here...
#OpenAPI #AIAgent #ClaudeToolUse #IoT #SmartLaundry #網宇科技