用 API 自動化你的短網址:toui.io 開發者指南
發佈於 2026年4月10日
行銷部丟了 50 條商品連結過來,說這週末要做限時特賣。
每條連結都要變成短網址,每條都要加上社群預覽標題跟描述——因為會丟到 LINE 群組和 Facebook 粉專。哦對了,活動結束後還要看成效報表。
你打開後台,手動建了三條,心裡開始算:50 條,每條要填網址、標題、OG 描述⋯⋯大概要花一整個下午。
然後你想起來:「等一下,這個東西有 API 吧?」
沒錯。這篇文章就是要帶你用 toui.io 的 API,從零到完成一個促銷活動連結產生器。三個步驟,不到 100 行程式碼。
開始之前
你需要:
- toui.io Pro 或 Business 方案 — API 存取需要付費方案(方案比較)
- 一組 API Key — 登入後台,到「API Keys」頁面建立
- Node.js 18+ — 我們用 Node 內建的
fetch(),不需要裝額外套件
API Key 長得像 toui_ 開頭加一串亂碼。拿到之後,在專案根目錄建一個 .env 檔案:
# .env
TOUI_API_KEY=toui_your_key_here
記得把 .env 加進 .gitignore——API Key 絕對不要 commit 進版本控制。
然後裝 dotenv 來讀環境變數:
npm install dotenv
如果你用 Node.js 20.6+,可以跳過 dotenv,直接用內建的
--env-file旗標:node --env-file=.env create-links.js
所有範例的開頭都會長這樣:
import "dotenv/config";
const API_KEY = process.env.TOUI_API_KEY;
if (!API_KEY) {
throw new Error("Missing TOUI_API_KEY in .env file");
}
少了這個 guard,漏設環境變數的人只會拿到一個莫名其妙的 401,debug 半天。
Step 1:批次建立短網址
情境:行銷部給了你一份商品清單,你要把每一條都變成有追蹤功能的短網址,順便加上 LINE 跟 Facebook 分享時的預覽資訊。
API 端點:POST https://toui.io/api/v1/shorten
import "dotenv/config";
const API_KEY = process.env.TOUI_API_KEY;
if (!API_KEY) {
throw new Error("Missing TOUI_API_KEY in .env file");
}
const API_BASE = "https://toui.io/api/v1";
// Weekend flash sale products
const products = [
{
url: "https://shop.example.com/airpods-pro-2",
title: "AirPods Pro 2",
code: "airpods",
og_title: "AirPods Pro 2 限時特價 $5,990",
og_description: "原價 $7,490,週末兩天限定。主動降噪、USB-C 充電盒。",
},
{
url: "https://shop.example.com/air-fryer-japan",
title: "日本氣炸鍋",
code: "airfryer",
og_title: "日本氣炸鍋 現折 $1,200",
og_description: "4.5L 大容量,八種預設模式,少油更健康。",
},
{
url: "https://shop.example.com/robot-vacuum-x1",
title: "掃地機器人 X1",
code: "vacuum",
og_title: "掃地機器人 X1 直降 $3,000",
og_description: "雷射導航、自動集塵、App 遠端遙控。",
},
];
async function createShortUrl(product) {
const res = await fetch(`${API_BASE}/shorten`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: product.url,
title: product.title,
custom_code: product.code,
og_title: product.og_title,
og_description: product.og_description,
}),
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Failed to create ${product.title}: ${err.error}`);
}
return res.json();
}
async function main() {
console.log("Creating short URLs for flash sale...\n");
for (const product of products) {
const result = await createShortUrl(product);
console.log(`${product.title}: ${result.short_url}`);
}
console.log("\nDone! All links ready.");
}
main().catch(console.error);
跑起來大概長這樣:
Creating short URLs for flash sale...
AirPods Pro 2: https://toui.io/airpods
日本氣炸鍋: https://toui.io/airfryer
掃地機器人 X1: https://toui.io/vacuum
Done! All links ready.
幾件事情說明一下:
custom_code是選填的,4-8 個英數字元。不填的話會自動產生 6 碼隨機短碼。自訂短碼是付費功能。og_title和og_description也是選填的。但如果你的連結要丟到 LINE 群組或 Facebook,強烈建議填上——預覽卡片好不好看,直接影響點擊率。title是給你自己在後台辨識用的,不會顯示在外部。- 回傳的
short_url就是可以直接用的完整短網址。
50 條商品?把 products 陣列換成從 Excel 或資料庫讀進來的資料,一樣的邏輯,兩分鐘跑完。
Step 2:確認連結狀態
建完之後,你可能想確認一下某條連結有沒有成功建立、目標網址對不對。
API 端點:GET https://toui.io/api/v1/urls/{code}
async function checkUrl(code) {
const res = await fetch(`${API_BASE}/urls/${code}`, {
headers: {
"Authorization": `Bearer ${API_KEY}`,
},
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Check failed for ${code}: ${err.error}`);
}
return res.json();
}
// Spot-check one link
const info = await checkUrl("airpods");
console.log("Short code:", info.short_code);
console.log("Target:", info.target_url);
console.log("Title:", info.title);
console.log("Clicks:", info.click_count);
console.log("OG title:", info.og_title);
console.log("Active:", info.is_active);
回傳的欄位包含:
| 欄位 | 說明 |
|---|---|
short_code | 短碼 |
target_url | 目標網址 |
title | 標題(後台用) |
click_count | 累計點擊數 |
is_active | 是否啟用中 |
og_title | 社群預覽標題 |
og_description | 社群預覽描述 |
og_image_url | 社群預覽圖片 |
created_at | 建立時間 |
這一步在正式環境裡通常不會每條都手動查——但寫進你的建立流程尾端做一次 spot check,debug 的時候會感謝自己。
Step 3:活動結束,看成效
週末結束了,行銷部問你:「哪個商品連結點最多?流量從哪來的?」
API 端點:GET https://toui.io/api/v1/urls/{code}/stats?days=7
async function getStats(code, days = 7) {
const res = await fetch(`${API_BASE}/urls/${code}/stats?days=${days}`, {
headers: {
"Authorization": `Bearer ${API_KEY}`,
},
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Stats failed for ${code}: ${err.error}`);
}
return res.json();
}
// Build a quick report
async function report() {
const codes = ["airpods", "airfryer", "vacuum"];
for (const code of codes) {
const stats = await getStats(code, 7);
console.log(`\n--- ${stats.short_code} ---`);
console.log(`Total clicks: ${stats.total_clicks}`);
// Daily breakdown
for (const day of stats.daily) {
console.log(` ${day.date}: ${day.clicks} clicks, ${day.unique_visitors} unique`);
}
// Top countries
if (stats.countries?.length) {
console.log("Top countries:");
for (const c of stats.countries) {
console.log(` ${c.country}: ${c.clicks}`);
}
}
// Top referers
if (stats.referers?.length) {
console.log("Top referers:");
for (const r of stats.referers) {
console.log(` ${r.referer}: ${r.clicks}`);
}
}
// Devices
if (stats.devices?.length) {
console.log("Devices:");
for (const d of stats.devices) {
console.log(` ${d.device_type}: ${d.clicks}`);
}
}
}
}
report().catch(console.error);
跑出來大概長這樣:
--- airpods ---
Total clicks: 1,847
2026-04-05: 823 clicks, 614 unique
2026-04-06: 1024 clicks, 789 unique
Top countries:
TW: 1,650
HK: 102
US: 53
Top referers:
line.me: 894
facebook.com: 612
direct: 341
Devices:
mobile: 1,423
desktop: 424
一眼就看出來——LINE 帶來的流量比 Facebook 多,而且絕大多數是手機點的。下次活動素材該怎麼調整,數據說了算。
有一個要注意的地方:進階分析(countries、referers、devices)需要 Pro 以上方案。如果你的方案不支援,這些欄位會回傳有限的資料,並帶有 limited: true 標記。
正式環境注意事項
把上面的 script 搬到正式環境之前,有幾件事要注意:
速率限制
| 方案 | 每分鐘上限 | 每月 API 總量 |
|---|---|---|
| Pro | 200 req/min | 500,000 req/月 |
| Business | 600 req/min | 500,000 req/月 |
超過限制會收到 429 Too Many Requests。每次回應的 header 裡都有 X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset,讓你知道上限、剩餘配額、什麼時候重置。被 429 的時候,response body 裡會有 retry_after 秒數。
處理 429
50 條連結不會碰到限制,但如果你要一次建幾百條,加個簡單的 backoff:
async function createWithRetry(product, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const res = await fetch(`${API_BASE}/shorten`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: product.url,
title: product.title,
custom_code: product.code,
og_title: product.og_title,
og_description: product.og_description,
}),
});
if (res.status === 429) {
const body = await res.json();
const retryAfter = body.retry_after || 5;
console.log(`Rate limited, waiting ${retryAfter}s... (attempt ${attempt})`);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
continue;
}
if (!res.ok) {
const err = await res.json();
throw new Error(`Failed: ${err.error}`);
}
return res.json();
}
throw new Error(`Max retries exceeded for ${product.title}`);
}
錯誤碼速查
| HTTP 狀態碼 | 意思 | 怎麼處理 |
|---|---|---|
400 | 參數錯誤(URL 格式不對、短碼不合規等) | 檢查 request body |
401 | API Key 無效或沒帶 | 確認 Authorization header |
403 | 權限不足(Free 方案打 API、自訂短碼等) | 確認方案等級 |
404 | 短碼不存在或不屬於你的團隊 | 確認短碼正確 |
429 | 超過速率限制 | 讀 body 的 retry_after 秒數後重試 |
安全提醒
- API Key 永遠放環境變數,不要 hardcode
- 不要在 log 裡印出完整的 API Key
- URL 會經過 Google Safe Browsing 檢查,惡意網址會被擋(
403) - 自訂短碼如果不可用,API 會回傳錯誤——換一個就好
你剛建了一個促銷活動引擎
回頭看一下——你用不到 100 行程式碼,做了這些事:
- 批次建立帶有社群預覽的短網址
- 自動驗證每條連結的狀態
- 活動結束後拉出完整的成效報表
這整套邏輯可以包成一個 script,下次行銷部再丟 50 條連結過來,你花 30 秒改一下商品清單,跑一次就搞定了。比手動建快 100 倍,而且不會漏、不會錯。
更進階的用法——串進你的電商後台、每次上新品自動建短網址、定期拉報表寄給行銷部——都是在這個基礎上加工而已。