快速入門
- 在儀表板開啟 API 金鑰(Business 方案)並建立金鑰。請複製保存:金鑰只會顯示一次。
- 把金鑰存成伺服器上的環境變數,例如 TESSERA_API_KEY。絕對不要放在瀏覽器或 App 的程式碼中。
- 建立動態 QR Code。回應的 url 欄位是它的短網址;把這個連結編碼,或從 /qr 下載現成的圖片。
cURL
curl "https://scanmosaic.com/api/v1/items" \
-H "Authorization: Bearer $TESSERA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"kind": "qr", "name": "Table menu", "dest": "https://example.com/menu"}'JavaScript
const res = await fetch("https://scanmosaic.com/api/v1/items", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TESSERA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"kind": "qr",
"name": "Table menu",
"dest": "https://example.com/menu"
})
});
const body = await res.json();
if (!res.ok) throw new Error(body.error.message);
console.log(body.data);Python
import os, requests
r = requests.post(
"https://scanmosaic.com/api/v1/items",
headers={"Authorization": f"Bearer {os.environ['TESSERA_API_KEY']}"},
json={
"kind": "qr",
"name": "Table menu",
"dest": "https://example.com/menu"
},
)
r.raise_for_status()
print(r.json()["data"])cURL
curl "https://scanmosaic.com/api/v1/qr?item=k3Xp9Qa&size=1024" \
-H "Authorization: Bearer $TESSERA_API_KEY" \
-o menu.pngJavaScript
import { writeFile } from "node:fs/promises";
const res = await fetch("https://scanmosaic.com/api/v1/qr?item=k3Xp9Qa&size=1024", {
headers: { Authorization: `Bearer ${process.env.TESSERA_API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).error.message);
await writeFile("menu.png", Buffer.from(await res.arrayBuffer()));Python
import os, requests
r = requests.get(
"https://scanmosaic.com/api/v1/qr?item=k3Xp9Qa&size=1024",
headers={"Authorization": f"Bearer {os.environ['TESSERA_API_KEY']}"},
)
r.raise_for_status()
open("menu.png", "wb").write(r.content)身分驗證
每個請求都要在 Authorization 標頭中附上你的金鑰。金鑰以 tsk_ 開頭並屬於單一工作區:可讀取和更改該工作區編輯者能操作的所有內容,但無法存取其他工作區。
Authorization: Bearer tsk_AbC123…
- 擁有者和管理員可在儀表板的「API 金鑰」中建立和刪除金鑰。刪除金鑰後會立即停用。
- API 是 Business 方案的功能。方案結束後,請求會收到 402 business_required,直到你續訂為止;你的 QR Code 和資料都會保留。
- 使用金鑰所做的變更,會以「API key tsk_…」的名義記錄在工作區的活動紀錄中。
- CORS 已開放,可從任何來源呼叫 API,但放在網頁中的金鑰,任何開啟網頁的人都能複製。請從你的伺服器呼叫 API。
錯誤
所有錯誤都使用相同的 JSON 格式和對應的 HTTP 狀態碼。code 是固定的,可以據此開發。message 是給記錄和人看的英文說明。有些錯誤會附加細節,例如 param。
{
"error": {
"code": "bad_param",
"message": "One of the parameters isn't valid.",
"status": 400,
"param": "size",
"hint": "64 to 2048"
}
}
| 狀態 | 驗證碼 | 意義 |
|---|---|---|
| 400 | bad_json | 內文不是有效的 JSON。 |
| 400 | bad_param | 查詢或內文參數超出範圍。details:param、hint。 |
| 400 | bad_destination | dest 不是完整的 http(s) 網址。 |
| 400 | bad_kind | kind 不是 qr、link 或 barcode。 |
| 400 | bad_alias | alias 不是 3–40 個字母、數字或連字號,或是保留字。 |
| 400 | domain_not_verified | domain 不是你工作區中已驗證的網域。 |
| 400 | unsafe_destination | Google Web Risk 將目的地標示為有風險。 |
| 400 | empty / too_long / bad_format | QR Code 圖片:沒有資料、資料過多,或格式不是 png 或 svg。 |
| 400 | bad_cursor | 這個 cursor 不是我們提供的。 |
| 401 | api_key_required | 缺少 Authorization 標頭,或格式不是 Bearer tsk_… |
| 401 | invalid_api_key | 金鑰不存在或已被刪除。 |
| 402 | business_required | 這個工作區不在 Business 方案中(例如方案已結束)。 |
| 403 | limit_reached | 已達到你方案中該類項目的上限。details:limit、kind。 |
| 404 | not_found | 這個工作區中沒有此項目。 |
| 409 | alias_taken | 這個別名在該網域上已被使用。 |
| 413 | too_big | 內文太大(項目上限為 250 KB)。 |
| 429 | rate_limited / daily_limit | 請求太多。請等待 Retry-After 秒數。 |
| 500 | server_error | 我們這邊發生錯誤。請以退避方式重試。 |
速率限制
每把金鑰每分鐘可發出 120 個請求,每天 20,000 個。每個回應都會告訴你目前的用量:
| 標頭 | 意義 |
|---|---|
X-RateLimit-Limit | 每分鐘允許的請求數。 |
X-RateLimit-Remaining | 本分鐘剩餘的請求數。 |
X-RateLimit-Reset | 本分鐘重設的 Unix 時間。 |
Retry-After | 僅限 429:重試前需等待的秒數。 |
每個工作區每小時最多建立 300 個項目,可保留的項目數量則依你的方案而定。需要更多?請聯絡我們。
分頁
清單由新到舊回傳,最多 limit 筆(預設 25,最多 100)。has_more 為 true 時,把 next_cursor 當作 cursor 傳入即可取得下一頁。新增項目時,cursor 仍然有效。
{
"data": [
"\u2026"
],
"has_more": true,
"next_cursor": "MTc2NzIyNTYwMC5rM1hwOVFh"
}
項目物件
QR Code、連結和條碼都屬於項目。單一項目以 data 回傳;清單以 data 加上分頁欄位回傳。
| 名稱 | 類型 | 說明 |
|---|---|---|
id | string | 簡短 id,也是短網址的預設結尾。 |
kind | qr | link | barcode | 項目種類。建立後無法變更。 |
type | string | 內容類型,QR Code 例如 url(預設)、pdf、menu、vcard;條碼例如 ean13、code128。 |
name | string | 你的標籤,最多 80 個字元。 |
dest | string | null | 掃描和點擊的目的地。代管頁面和條碼為 null。 |
url | string | QR Code 所編碼的短網址,例如 https://go.yourbrand.com/menu。 |
alias | string | null | 自訂結尾(3–40 個字母、數字或連字號),在同一網域中不可重複。 |
domain | string | null | 你已驗證的自訂網域,或用 null 表示使用我們的網域。 |
campaign | string | null | 將項目分組以便報表統計。 |
content | object | null | 代管頁面資料,或條碼資料({ "data": "…" })。 |
style | object | QR Code 的設計(顏色、點和定位角樣式)。 |
rules | object | null | 導向規則(依裝置、國家、時間…)。 |
paused | boolean | 已暫停的項目會顯示「已暫停」頁面,而不會轉址。 |
disabled | boolean | 因違反條款而被我們停用。 |
hits | integer | 累計掃描數(QR Code)或點擊數(連結)。 |
last_hit | integer | null | 最後一次掃描或點擊的 Unix 時間。 |
created / updated | integer | Unix 時間。 |
端點
GET /api/v1/me
帳號
此金鑰所屬的工作區、方案、上限、用量、已驗證的網域和速率限制。方便檢查金鑰是否可用。
cURL
curl "https://scanmosaic.com/api/v1/me" \
-H "Authorization: Bearer $TESSERA_API_KEY"JavaScript
const res = await fetch("https://scanmosaic.com/api/v1/me", {
headers: { Authorization: `Bearer ${process.env.TESSERA_API_KEY}` },
});
const body = await res.json();
if (!res.ok) throw new Error(body.error.message);
console.log(body.data);Python
import os, requests
r = requests.get(
"https://scanmosaic.com/api/v1/me",
headers={"Authorization": f"Bearer {os.environ['TESSERA_API_KEY']}"},
)
r.raise_for_status()
print(r.json()["data"])回應 200 OK
{
"data": {
"workspace": {
"id": "w_8fK2mQ",
"name": "Acme"
},
"plan": "business",
"key": {
"prefix": "tsk_AbC123",
"name": "Shopify sync"
},
"limits": {
"qr": 1000,
"link": 5000,
"barcode": 10000,
"domains": 5
},
"usage": {
"dynamic": 12,
"links": 40,
"barcodes": 3,
"storageBytes": 0,
"workspaces": 1
},
"domains": [
"go.yourbrand.com"
],
"rate_limits": {
"per_minute": 120,
"per_day": 20000
}
}
}GET /api/v1/items
列出項目
工作區中的項目,最新的在前。
| 名稱 | 類型 | 說明 |
|---|---|---|
kind | qr | link | barcode | 只顯示此種類。 |
type | string | 只顯示此內容類型,例如 url 或 pdf。 |
campaign | string | 只顯示此活動。 |
domain | string | 只顯示此自訂網域上的項目。 |
paused | true | false | 只顯示已暫停或使用中的項目。 |
q | string | 搜尋名稱、目的地和別名。 |
limit | integer | 1–100,預設 25。 |
cursor | string | 上一頁的 next_cursor。 |
cURL
curl "https://scanmosaic.com/api/v1/items?kind=link&campaign=spring&limit=50" \
-H "Authorization: Bearer $TESSERA_API_KEY"JavaScript
const res = await fetch("https://scanmosaic.com/api/v1/items?kind=link&campaign=spring&limit=50", {
headers: { Authorization: `Bearer ${process.env.TESSERA_API_KEY}` },
});
const body = await res.json();
if (!res.ok) throw new Error(body.error.message);
console.log(body.data);Python
import os, requests
r = requests.get(
"https://scanmosaic.com/api/v1/items?kind=link&campaign=spring&limit=50",
headers={"Authorization": f"Bearer {os.environ['TESSERA_API_KEY']}"},
)
r.raise_for_status()
print(r.json()["data"])回應 200 OK
{
"data": [
{
"id": "k3Xp9Qa",
"kind": "link",
"type": "url",
"name": "Spring menu",
"dest": "https://example.com/menu",
"content": null,
"style": {},
"rules": null,
"alias": "menu",
"domain": "go.yourbrand.com",
"campaign": "spring",
"og": null,
"created": 1767225600,
"updated": 1767225600,
"paused": false,
"disabled": false,
"hits": 42,
"last_hit": 1767312000,
"url": "https://go.yourbrand.com/menu"
}
],
"has_more": false,
"next_cursor": null
}POST /api/v1/items
建立項目
建立動態 QR Code、短網址或儲存的條碼。會計入你的方案上限。
| 名稱 | 類型 | 說明 |
|---|---|---|
kind | qr | link | barcode | 預設 qr。 |
dest 必填 | string | 要轉址到的完整 http(s) 網址。條碼和代管頁面不使用此欄位。 |
name | string | 最多 80 個字元。 |
type | string | 預設為 url(QR Code、連結)或 code128(條碼)。 |
alias | string | 自訂結尾,例如 menu。3–40 個字母、數字或連字號,在同一網域中不可重複。 |
domain | string | 你已驗證的自訂網域之一(見 /domains)。 |
campaign | string | 最多 60 個字元。 |
style | object | QR Code 設計,例如 {"fg": "#112233", "dot": "rounded"}。 |
content | object | 代管頁面或條碼資料,例如條碼可用 {"bcid": "ean13", "text": "4006381333931"}。 |
rules | object | 導向規則。 |
paused | boolean | 建立時即暫停。 |
cURL
curl "https://scanmosaic.com/api/v1/items" \
-H "Authorization: Bearer $TESSERA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"kind": "link", "name": "Spring menu", "dest": "https://example.com/menu", "alias": "menu", "domain": "go.yourbrand.com", "campaign": "spring"}'JavaScript
const res = await fetch("https://scanmosaic.com/api/v1/items", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TESSERA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"kind": "link",
"name": "Spring menu",
"dest": "https://example.com/menu",
"alias": "menu",
"domain": "go.yourbrand.com",
"campaign": "spring"
})
});
const body = await res.json();
if (!res.ok) throw new Error(body.error.message);
console.log(body.data);Python
import os, requests
r = requests.post(
"https://scanmosaic.com/api/v1/items",
headers={"Authorization": f"Bearer {os.environ['TESSERA_API_KEY']}"},
json={
"kind": "link",
"name": "Spring menu",
"dest": "https://example.com/menu",
"alias": "menu",
"domain": "go.yourbrand.com",
"campaign": "spring"
},
)
r.raise_for_status()
print(r.json()["data"])回應 201 Created
{
"data": {
"id": "k3Xp9Qa",
"kind": "link",
"type": "url",
"name": "Spring menu",
"dest": "https://example.com/menu",
"content": null,
"style": {},
"rules": null,
"alias": "menu",
"domain": "go.yourbrand.com",
"campaign": "spring",
"og": null,
"created": 1767225600,
"updated": 1767225600,
"paused": false,
"disabled": false,
"hits": 42,
"last_hit": 1767312000,
"url": "https://go.yourbrand.com/menu"
}
}GET /api/v1/items/{id}
取得項目
依 id 取得單一項目。
cURL
curl "https://scanmosaic.com/api/v1/items/k3Xp9Qa" \
-H "Authorization: Bearer $TESSERA_API_KEY"JavaScript
const res = await fetch("https://scanmosaic.com/api/v1/items/k3Xp9Qa", {
headers: { Authorization: `Bearer ${process.env.TESSERA_API_KEY}` },
});
const body = await res.json();
if (!res.ok) throw new Error(body.error.message);
console.log(body.data);Python
import os, requests
r = requests.get(
"https://scanmosaic.com/api/v1/items/k3Xp9Qa",
headers={"Authorization": f"Bearer {os.environ['TESSERA_API_KEY']}"},
)
r.raise_for_status()
print(r.json()["data"])回應 200 OK
{
"data": {
"id": "k3Xp9Qa",
"kind": "link",
"type": "url",
"name": "Spring menu",
"dest": "https://example.com/menu",
"content": null,
"style": {},
"rules": null,
"alias": "menu",
"domain": "go.yourbrand.com",
"campaign": "spring",
"og": null,
"created": 1767225600,
"updated": 1767225600,
"paused": false,
"disabled": false,
"hits": 42,
"last_hit": 1767312000,
"url": "https://go.yourbrand.com/menu"
}
}PATCH /api/v1/items/{id}
更新項目
只傳送要更改的欄位。更改 dest 會在下次掃描時生效,已印出的 QR Code 仍可使用。更改 domain 或 alias 會改變短網址,已印出的 QR Code 將會失效。
| 名稱 | 類型 | 說明 |
|---|---|---|
name, dest, alias, domain, campaign, style, content, rules, paused | 同「建立」。kind 無法變更。傳送 null 可清除 alias、domain、campaign 或 rules。 |
cURL
curl -X PATCH "https://scanmosaic.com/api/v1/items/k3Xp9Qa" \
-H "Authorization: Bearer $TESSERA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dest": "https://example.com/summer-menu", "paused": false}'JavaScript
const res = await fetch("https://scanmosaic.com/api/v1/items/k3Xp9Qa", {
method: "PATCH",
headers: { Authorization: `Bearer ${process.env.TESSERA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"dest": "https://example.com/summer-menu",
"paused": false
})
});
const body = await res.json();
if (!res.ok) throw new Error(body.error.message);
console.log(body.data);Python
import os, requests
r = requests.patch(
"https://scanmosaic.com/api/v1/items/k3Xp9Qa",
headers={"Authorization": f"Bearer {os.environ['TESSERA_API_KEY']}"},
json={
"dest": "https://example.com/summer-menu",
"paused": False
},
)
r.raise_for_status()
print(r.json()["data"])回應 200 OK
{
"data": {
"id": "k3Xp9Qa",
"kind": "link",
"type": "url",
"name": "Spring menu",
"dest": "https://example.com/summer-menu",
"content": null,
"style": {},
"rules": null,
"alias": "menu",
"domain": "go.yourbrand.com",
"campaign": "spring",
"og": null,
"created": 1767225600,
"updated": 1767225600,
"paused": false,
"disabled": false,
"hits": 42,
"last_hit": 1767312000,
"url": "https://go.yourbrand.com/menu"
}
}DELETE /api/v1/items/{id}
刪除項目
刪除項目及其掃描紀錄。它的短網址會立即失效。此動作無法復原。
cURL
curl -X DELETE "https://scanmosaic.com/api/v1/items/k3Xp9Qa" \
-H "Authorization: Bearer $TESSERA_API_KEY"JavaScript
const res = await fetch("https://scanmosaic.com/api/v1/items/k3Xp9Qa", {
method: "DELETE",
headers: { Authorization: `Bearer ${process.env.TESSERA_API_KEY}` },
});
const body = await res.json();
if (!res.ok) throw new Error(body.error.message);
console.log(body.data);Python
import os, requests
r = requests.delete(
"https://scanmosaic.com/api/v1/items/k3Xp9Qa",
headers={"Authorization": f"Bearer {os.environ['TESSERA_API_KEY']}"},
)
r.raise_for_status()
print(r.json()["data"])回應 200 OK
{
"data": {
"id": "k3Xp9Qa",
"deleted": true
}
}GET /api/v1/items/{id}/stats
項目統計
一段期間內的掃描或點擊:每日數列,以及依國家、裝置、作業系統、瀏覽器、語言和導向規則的分析。不計入機器人。
| 名稱 | 類型 | 說明 |
|---|---|---|
days | integer | 1–365,預設 30。 |
cURL
curl "https://scanmosaic.com/api/v1/items/k3Xp9Qa/stats?days=7" \
-H "Authorization: Bearer $TESSERA_API_KEY"JavaScript
const res = await fetch("https://scanmosaic.com/api/v1/items/k3Xp9Qa/stats?days=7", {
headers: { Authorization: `Bearer ${process.env.TESSERA_API_KEY}` },
});
const body = await res.json();
if (!res.ok) throw new Error(body.error.message);
console.log(body.data);Python
import os, requests
r = requests.get(
"https://scanmosaic.com/api/v1/items/k3Xp9Qa/stats?days=7",
headers={"Authorization": f"Bearer {os.environ['TESSERA_API_KEY']}"},
)
r.raise_for_status()
print(r.json()["data"])回應 200 OK
{
"data": {
"days": 7,
"total": 42,
"last_hit": 1767312000,
"inRange": 12,
"series": [
[
"2026-01-01",
3
],
[
"2026-01-02",
0
],
[
"…",
0
]
],
"countries": [
{
"k": "GB",
"n": 7
},
{
"k": "US",
"n": 5
}
],
"devices": [
{
"k": "Mobile",
"n": 10
},
{
"k": "Desktop",
"n": 2
}
],
"os": [
{
"k": "iOS",
"n": 6
}
],
"browsers": [
{
"k": "Safari",
"n": 6
}
],
"langs": [
{
"k": "en-gb",
"n": 7
}
],
"rules": [
{
"k": "default",
"n": 12
}
]
}
}GET /api/v1/qr
QR Code 圖片
將 QR Code 產生為 PNG 或 SVG。可用 GET 加查詢參數,或以 JSON POST 相同的參數。傳入 item 可依儲存的設計和短網址繪製已儲存的 QR Code,或傳入 data 編碼任何文字。
| 名稱 | 類型 | 說明 |
|---|---|---|
data | string | 要編碼的文字或連結(最多 2,953 位元組)。未設定 item 時為必填。 |
item | string | 已儲存的 QR Code 或連結的 id:以儲存的設計編碼它的短網址。 |
format | png | svg | 預設 png。 |
size | integer | 像素,PNG 為 64–2048(預設 512),SVG 為 64–4096(預設 1024)。 |
color | #RRGGBB | 點的顏色。預設 #141A2E。 |
background | #RRGGBB | 預設 #FFFFFF。 |
transparent | boolean | 透明背景。 |
corner_color | #RRGGBB | 定位角方塊。預設:color。 |
gradient_color | #RRGGBB | 僅限 SVG:對角漸層的第二個顏色。 |
error_correction | L | M | Q | H | QR Code 可承受的損壞程度:7%、15%、25%、30%。預設 M(PNG)或 Q(SVG)。 |
margin | integer | 靜區的模組數,0–8。預設 4。 |
dot_style | string | square, dots, rounded, extra-rounded, classy, classy-rounded |
corner_style | string | square, extra-rounded, dot |
corner_dot_style | string | square, dot |
download | string | 檔案名稱:加上 Content-Disposition: attachment。 |
PNG 由我們的伺服器繪製,每個模組都對齊整數像素,因此邊緣在印刷時依然清晰。漸層和 Logo 僅支援 SVG;若要加上 Logo,請使用產生器。
cURL
curl "https://scanmosaic.com/api/v1/qr" \
-H "Authorization: Bearer $TESSERA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"data": "https://example.com", "format": "png", "size": 1024, "color": "#4865F0", "dot_style": "rounded", "corner_style": "extra-rounded"}' \
-o qr.pngJavaScript
import { writeFile } from "node:fs/promises";
const res = await fetch("https://scanmosaic.com/api/v1/qr", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TESSERA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"data": "https://example.com",
"format": "png",
"size": 1024,
"color": "#4865F0",
"dot_style": "rounded",
"corner_style": "extra-rounded"
})
});
if (!res.ok) throw new Error((await res.json()).error.message);
await writeFile("qr.png", Buffer.from(await res.arrayBuffer()));Python
import os, requests
r = requests.post(
"https://scanmosaic.com/api/v1/qr",
headers={"Authorization": f"Bearer {os.environ['TESSERA_API_KEY']}"},
json={
"data": "https://example.com",
"format": "png",
"size": 1024,
"color": "#4865F0",
"dot_style": "rounded",
"corner_style": "extra-rounded"
},
)
r.raise_for_status()
open("qr.png", "wb").write(r.content)GET /api/v1/domains
網域
工作區的自訂網域。建立項目時,請使用 ready 為 true 的主機作為 domain。
cURL
curl "https://scanmosaic.com/api/v1/domains" \
-H "Authorization: Bearer $TESSERA_API_KEY"JavaScript
const res = await fetch("https://scanmosaic.com/api/v1/domains", {
headers: { Authorization: `Bearer ${process.env.TESSERA_API_KEY}` },
});
const body = await res.json();
if (!res.ok) throw new Error(body.error.message);
console.log(body.data);Python
import os, requests
r = requests.get(
"https://scanmosaic.com/api/v1/domains",
headers={"Authorization": f"Bearer {os.environ['TESSERA_API_KEY']}"},
)
r.raise_for_status()
print(r.json()["data"])回應 200 OK
{
"data": [
{
"host": "go.yourbrand.com",
"verified": true,
"ready": true
}
]
}自訂網域
在 Business 方案中,你的短網址可以使用自己的網域,例如 go.yourbrand.com/menu。請在儀表板的「網域」中新增並驗證網域:新增一筆指向我們的 CNAME 紀錄,以及一筆證明網域屬於你的 TXT 紀錄,我們就會核發 SSL 憑證。之後在建立或更新項目時傳入 domain 即可。
每個網域都可以把根網址(go.yourbrand.com)和不存在的路徑轉址到你指定的頁面。
OpenAPI
完整的 API 說明請見 openapi.json(OpenAPI 3.1)。可匯入 Postman、Insomnia 或用戶端產生器。
版本管理
v1 是穩定版。我們可能會新增端點、欄位和錯誤代碼,所以請忽略你不認得的欄位。我們不會移除或重新命名 v1 中的任何內容;破壞性變更會以 v2 推出,並會提早公告。