快速入门
- 在控制台打开 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 推出,并会提早公告。