ScanMosaic
REST API · v1

把 QR Code 和短網址整合到你的 App

從你自己的程式碼建立動態 QR Code、短網址和條碼,更改它們的目的地,讀取掃描統計,並產生可直接印刷的 QR Code 圖片。透過 HTTPS 傳送 JSON,一把 API 金鑰,錯誤訊息清楚易懂。

基礎網址
https://scanmosaic.com/api/v1
驗證
Authorization: Bearer tsk_…
方案
商業

快速入門

  1. 在儀表板開啟 API 金鑰(Business 方案)並建立金鑰。請複製保存:金鑰只會顯示一次。
  2. 把金鑰存成伺服器上的環境變數,例如 TESSERA_API_KEY。絕對不要放在瀏覽器或 App 的程式碼中。
  3. 建立動態 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.png

JavaScript

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"
  }
}
錯誤代碼
狀態驗證碼意義
400bad_json內文不是有效的 JSON。
400bad_param查詢或內文參數超出範圍。details:param、hint。
400bad_destinationdest 不是完整的 http(s) 網址。
400bad_kindkind 不是 qr、link 或 barcode。
400bad_aliasalias 不是 3–40 個字母、數字或連字號,或是保留字。
400domain_not_verifieddomain 不是你工作區中已驗證的網域。
400unsafe_destinationGoogle Web Risk 將目的地標示為有風險。
400empty / too_long / bad_formatQR Code 圖片:沒有資料、資料過多,或格式不是 png 或 svg。
400bad_cursor這個 cursor 不是我們提供的。
401api_key_required缺少 Authorization 標頭,或格式不是 Bearer tsk_…
401invalid_api_key金鑰不存在或已被刪除。
402business_required這個工作區不在 Business 方案中(例如方案已結束)。
403limit_reached已達到你方案中該類項目的上限。details:limit、kind。
404not_found這個工作區中沒有此項目。
409alias_taken這個別名在該網域上已被使用。
413too_big內文太大(項目上限為 250 KB)。
429rate_limited / daily_limit請求太多。請等待 Retry-After 秒數。
500server_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 加上分頁欄位回傳。

項目欄位
名稱類型說明
idstring簡短 id,也是短網址的預設結尾。
kindqr | link | barcode項目種類。建立後無法變更。
typestring內容類型,QR Code 例如 url(預設)、pdf、menu、vcard;條碼例如 ean13、code128。
namestring你的標籤,最多 80 個字元。
deststring | null掃描和點擊的目的地。代管頁面和條碼為 null。
urlstringQR Code 所編碼的短網址,例如 https://go.yourbrand.com/menu。
aliasstring | null自訂結尾(3–40 個字母、數字或連字號),在同一網域中不可重複。
domainstring | null你已驗證的自訂網域,或用 null 表示使用我們的網域。
campaignstring | null將項目分組以便報表統計。
contentobject | null代管頁面資料,或條碼資料({ "data": "…" })。
styleobjectQR Code 的設計(顏色、點和定位角樣式)。
rulesobject | null導向規則(依裝置、國家、時間…)。
pausedboolean已暫停的項目會顯示「已暫停」頁面,而不會轉址。
disabledboolean因違反條款而被我們停用。
hitsinteger累計掃描數(QR Code)或點擊數(連結)。
last_hitinteger | null最後一次掃描或點擊的 Unix 時間。
created / updatedintegerUnix 時間。

端點

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

列出項目

工作區中的項目,最新的在前。

參數
名稱類型說明
kindqr | link | barcode只顯示此種類。
typestring只顯示此內容類型,例如 url 或 pdf。
campaignstring只顯示此活動。
domainstring只顯示此自訂網域上的項目。
pausedtrue | false只顯示已暫停或使用中的項目。
qstring搜尋名稱、目的地和別名。
limitinteger1–100,預設 25。
cursorstring上一頁的 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、短網址或儲存的條碼。會計入你的方案上限。

參數
名稱類型說明
kindqr | link | barcode預設 qr。
dest 必填string要轉址到的完整 http(s) 網址。條碼和代管頁面不使用此欄位。
namestring最多 80 個字元。
typestring預設為 url(QR Code、連結)或 code128(條碼)。
aliasstring自訂結尾,例如 menu。3–40 個字母、數字或連字號,在同一網域中不可重複。
domainstring你已驗證的自訂網域之一(見 /domains)。
campaignstring最多 60 個字元。
styleobjectQR Code 設計,例如 {"fg": "#112233", "dot": "rounded"}。
contentobject代管頁面或條碼資料,例如條碼可用 {"bcid": "ean13", "text": "4006381333931"}。
rulesobject導向規則。
pausedboolean建立時即暫停。

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

項目統計

一段期間內的掃描或點擊:每日數列,以及依國家、裝置、作業系統、瀏覽器、語言和導向規則的分析。不計入機器人。

參數
名稱類型說明
daysinteger1–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 編碼任何文字。

參數
名稱類型說明
datastring要編碼的文字或連結(最多 2,953 位元組)。未設定 item 時為必填。
itemstring已儲存的 QR Code 或連結的 id:以儲存的設計編碼它的短網址。
formatpng | svg預設 png。
sizeinteger像素,PNG 為 64–2048(預設 512),SVG 為 64–4096(預設 1024)。
color#RRGGBB點的顏色。預設 #141A2E。
background#RRGGBB預設 #FFFFFF。
transparentboolean透明背景。
corner_color#RRGGBB定位角方塊。預設:color。
gradient_color#RRGGBB僅限 SVG:對角漸層的第二個顏色。
error_correctionL | M | Q | HQR Code 可承受的損壞程度:7%、15%、25%、30%。預設 M(PNG)或 Q(SVG)。
margininteger靜區的模組數,0–8。預設 4。
dot_stylestringsquare, dots, rounded, extra-rounded, classy, classy-rounded
corner_stylestringsquare, extra-rounded, dot
corner_dot_stylestringsquare, dot
downloadstring檔案名稱:加上 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.png

JavaScript

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 推出,並會提早公告。