Giao thức CLV1 đã công khaiĐọc đặc tả
Mục lục thư viện

Thư viện tài liệuBán hàng

Bán hàng qua Seller API

Seller API cho phép cửa hàng, bot Discord hay hệ thống nội bộ của bạn tự tạo và quản lý license qua HTTP, không cần mở dashboard. Hướng dẫn này đi từ cách tạo một khoá API an toàn, xác thực, ghi idempotent và rate limit, tới một ví dụ hoàn chỉnh bán license từ backend cửa hàng.

12 phút đọc

Trước khi bắt đầu

  • Khoá API nằm ở Kinh doanh › Khoá Seller API. Chỉ Chủ sở hữu và Quản trị của workspace được tạo, xoay và thu hồi khoá.
  • Mọi endpoint nằm dưới /seller/v1 trên địa chỉ API của bạn. Ví dụ trong bài dùng https://api.example.com; hộp thoại hiện khoá mới có sẵn một lệnh curl mẫu với địa chỉ thật.
  • Mỗi khoá thuộc một workspace và chỉ chạm được tới ứng dụng của workspace đó.
  • Số khoá theo gói dịch vụ, mặc định 2 trên Basic và 25 trên Premium. Con số áp cho workspace của bạn hiện ở chú thích cuối trang khoá và ở Tài khoản & tổ chức › Gói & sử dụng. Hạn mức tính mọi khoá chưa bị thu hồi.

Tạo khoá API

  1. Mở Kinh doanh › Khoá Seller API và bấm Tạo khoá.
  2. Trong khung Khoá API mới, điền các mục ở bảng dưới.
  3. Bấm Tạo khoá.
  4. Hộp thoại Khoá mới của bạn hiện khoá đầy đủ. Sao chép vào trình quản lý bí mật rồi bấm Đã cất.
MụcÝ nghĩa
Tên khoáTối đa 64 ký tự, nên đặt theo hệ thống dùng khoá, ví dụ "Cửa hàng Shopify". Tên được ghi kèm mỗi thao tác ghi mà khoá thực hiện.
QuyềnBốn nút mẫu và lưới ô đọc / ghi cho từng nhóm tài nguyên. Khung mở sẵn mẫu Cửa hàng. Phải chọn ít nhất một quyền.
Giới hạn appTích các ứng dụng khoá được dùng, tối đa 50. Không chọn là mọi app của workspace, kể cả app tạo sau này.
IP được phépIPv4, IPv6 hoặc dải CIDR, cách nhau bằng dấu phẩy hoặc khoảng trắng, tối đa 50 mục. Để trống là gọi được từ mọi nơi.
Hết hạn vàoKhông bắt buộc. Khoá ngừng hoạt động lúc 23:59:59 UTC của ngày đã chọn.

Quyền (scope)

Khoá chỉ làm được những gì scope cho phép, và scope :read không bao giờ thay đổi dữ liệu. Riêng GET /seller/v1/me và GET /seller/v1/apps không cần scope nào.

Nhómđọcghi
licensesDanh sách, chi tiết, tra cứu theo keyTạo key, thao tác, xoá
usersNgười dùng cuốiTạo, cấm, bỏ cấm, đặt lại mật khẩu, đăng xuất mọi phiên, xoá
devicesThiết bị, lịch sử chuyển máyGỡ, chặn, bỏ chặn
sessionsPhiên đang chạyKết thúc một phiên hoặc mọi phiên của app
variablesBiến (giá trị bí mật trả về null)Đặt và xoá biến
releasesBản phát hànhPhát hành bản nháp, chặn, bỏ chặn
rulesLuật truy cậpThêm, xoá luật
webhooksWebhookTạo, xoá, gửi thử
statsSố license theo trạng thái, người dùng, phiên, lượt xác thực 24 giờ qua
protectTóm tắt và sự kiện Protect 24/7
resellersĐại lý, số dư, sổ cáiNạp hoặc trừ tín dụng

Các mẫu có sẵn: Chỉ đọc (mọi scope :read), Bot Discord (licenses, users, devices, cả đọc lẫn ghi), Cửa hàng (licenses:read và licenses:write, đủ để bán và tra cứu key) và Toàn quyền (mọi scope).

Khoá chỉ hiện một lần

Sau đó, mỗi dòng trong danh sách chỉ hiện tên, 13 ký tự đầu của khoá, các scope, số IP được phép, số app bị giới hạn và Dùng lần cuối kèm IP gọi (cập nhật tối đa mỗi phút một lần). Tiền tố clv_live_ cũng giúp công cụ quét bí mật dễ nhận ra khoá nếu nó lỡ bị đẩy lên kho mã.

Xoay và thu hồi

  • Xoay khoá (biểu tượng mũi tên vòng trên dòng khoá) tạo ngay một khoá mới cùng tên, scope, app, IP và ngày hết hạn, hiện một lần trong hộp thoại Khoá mới của bạn. Khoá cũ vẫn chạy thêm 7 ngày để bạn kịp cập nhật máy chủ; dòng của nó ghi Hết hạn ân hạn kèm thời điểm. Qua 7 ngày, khoá cũ bị từ chối.
  • Thu hồi hỏi xác nhận, rồi khoá bị từ chối ngay từ lời gọi kế tiếp. Không hoàn tác được.
  • Hai nút này chỉ có trên khoá Đang hoạt động. Scope, app, IP và ngày hết hạn không sửa được sau khi tạo; muốn đổi, tạo khoá mới rồi thu hồi khoá cũ.

Xác thực

Gửi khoá trong header Authorization ở mọi lời gọi:

curl https://api.example.com/seller/v1/me \
  -H "Authorization: Bearer $CLAVIS_API_KEY"

/me trả về ID, tên, tiền tố, scope, các app được phép, loại khoá (seller) và workspaceId. Đây là cách nhanh nhất để kiểm tra khoá đã cấu hình đúng. Khi một lời gọi bị từ chối:

Tình huốngHTTPMã lỗi
Khoá nằm trên URL (api_key, apikey, key, token, access_token, secret trong query string)400key_in_url
Thiếu khoá, sai định dạng, không tồn tại, đã thu hồi, đã hết hạn hoặc đã qua 7 ngày sau khi xoay401unauthorized
IP gọi không nằm trong IP được phép403ip_not_allowed
Vượt rate limit429rate_limited
Khoá thiếu scope của endpoint403forbidden_scope
App hay đối tượng không tồn tại, hoặc nằm ngoài Giới hạn app404not_found

Mọi lỗi có cùng một dạng. Hãy ghi requestId vào log của bạn để đối chiếu khi cần:

{ "error": { "code": "forbidden_scope", "message": "This key needs the \"licenses:write\" scope", "requestId": "…" } }

Ghi idempotent

Mạng có thể rớt đúng lúc máy chủ đã tạo key xong; gọi lại mà không có gì bảo vệ sẽ sinh key thứ hai. Header Idempotency-Key giải quyết việc này cho mọi lời gọi ghi (POST, PUT, DELETE):

  • Dùng một giá trị gắn với nghiệp vụ, ví dụ mã đơn hàng order-1042, tối đa 255 ký tự.
  • Trong 24 giờ, gửi lại cùng khoá với cùng phương thức, đường dẫn và body sẽ nhận lại đúng câu trả lời đầu tiên, kèm header Idempotent-Replayed: true, và không có gì được làm lần hai.
  • Cùng khoá nhưng body khác: 422 idempotency_mismatch. Lời gọi đầu còn đang chạy: 409 idempotency_in_progress, chờ một chút rồi gửi lại.
  • Câu trả lời 5xx không được lưu, nên gọi lại với cùng khoá sẽ chạy lại thao tác. Lỗi do chính thao tác trả về, như plan_limit hay unknown_policy, thì được lưu và phát lại; sửa nguyên nhân xong hãy dùng khoá mới.
  • Bản ghi idempotency thuộc về từng khoá API. Khoá mới sau khi xoay không thấy bản ghi của khoá cũ.

Rate limit và phân trang

  • Mỗi khoá được 120 lời gọi mỗi phút, 600 nếu workspace dùng Premium. Bộ đếm làm mới vào đầu mỗi phút.
  • Câu trả lời cho lời gọi đã qua bước kiểm tra khoá và IP có RateLimit-Limit, RateLimit-Remaining và RateLimit-Reset (số giây tới lúc làm mới). Vượt mức nhận 429 rate_limited; chờ theo RateLimit-Reset rồi gọi lại.
  • Các danh sách lớn (license, người dùng, thiết bị, phiên) nhận limit từ 1 tới 100, mặc định 50, cùng cursor, và trả { "data": [...], "nextCursor": "…" }. Truyền nextCursor vào cursor cho tới khi nó là null.

Ví dụ: bán license từ backend cửa hàng

Kịch bản: khách thanh toán xong, cổng thanh toán báo cho backend của bạn, backend tạo một key, gửi cho khách và lưu ID license vào đơn hàng.

Chuẩn bị

  1. Tạo khoá với mẫu Cửa hàng, tích đúng ứng dụng ở Giới hạn app và điền IP máy chủ cửa hàng.
  2. Lưu khoá vào biến môi trường CLAVIS_API_KEY trên máy chủ. Không bao giờ đặt khoá trong mã chạy trên trình duyệt hay trong phần mềm phát cho khách.
  3. Lấy App ID app_… ở trang Tổng quan của ứng dụng, hoặc gọi GET /seller/v1/apps.
  4. Chọn gói license. Bỏ policyId thì dùng gói mặc định của ứng dụng. Dashboard chưa hiện ID của gói; để lấy pol_… của một gói khác, tạo một key bằng gói đó ở Phát hành & bản quyền › License, rồi tra key ấy bằng POST /seller/v1/licenses/lookup: câu trả lời có policyId.

Tạo key bằng curl

curl -X POST https://api.example.com/seller/v1/apps/app_…/licenses \
  -H "Authorization: Bearer $CLAVIS_API_KEY" \
  -H "Idempotency-Key: order-1042" \
  -H "Content-Type: application/json" \
  -d '{"policyId": "pol_…", "count": 1, "email": "buyer@example.com", "note": "Order 1042", "metadata": {"orderId": "1042"}}'

Thành công trả 201, keys và ids cùng thứ tự:

{
  "data": {
    "keys": ["PRO-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"],
    "ids": ["lic_…"],
    "warnings": []
  }
}
TrườngÝ nghĩa
countSố key, từ 1 tới hạn mức "Key mỗi lô" của gói dịch vụ.
policyIdGói license; phải thuộc ứng dụng trên đường dẫn, nếu không trả unknown_policy.
durationSecondsGhi đè thời hạn của gói, từ 60 giây tới 10 năm, null là vĩnh viễn. Tính từ lần kích hoạt đầu tiên.
maxDevicesGhi đè số máy, không vượt "Thiết bị mỗi key".
prefix1 tới 12 chữ in hoa hoặc chữ số, ví dụ PRO.
noteGhi chú, tối đa 500 ký tự. Nên ghi mã đơn để tìm lại.
emailEmail người mua, dùng cho cổng khách hàng và thư nhắc hết hạn.
metadataObject phẳng, giá trị là chuỗi, số hoặc boolean; trả lại ở GET /seller/v1/licenses/{id}.
expiresAtNgày ISO 8601, chỉ có ở Seller API. Key chưa kích hoạt bị từ chối sau ngày này (cộng ân hạn của gói). Khi kích hoạt lần đầu, ngày hết hạn được tính lại từ thời hạn của key, hoặc thành vĩnh viễn nếu key không có thời hạn. Muốn bán theo kỳ, dùng durationSeconds.
testtrue để tạo key TEST: không tính hạn mức ngày, tối đa 10 key một lần, hết hạn 24 giờ sau khi tạo. Bị từ chối với app_released khi app đã phát hành công khai.

Cùng việc đó bằng JavaScript

Đoạn mã dưới chạy trên Node.js 18 trở lên. Hàm clavis gửi khoá, gắn Idempotency-Key và tự gọi lại khi gặp 429, lỗi 5xx hoặc idempotency_in_progress:

const API = "https://api.example.com/seller/v1";
const APP_ID = "app_…";

async function clavis(path, { method = "GET", body, idempotencyKey } = {}) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(API + path, {
      method,
      headers: {
        Authorization: `Bearer ${process.env.CLAVIS_API_KEY}`,
        ...(body ? { "Content-Type": "application/json" } : {}),
        ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    if (res.status === 204) return null;
    const json = await res.json();
    if (res.ok) return json;
    const retry = res.status === 429 || res.status >= 500 || json.error?.code === "idempotency_in_progress";
    if (!retry || attempt === 3) {
      throw new Error(`${json.error?.code}: ${json.error?.message} (requestId ${json.error?.requestId})`);
    }
    const wait = res.status === 429 ? Number(res.headers.get("RateLimit-Reset") ?? 1) : 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, wait * 1000));
  }
}

// Gọi khi cổng thanh toán xác nhận đơn đã trả tiền
export async function fulfilOrder(order) {
  const { data } = await clavis(`/apps/${APP_ID}/licenses`, {
    method: "POST",
    idempotencyKey: `order-${order.id}`,
    body: {
      policyId: order.policyId,
      count: 1,
      email: order.email,
      note: `Order ${order.id}`,
      metadata: { orderId: String(order.id) },
    },
  });
  if (data.warnings.length) console.warn("Clavis:", data.warnings);
  return { licenseId: data.ids[0], key: data.keys[0] };
}

// Hoàn tiền: thu hồi key
export async function refundOrder(order) {
  await clavis(`/licenses/${order.licenseId}/actions`, {
    method: "POST",
    idempotencyKey: `refund-${order.id}`,
    body: { action: "revoke" },
  });
}

// Gia hạn 30 ngày, mỗi lần gia hạn một Idempotency-Key riêng
export async function renewLicense(licenseId, renewalId) {
  await clavis(`/licenses/${licenseId}/actions`, {
    method: "POST",
    idempotencyKey: `renew-${renewalId}`,
    body: { action: "extend", seconds: 30 * 86400 },
  });
}

Lỗi thường gặp khi bán

  • unknown_policy (400): policyId không thuộc ứng dụng này.
  • plan_limit: lô vượt "Key mỗi lô" hoặc số máy vượt "Thiết bị mỗi key" (400), hoặc đã hết hạn mức key trong ngày trên Basic (402). Trên Premium, key vẫn được tạo và warnings có daily_key_limit_exceeded. Xem Gói Basic, Premium và hạn mức.
  • validation_failed (400): body sai schema, details chỉ ra trường lỗi.

Tra cứu và thao tác trên license

Bot hỗ trợ hay trang quản trị của bạn thường bắt đầu từ chiếc key khách gửi tới:

curl -X POST https://api.example.com/seller/v1/licenses/lookup \
  -H "Authorization: Bearer $CLAVIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"key": "PRO-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"}'
  • Key đi trong body, không bao giờ trên URL. Máy chủ so bằng mã băm, bỏ qua chữ hoa thường và khoảng trắng thừa; không thấy thì trả 404 not_found.
  • Gửi {"mark": "0123456789.abcdef"} thay cho key để tìm license đứng sau một dấu người mua (Forensic Watermarking). Câu trả lời có thêm markDevice (id, name, platform, active) khi nửa sau của dấu khớp khoá thiết bị hiện tại của một máy, nếu không thì null.
  • License trả về có id, preview, status, maxDevices, devices (số máy đang giữ suất), activatedAt, expiresAt, note, email, policyId và appId. status là trạng thái hiệu lực: key đang dùng đã quá hạn được trả về là expired.
  • GET /seller/v1/licenses/{id} trả thêm policy (id, name, entitlements) và metadata.
  • GET /seller/v1/apps/{appId}/licenses lọc theo status (unused, active, expired, suspended, revoked), policyId và q (khớp một phần ghi chú hoặc email, không phân biệt hoa thường).

Thao tác dùng POST /seller/v1/licenses/{id}/actions và trả về license sau khi đổi:

actionTrường thêmTác dụng
suspendTạm khoá key Chưa dùng hoặc Đang dùng, kết thúc mọi phiên.
reinstateVề Đang dùng nếu từng kích hoạt, ngược lại về Chưa dùng.
revokeDừng vĩnh viễn, kết thúc mọi phiên.
extendseconds, từ 60 giây tới 10 nămGia hạn, cùng quy tắc với nút Gia hạn trong dashboard.
reset_devicesGỡ mọi máy và kết thúc mọi phiên.
notenote, tối đa 500 ký tự, null để xoáĐổi ghi chú.
emailemail, null để xoáGắn hoặc đổi email người mua.

DELETE /seller/v1/licenses/{id} xoá key: key bị thu hồi, mọi phiên kết thúc, key biến khỏi danh sách, và API trả 204. Muốn gỡ đúng một máy thay vì cả key, dùng POST /seller/v1/devices/{id}/actions với {"action": "revoke"} (scope devices:write); lần gỡ này ghi là người bán gỡ và không trừ lượt chuyển máy của khách.

Nhật ký request

Trên mỗi dòng khoá, kể cả khoá đã thu hồi, bấm biểu tượng Nhật ký gọi để mở khung Nhật ký request:

  • 50 lời gọi gần nhất, mới nhất trước, tự làm mới mỗi 10 giây.
  • Mỗi dòng có phương thức, đường dẫn (không kèm query string), mã trạng thái (tô đỏ từ 400 trở lên) và thời gian xử lý tính bằng ms.
  • Chỉ lời gọi đã qua bước kiểm tra khoá, IP và rate limit mới được ghi. Lời gọi bị từ chối với key_in_url, unauthorized, ip_not_allowed hay rate_limited không xuất hiện; nếu tích hợp không chạy mà nhật ký trống, hãy kiểm tra các nguyên nhân này trước.

Danh sách đầy đủ endpoint, mã lỗi và tài liệu OpenAPI (GET /seller/v1/openapi.json) có ở tham chiếu Seller API.

Liên quan

Bạn cần giúp gì?

Câu hỏi thường gặp

ClavisAUH là gì?

Dịch vụ bản quyền và xác thực cho phần mềm desktop: license key, khoá thiết bị, đăng nhập người dùng và phản hồi có chữ ký. Tất cả quản lý trên một dashboard

Vì sao kẻ crack không làm giả được server?

SDK chỉ tin câu trả lời ký bằng khoá bí mật của ứng dụng. Khoá đó không bao giờ rời server của chúng tôi, nên server giả không ký được và bị từ chối

Trong file chạy của tôi có gì?

Mã ứng dụng và khoá công khai. Không có bí mật nào, vì mỗi thiết bị tự tạo cặp khoá riêng ở lần chạy đầu

Có chạy được khi offline không?

Có. Mọi gói đều có token offline có chữ ký dùng được 7 ngày, ứng dụng tự kiểm tra tại máy. Premium cho bạn chọn bao lâu. License đã thu hồi vẫn có thể chạy offline trong khoảng đó, trừ khi Chế độ chỉ online đang bật

Hỗ trợ những ngôn ngữ nào?

Hiện có JavaScript và TypeScript. Tiếp theo là C++, C# và Python, cùng một giao thức công khai

Gói Basic có miễn phí thật không?

Có. Không giới hạn thời gian, không cần thẻ. Cần nhiều hơn thì lên Premium

Liên hệ