桌面客户端激活码接入文档
按这个顺序接入:后台准备 projectKey 和 API Secret,客户端保存 machineId,用户输入激活码时 activate,程序启动时 status,次数卡真实使用后 consume。
先看这 3 件事
新客户端不用先研究全部接口。先准备接入信息,再让客户端保存稳定 machineId,最后按 activate、status、consume 三个接口接入。/api/verify 只给旧版本兼容。
后台拿到 Base URL、projectKey 和 API Secret;客户端再保存一个稳定 machineId。
用户输入激活码时 activate,程序启动时 status,真正消耗权益时 consume。
只给历史客户端兼容。新接入不要再用它,避免“查询状态”和“扣次数”混在一起。
客户端需要准备什么
Python 桌面程序通常内置 Base URL、projectKey 和 API Secret。machineId 不要写死,首次运行生成后保存在本机,以后一直复用。
API Secret 怎么用
如果项目配置了 API Secret,请求必须带签名头。签名不是授权本身,它只是证明请求按你的客户端规则发出;真正的授权仍由激活码、machineId 绑定、有效期和次数决定。
import hashlib
import hmac
import json
import time
import uuid
api_secret = "paste-project-api-secret-here"
method = "POST"
path = "/api/license/status"
payload = {
"projectKey": "desktop-app",
"code": "A1B2C3D4E5F6G7H8",
"machineId": "machine-001",
}
body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False)
timestamp = str(int(time.time()))
nonce = str(uuid.uuid4())
body_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
canonical = "\n".join([method, path, timestamp, nonce, body_hash])
signature = hmac.new(
api_secret.encode("utf-8"),
canonical.encode("utf-8"),
hashlib.sha256,
).hexdigest()
headers = {
"Content-Type": "application/json",
"X-License-Timestamp": timestamp,
"X-License-Nonce": nonce,
"X-License-Signature": signature,
"X-License-Signature-Version": "v1",
}完整接入流程
下面是桌面客户端最常见的接入顺序。照这个流程做,用户换设备、网络重试、次数扣减和后台排查都能对应起来。
后台创建项目和激活码
在后台项目管理里创建项目,复制 projectKey 和 API Secret,然后生成 TIME 或 COUNT 激活码。
客户端只需要这三个值:Base URL、projectKey、API Secret;激活码发给用户输入。
客户端保存稳定 machineId
Python 程序首次运行时生成一个 UUID,保存到本机配置文件或系统钥匙串,以后每次请求都复用。
服务端会把激活码绑定到这个设备;换电脑或重装后,会走自助换绑策略。
用户输入激活码后调用 activate
把 code、machineId、projectKey 发到 /api/license/activate。TIME 从这里开始算有效期,COUNT 这里只绑定设备。
激活成功后保存 code 和 machineId;不要在 activate 时扣减次数。
程序启动时调用 status
用已保存的 code + machineId 调用 /api/license/status,判断 success 和 valid,再决定是否开放付费功能。
status 只查询授权,不扣次数,适合启动检查和授权信息展示。
功能真正完成后调用 consume
COUNT 次数卡在每次真实使用成功后调用 /api/license/consume,并为这次业务动作生成 requestId。
同一个 requestId 重试不会重复扣次;TIME 授权调用 consume 只做有效性校验。
在后台日志里排查问题
遇到客户反馈时,用激活码、machineId 或 requestId 在后台消费日志和绑定历史里搜索。
可以确认是否已绑定其他设备、是否命中自助换绑、是否重复请求或次数用完。
公共请求参数
正式接口共用以下字段。projectKey 用于项目隔离,code 是激活码正文,machineId 表示设备,requestId 用于 consume 幂等扣次。
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| projectKey / project_key | string | 否 | 项目标识。建议每个桌面软件或产品使用独立 projectKey;不传时走 default 项目。 |
| code | string | 是 | 后台生成并发放给用户的激活码正文。 |
| machineId / machine_id | string | 是 | 设备唯一标识。必须稳定保存,不能每次启动重新生成。 |
| requestId / request_id | string | 仅 consume 推荐 | 每次真实业务动作的唯一 ID。网络重试时复用同一个值,不会重复扣次。 |
响应数据结构
响应同时返回 camelCase 和 snake_case 字段,便于新旧客户端共存。前端展示授权状态时优先使用 licenseMode、expiresAt、remainingCount、valid 和 message。
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| success | boolean | 总是返回 | 业务是否通过。客户端一定要判断这个字段,不要只看 HTTP 状态码。 |
| message | string | 总是返回 | 失败原因或成功提示,可直接用于日志,也可整理后展示给用户。 |
| licenseMode / license_mode | TIME | COUNT | null | 按场景返回 | 授权模型;TIME 表示有效期授权,COUNT 表示按次数消费。 |
| expiresAt / expires_at | string | null | 时间型常用 | TIME 模式的过期时间;COUNT 模式通常为空。 |
| remainingCount / remaining_count | number | null | 次数型常用 | COUNT 模式的剩余次数;TIME 模式通常为空。 |
| isActivated / is_activated | boolean | null | 按场景返回 | 当前激活码是否已经绑定到设备。 |
| valid | boolean | null | 按场景返回 | 当前授权是否仍可继续使用。启动检查时重点看 success 和 valid。 |
| idempotent | boolean | null | consume 常用 | consume 是否命中 requestId 幂等重放;true 表示没有再次扣次。 |
1. 激活码绑定设备
用户第一次输入激活码时调用。服务端会把激活码绑定到当前 machineId。
登录授权页、设置页输入激活码、用户换设备后重新激活时调用。
请求说明
{
"projectKey": "browser-plugin",
"code": "A1B2C3D4E5F6G7H8",
"machineId": "machine-001"
}{
"success": true,
"message": "激活码激活成功",
"licenseMode": "COUNT",
"license_mode": "COUNT",
"expiresAt": null,
"expires_at": null,
"remainingCount": 2,
"remaining_count": 2,
"isActivated": true,
"is_activated": true,
"valid": true,
"idempotent": null
}2. 查询授权是否有效
检查当前 code + machineId 是否还能使用,并返回剩余次数或过期时间。
程序启动、用户打开授权信息页、用户点击刷新授权状态时调用。
请求说明
{
"project_key": "browser-plugin",
"code": "A1B2C3D4E5F6G7H8",
"machine_id": "machine-001"
}{
"success": true,
"message": "获取激活码状态成功",
"licenseMode": "COUNT",
"license_mode": "COUNT",
"expiresAt": null,
"expires_at": null,
"remainingCount": 2,
"remaining_count": 2,
"isActivated": true,
"is_activated": true,
"valid": true,
"idempotent": null
}3. 次数卡扣一次
COUNT 授权每次真实使用成功后调用一次;TIME 授权调用它只做有效性校验。
导出、生成、分析、识别、下载等付费功能真正完成后调用。
请求说明
{
"projectKey": "browser-plugin",
"code": "A1B2C3D4E5F6G7H8",
"machineId": "machine-001",
"requestId": "req-001"
}{
"success": true,
"message": "激活码验证成功",
"licenseMode": "COUNT",
"license_mode": "COUNT",
"remainingCount": 1,
"remaining_count": 1,
"isActivated": true,
"is_activated": true,
"valid": true,
"idempotent": false
}旧接口:verify
旧客户端兼容入口,会把验证和消费混在一个接口里。
只用于历史版本。新 Python 桌面程序不要使用这个接口。
请求说明
{
"project_key": "browser-plugin",
"code": "A1B2C3D4E5F6G7H8",
"machine_id": "machine-001"
}{
"success": true,
"message": "激活码验证成功",
"license_mode": "COUNT",
"expires_at": null,
"remaining_count": 1
}可直接参考的代码
Python 示例是桌面客户端最小可用接入方式,包含签名、machineId 持久化和三个正式接口。cURL 只用于看请求结构。
适合 PyQt、Tkinter、Flet、命令行工具等 Python 客户端。示例包含 machineId 持久化和 API Secret 签名。
import hashlib
import hmac
import json
import time
import uuid
from pathlib import Path
from urllib.parse import urlparse
import requests
BASE_URL = "https://your-domain.com"
PROJECT_KEY = "desktop-app"
API_SECRET = "paste-project-api-secret-here"
APP_NAME = "MyDesktopApp"
def get_machine_id() -> str:
config_dir = Path.home() / ".config" / APP_NAME
config_dir.mkdir(parents=True, exist_ok=True)
machine_file = config_dir / "machine_id.txt"
if machine_file.exists():
machine_id = machine_file.read_text(encoding="utf-8").strip()
if machine_id:
return machine_id
machine_id = "machine-" + str(uuid.uuid4())
machine_file.write_text(machine_id, encoding="utf-8")
return machine_id
def signed_post(path: str, payload: dict) -> dict:
url = BASE_URL.rstrip("/") + path
body_payload = {"projectKey": PROJECT_KEY, **payload}
body = json.dumps(body_payload, separators=(",", ":"), ensure_ascii=False)
timestamp = str(int(time.time()))
nonce = str(uuid.uuid4())
body_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
canonical = "\n".join(["POST", urlparse(url).path, timestamp, nonce, body_hash])
signature = hmac.new(
API_SECRET.encode("utf-8"),
canonical.encode("utf-8"),
hashlib.sha256,
).hexdigest()
response = requests.post(
url,
data=body.encode("utf-8"),
headers={
"Content-Type": "application/json",
"X-License-Timestamp": timestamp,
"X-License-Nonce": nonce,
"X-License-Signature": signature,
"X-License-Signature-Version": "v1",
},
timeout=10,
)
return response.json()
machine_id = get_machine_id()
code = input("请输入激活码: ").strip()
activate = signed_post("/api/license/activate", {
"code": code,
"machineId": machine_id,
})
print("activate:", activate)
status = signed_post("/api/license/status", {
"code": code,
"machineId": machine_id,
})
print("status:", status)
consume = signed_post("/api/license/consume", {
"code": code,
"machineId": machine_id,
"requestId": str(uuid.uuid4()),
})
print("consume:", consume)适合临时查看请求体结构。若项目启用了 API Secret,正式请求仍需要带签名头。
# activate: 用户输入激活码后调用
curl -X POST "https://your-domain.com/api/license/activate" \
-H "Content-Type: application/json" \
-d '{
"projectKey": "desktop-app",
"code": "A1B2C3D4E5F6G7H8",
"machineId": "machine-001"
}'
# status: 程序启动时查询授权
curl -X POST "https://your-domain.com/api/license/status" \
-H "Content-Type: application/json" \
-d '{
"projectKey": "desktop-app",
"code": "A1B2C3D4E5F6G7H8",
"machineId": "machine-001"
}'
# consume: 次数卡真实使用成功后调用
curl -X POST "https://your-domain.com/api/license/consume" \
-H "Content-Type: application/json" \
-d '{
"projectKey": "desktop-app",
"code": "A1B2C3D4E5F6G7H8",
"machineId": "machine-001",
"requestId": "req-001"
}'适合浏览器插件、Electron、Tauri 或 Node 项目。Python 客户端可直接参考上面的签名规则。
import { createLicenseClient } from '@/lib/license-sdk'
const client = createLicenseClient({
baseUrl: 'https://your-domain.com',
projectKey: 'desktop-app',
apiSecret: 'paste-project-api-secret-here',
timeoutMs: 10000,
})
await client.activate({
code: 'A1B2C3D4E5F6G7H8',
machineId: 'machine-001',
})
await client.status({
code: 'A1B2C3D4E5F6G7H8',
machineId: 'machine-001',
})
await client.consume({
code: 'A1B2C3D4E5F6G7H8',
machineId: 'machine-001',
requestId: crypto.randomUUID(),
})后台需要做什么
客户端不要调用后台接口。后台只负责创建项目、复制 projectKey / API Secret、生成激活码、查看绑定和消费日志。
项目与发码
用于准备接入环境:先创建 projectKey,再为对应项目生成 TIME 或 COUNT 激活码。
创建项目,为不同产品、插件或客户生成独立 projectKey。
维护项目名称、描述、启停状态和项目级策略。
为指定项目批量生成 TIME 或 COUNT 激活码。
查看已发放激活码,反查绑定设备、剩余次数和有效期。
日志与统计
用于排查客户反馈、核对扣次结果和导出运营数据。
按 projectKey、关键词和时间范围查询消费日志。
按当前筛选条件导出消费日志 CSV,便于对账和留档。
查看 1-90 天消费趋势,支持按日、周、月聚合。
查看全局和项目级授权统计。